Commits

Wilson Xu  committed 5dcf20d

Format files.

  • Participants
  • Parent commits b093407

Comments (0)

Files changed (39)

-# http://docs.python.org/distutils/sourcedist.html#manifest-template
-# http://docs.python.org/distutils/sourcedist.html#specifying-the-files-to-distribute
-#
-# Tell distutils to distribute following files.
-recursive-include fbone/templates *
-recursive-include fbone/static *
-recursive-include fbone/views *
-recursive-include fbone/forms *
-recursive-include fbone/models *
-recursive-include fbone/translations *
+# http://docs.python.org/distutils/sourcedist.html#manifest-template
+# http://docs.python.org/distutils/sourcedist.html#specifying-the-files-to-distribute
+#
+# Tell distutils to distribute following files.
+recursive-include fbone/templates *
+recursive-include fbone/static *
+recursive-include fbone/views *
+recursive-include fbone/forms *
+recursive-include fbone/models *
+recursive-include fbone/translations *
-Fbone
-======
-A Flask skeleton
-Functions
----------
-    Login
-    Remember me
-    Signup
-    Follow
-    Reauth
-    Settings
-    Send email
-    i18n
-    api
-Maintain
---------
-    Unit testing
-    Commands to manipulate database
-    Auto deployment
-    Send email once error occurs
-
-
-Component
-=========
-Flask
-    Flask-SQLAlchemy
-    Flask-WTF
-    Flask-Script
-    Flask-Babel
-    Flask-Testing
-    Flask-Uploads
-    Flask-Mail
-    Flask-Cache
-    Flask-Login
-nose
-HTML5 Boilerplate
-jQuery
-
-
-Install
-=======
-python setup.py install
-
-
-Command
-=======
-Interact with fbone with command line.
-
-# Init 
-python manage.py init
-
-# Run local server
-python manage.py run
-
-# Reset database
-python manage.py db
-
-# Compile babel
-python manage.py babel
-
-# Unittest
-python manage.py nose 
-
-# Unittest
-python manage.py deploy 
-
-
-Compile Babel
-=============
-python setup.py compile_catalog --directory fbone/translations --locale zh -f
+Fbone
+======
+A Flask skeleton
+Functions
+---------
+    Login
+    Remember me
+    Signup
+    Follow
+    Reauth
+    Settings
+    Send email
+    i18n
+    api
+Maintain
+--------
+    Unit testing
+    Commands to manipulate database
+    Auto deployment
+    Send email once error occurs
+
+
+Component
+=========
+Flask
+    Flask-SQLAlchemy
+    Flask-WTF
+    Flask-Script
+    Flask-Babel
+    Flask-Testing
+    Flask-Uploads
+    Flask-Mail
+    Flask-Cache
+    Flask-Login
+nose
+HTML5 Boilerplate
+jQuery
+
+
+Install
+=======
+python setup.py install
+
+
+Command
+=======
+Interact with fbone with command line.
+
+# Init 
+python manage.py init
+
+# Run local server
+python manage.py run
+
+# Reset database
+python manage.py db
+
+# Compile babel
+python manage.py babel
+
+# Unittest
+python manage.py nose 
+
+# Unittest
+python manage.py deploy 
+
+
+Compile Babel
+=============
+python setup.py compile_catalog --directory fbone/translations --locale zh -f
-# -*- coding: utf-8 -*-
-
-from fabric.api import *
-
-# the user to use for the remote commands
-env.user = 'user_of_deploy_machine'
-# the servers where the commands are executed
-env.hosts = ['ip_of_deploy_machine']
-
-def pack():
-    # create a new source distribution as tarball
-    local('python setup.py sdist --formats=gztar', capture=False)
-
-def deploy():
-    pack()
-    # figure out the release name and version
-    dist = local('python setup.py --fullname', capture=True).strip()
-    # upload the source tarball to the temporary folder on the server
-    put('dist/%s.tar.gz' % dist, '/tmp/fbone.tar.gz')
-    # create a place where we can unzip the tarball, then enter
-    # that directory and unzip it
-    run('mkdir /tmp/fbone')
-    with cd('/tmp/fbone'):
-        run('tar xzf /tmp/fbone.tar.gz')
-    with cd('/tmp/fbone/%s' % dist):
-        # now setup the package with our virtual environment's
-        # python interpreter
-        run('/var/www/fbone/env/bin/python setup.py install')
-    # now that all is set up, delete the folder again
-    run('rm -rf /tmp/fbone /tmp/fbone.tar.gz')
-    # and finally touch the .wsgi file so that mod_wsgi triggers
-    # a reload of the application
-    run('touch /var/www/fbone.wsgi')
+# -*- coding: utf-8 -*-
+
+from fabric.api import *
+
+# the user to use for the remote commands
+env.user = 'user_of_deploy_machine'
+# the servers where the commands are executed
+env.hosts = ['ip_of_deploy_machine']
+
+def pack():
+    # create a new source distribution as tarball
+    local('python setup.py sdist --formats=gztar', capture=False)
+
+def deploy():
+    pack()
+    # figure out the release name and version
+    dist = local('python setup.py --fullname', capture=True).strip()
+    # upload the source tarball to the temporary folder on the server
+    put('dist/%s.tar.gz' % dist, '/tmp/fbone.tar.gz')
+    # create a place where we can unzip the tarball, then enter
+    # that directory and unzip it
+    run('mkdir /tmp/fbone')
+    with cd('/tmp/fbone'):
+        run('tar xzf /tmp/fbone.tar.gz')
+    with cd('/tmp/fbone/%s' % dist):
+        # now setup the package with our virtual environment's
+        # python interpreter
+        run('/var/www/fbone/env/bin/python setup.py install')
+    # now that all is set up, delete the folder again
+    run('rm -rf /tmp/fbone /tmp/fbone.tar.gz')
+    # and finally touch the .wsgi file so that mod_wsgi triggers
+    # a reload of the application
+    run('touch /var/www/fbone.wsgi')
-# -*- coding: utf-8 -*-
-"""
-    wsgi
-    ~~~~
-
-    Deploy with apache2 wsgi.
-"""
-
-import sys, os, pwd
-os.environ['FBONE_APP_CONFIG'] = ''
-# http://code.google.com/p/modwsgi/wiki/ApplicationIssues#User_HOME_Environment_Variable
-os.environ['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
-
-activate_this = '/var/www/fbone/env/bin/activate_this.py'
-execfile(activate_this, dict(__file__=activate_this))
-
-BASE_DIR = os.path.join(os.path.dirname(__file__))
-if BASE_DIR not in sys.path:
-    sys.path.append(BASE_DIR)
-
-from fbone import create_app
-application = create_app()
+# -*- coding: utf-8 -*-
+"""
+    wsgi
+    ~~~~
+
+    Deploy with apache2 wsgi.
+"""
+
+import sys, os, pwd
+os.environ['FBONE_APP_CONFIG'] = ''
+# http://code.google.com/p/modwsgi/wiki/ApplicationIssues#User_HOME_Environment_Variable
+os.environ['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
+
+activate_this = '/var/www/fbone/env/bin/activate_this.py'
+execfile(activate_this, dict(__file__=activate_this))
+
+BASE_DIR = os.path.join(os.path.dirname(__file__))
+if BASE_DIR not in sys.path:
+    sys.path.append(BASE_DIR)
+
+from fbone import create_app
+application = create_app()

File fbone/__init__.py

-# -*- coding: utf-8 -*-
-
-from app import create_app
+# -*- coding: utf-8 -*-
+
+from app import create_app

File fbone/config.py

-# -*- coding: utf-8 -*-
-
-class BaseConfig(object):
-    DEBUG = False
-    TESTING = False
-    SECRET_KEY = '\xa4D\x08\xd0\x9a\xcfQl_\\\xa3\xd3\xdf\xdd$\t\x88}\xe0\xd7\xe4Un\x8b'
-
-
-class DefaultConfig(BaseConfig):
-    DEBUG = True
-
-    SQLALCHEMY_ECHO = True
-    SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/fbone.db'
-    
-    # To create log folder. 
-    # $ sudo mkdir -p /var/log/fbone
-    # $ sudo chown $USER /var/log/fbone
-    DEBUG_LOG = '/var/log/fbone/debug.log'
-    
-    ACCEPT_LANGUAGES = ['zh']
-    BABEL_DEFAULT_LOCALE = 'zh'
-
-    CACHE_TYPE = 'simple'
-    CACHE_DEFAULT_TIMEOUT = 60
-
-    DEFAULT_MAIL_SENDER = 'imwilsonxu@gmail.com'
-
-
-class TestConfig(BaseConfig):
-    TESTING = True
-    CSRF_ENABLED = False
-    
-    SQLALCHEMY_ECHO = False
-    SQLALCHEMY_DATABASE_URI = 'sqlite://'
+# -*- coding: utf-8 -*-
+
+class BaseConfig(object):
+    DEBUG = False
+    TESTING = False
+    SECRET_KEY = '\xa4D\x08\xd0\x9a\xcfQl_\\\xa3\xd3\xdf\xdd$\t\x88}\xe0\xd7\xe4Un\x8b'
+
+
+class DefaultConfig(BaseConfig):
+    DEBUG = True
+
+    SQLALCHEMY_ECHO = True
+    SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/fbone.db'
+    
+    # To create log folder. 
+    # $ sudo mkdir -p /var/log/fbone
+    # $ sudo chown $USER /var/log/fbone
+    DEBUG_LOG = '/var/log/fbone/debug.log'
+    
+    ACCEPT_LANGUAGES = ['zh']
+    BABEL_DEFAULT_LOCALE = 'zh'
+
+    CACHE_TYPE = 'simple'
+    CACHE_DEFAULT_TIMEOUT = 60
+
+    DEFAULT_MAIL_SENDER = 'imwilsonxu@gmail.com'
+
+
+class TestConfig(BaseConfig):
+    TESTING = True
+    CSRF_ENABLED = False
+    
+    SQLALCHEMY_ECHO = False
+    SQLALCHEMY_DATABASE_URI = 'sqlite://'

File fbone/decorators.py

-# -*- coding: utf-8 -*-
-
-from functools import wraps
-
-from flask import g, url_for, flash, redirect, Markup, request
-from flaskext.babel import gettext as _
-
-
-def keep_login_url(func):
-    """
-    Adds attribute g.keep_login_url in order to pass the current
-    login URL to login/signup links.
-    """
-    @wraps(func)
-    def wrapper(*args, **kwargs):
-        g.keep_login_url = True
-        return func(*args, **kwargs)
-    return wrapper
+# -*- coding: utf-8 -*-
+
+from functools import wraps
+
+from flask import g, url_for, flash, redirect, Markup, request
+from flaskext.babel import gettext as _
+
+
+def keep_login_url(func):
+    """
+    Adds attribute g.keep_login_url in order to pass the current
+    login URL to login/signup links.
+    """
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        g.keep_login_url = True
+        return func(*args, **kwargs)
+    return wrapper

File fbone/extensions.py

-# -*- coding: utf-8 -*-
-
-from flaskext.sqlalchemy import SQLAlchemy
-db = SQLAlchemy()
-
-from flaskext.mail import Mail
-mail = Mail()
-
-from flaskext.cache import Cache
-cache = Cache()
-
-from flaskext.login import LoginManager
-login_manager = LoginManager()
+# -*- coding: utf-8 -*-
+
+from flaskext.sqlalchemy import SQLAlchemy
+db = SQLAlchemy()
+
+from flaskext.mail import Mail
+mail = Mail()
+
+from flaskext.cache import Cache
+cache = Cache()
+
+from flaskext.login import LoginManager
+login_manager = LoginManager()

File fbone/forms/__init__.py

-# -*- coding: utf-8 -*-
-
-from fbone.forms.frontend import (SignupForm, LoginForm, RecoverPasswordForm,
-                                  ChangePasswordForm, ReauthForm)
+# -*- coding: utf-8 -*-
+
+from fbone.forms.frontend import (SignupForm, LoginForm, RecoverPasswordForm,
+                                  ChangePasswordForm, ReauthForm)

File fbone/forms/frontend.py

-# -*- coding: utf-8 -*-
-
-from flaskext.wtf import (Form, HiddenField, BooleanField, TextField,
-                          PasswordField, SubmitField, TextField,
-                          ValidationError, required, equal_to, email,
-                          length)
-from flaskext.babel import gettext, lazy_gettext as _ 
-
-from fbone.models import User
-
-
-class LoginForm(Form):
-    next = HiddenField()
-    remember = BooleanField(_('Remember me'))
-    login = TextField(_('Username or email address'), [required()])
-    password = PasswordField(_('Password'), [required(), length(min=6, max=16)])
-    submit = SubmitField(_('Login'))
-
-
-class SignupForm(Form):
-    next = HiddenField()
-    name = TextField(_('Username'), [required()])
-    password = PasswordField(_('Password'), [required(), length(min=6, max=16)])
-    password_again = PasswordField(_('Password again'), [required(), length(min=6, max=16), equal_to('password')])
-    email = TextField(_('Email address'), [required(), email(message=_('A valid email address is required'))])
-    submit = SubmitField(_('Signup'))
-
-    def validate_name(self, field):
-        if User.query.filter_by(name=field.data).first() is not None:
-            raise ValidationError, gettext('This username is taken')
-
-    def validate_email(self, field):
-        if User.query.filter_by(email=field.data).first() is not None:
-            raise ValidationError, gettext('This email is taken')
-
-
-class RecoverPasswordForm(Form):
-    email = TextField(_('Your email'), validators=[
-                      email(message=_('A valid email address is required'))])
-    submit = SubmitField(_('Send instructions'))
-
-
-class ChangePasswordForm(Form):
-    activation_key = HiddenField()
-    password = PasswordField('Password', validators=[
-                             required(message=_('Password is required'))])
-    password_again = PasswordField(_('Password again'), validators=[
-                                   equal_to('password', message=\
-                                            _("Passwords don't match"))])
-    submit = SubmitField(_('Save'))
-
-
-class ReauthForm(Form):
-    next = HiddenField()
-    password = PasswordField(_('Password'), [required(), length(min=6, max=16)])
-    submit = SubmitField(_('Reauthenticate'))
+# -*- coding: utf-8 -*-
+
+from flaskext.wtf import (Form, HiddenField, BooleanField, TextField,
+                          PasswordField, SubmitField, TextField,
+                          ValidationError, required, equal_to, email,
+                          length)
+from flaskext.babel import gettext, lazy_gettext as _ 
+
+from fbone.models import User
+
+
+class LoginForm(Form):
+    next = HiddenField()
+    remember = BooleanField(_('Remember me'))
+    login = TextField(_('Username or email address'), [required()])
+    password = PasswordField(_('Password'), [required(), length(min=6, max=16)])
+    submit = SubmitField(_('Login'))
+
+
+class SignupForm(Form):
+    next = HiddenField()
+    name = TextField(_('Username'), [required()])
+    password = PasswordField(_('Password'), [required(), length(min=6, max=16)])
+    password_again = PasswordField(_('Password again'), [required(), length(min=6, max=16), equal_to('password')])
+    email = TextField(_('Email address'), [required(), email(message=_('A valid email address is required'))])
+    submit = SubmitField(_('Signup'))
+
+    def validate_name(self, field):
+        if User.query.filter_by(name=field.data).first() is not None:
+            raise ValidationError, gettext('This username is taken')
+
+    def validate_email(self, field):
+        if User.query.filter_by(email=field.data).first() is not None:
+            raise ValidationError, gettext('This email is taken')
+
+
+class RecoverPasswordForm(Form):
+    email = TextField(_('Your email'), validators=[
+                      email(message=_('A valid email address is required'))])
+    submit = SubmitField(_('Send instructions'))
+
+
+class ChangePasswordForm(Form):
+    activation_key = HiddenField()
+    password = PasswordField('Password', validators=[
+                             required(message=_('Password is required'))])
+    password_again = PasswordField(_('Password again'), validators=[
+                                   equal_to('password', message=\
+                                            _("Passwords don't match"))])
+    submit = SubmitField(_('Save'))
+
+
+class ReauthForm(Form):
+    next = HiddenField()
+    password = PasswordField(_('Password'), [required(), length(min=6, max=16)])
+    submit = SubmitField(_('Reauthenticate'))

File fbone/models/__init__.py

-# -*- coding: utf-8 -*-
-
-from fbone.models.types import DenormalizedText
-from fbone.models.user import User
+# -*- coding: utf-8 -*-
+
+from fbone.models.types import DenormalizedText
+from fbone.models.user import User

File fbone/models/types.py

-# -*- coding: utf-8 -*-
-
-from sqlalchemy import types
-
-
-class DenormalizedText(types.MutableType, types.TypeDecorator):
-    """
-    Stores denormalized primary keys that can be 
-    accessed as a set. 
-
-    :param coerce: coercion function that ensures correct
-                   type is returned
-
-    :param separator: separator character
-    """
-
-    impl = types.Text
-
-    def __init__(self, coerce=int, separator=" ", **kwargs):
-
-        self.coerce = coerce
-        self.separator = separator
-        
-        super(DenormalizedText, self).__init__(**kwargs)
-
-    def process_bind_param(self, value, dialect):
-        if value is not None:
-            items = [str(item).strip() for item in value]
-            value = self.separator.join(item for item in items if item)
-        return value
-
-    def process_result_value(self, value, dialect):
-         if not value:
-            return set()
-         return set(self.coerce(item) for item in value.split(self.separator))
-        
-    def copy_value(self, value):
-        return set(value)
+# -*- coding: utf-8 -*-
+
+from sqlalchemy import types
+
+
+class DenormalizedText(types.MutableType, types.TypeDecorator):
+    """
+    Stores denormalized primary keys that can be 
+    accessed as a set. 
+
+    :param coerce: coercion function that ensures correct
+                   type is returned
+
+    :param separator: separator character
+    """
+
+    impl = types.Text
+
+    def __init__(self, coerce=int, separator=" ", **kwargs):
+
+        self.coerce = coerce
+        self.separator = separator
+        
+        super(DenormalizedText, self).__init__(**kwargs)
+
+    def process_bind_param(self, value, dialect):
+        if value is not None:
+            items = [str(item).strip() for item in value]
+            value = self.separator.join(item for item in items if item)
+        return value
+
+    def process_result_value(self, value, dialect):
+         if not value:
+            return set()
+         return set(self.coerce(item) for item in value.split(self.separator))
+        
+    def copy_value(self, value):
+        return set(value)

File fbone/models/user.py

-# -*- coding: utf-8 -*-
-
-from werkzeug import (generate_password_hash, check_password_hash,
-                      cached_property)
-from flaskext.login import UserMixin
-
-from fbone.extensions import db
-from fbone.models import DenormalizedText
-from fbone.utils import get_current_time, VARCHAR_LEN_128 
-
-
-class User(db.Model, UserMixin):
-
-    __tablename__ = 'users'
-
-    id = db.Column(db.Integer, primary_key=True)
-    name = db.Column(db.String(VARCHAR_LEN_128), nullable=False, unique=True)
-    email = db.Column(db.String(VARCHAR_LEN_128), nullable=False, unique=True)
-    _password = db.Column('password', db.String(VARCHAR_LEN_128), nullable=False)
-    activation_key = db.Column(db.String(VARCHAR_LEN_128))
-    followers = db.Column(DenormalizedText)
-    following = db.Column(DenormalizedText)
-    created_time = db.Column(db.DateTime, default=get_current_time)
-    updated_time = db.Column(db.DateTime, default=get_current_time,
-                             onupdate=get_current_time)
-
-    def __repr__(self):
-        return '<User %r>' % self.name
-
-    def _get_password(self):
-        return self._password
-
-    def _set_password(self, password):
-        self._password = generate_password_hash(password)
-
-    # Hide password encryption by exposing password field only.
-    password = db.synonym('_password',
-                          descriptor=property(_get_password,
-                                              _set_password))
-
-    def check_password(self, password):
-        if self.password is None:
-            return False
-        return check_password_hash(self.password, password)
-
-    @property
-    def num_followers(self):
-        if self.followers:
-            return len(self.followers)
-        return 0
-
-    @property
-    def num_following(self):
-        return len(self.following)
-    
-    def follow(self, user):
-        user.followers.add(self.id)
-        self.following.add(user.id)
-
-    def unfollow(self, user):
-        if self.id in user.followers:
-            user.followers.remove(self.id)
-
-        if user.id in self.following:
-            self.following.remove(user.id)
-
-    def get_following_query(self):
-        return User.query.filter(User.id.in_(self.following or set()))
-
-    def get_followers_query(self):
-        return User.query.filter(User.id.in_(self.followers or set()))
-
-    @classmethod
-    def authenticate(cls, login, password):
-        user = cls.query.filter(db.or_(User.name==login,
-                                  User.email==login)).first()
-
-        if user:
-            authenticated = user.check_password(password)
-        else:
-            authenticated = False
-
-        return user, authenticated
-
-    @classmethod
-    def search(cls, keywords):
-        criteria = []
-        for keyword in keywords.split():
-            keyword = '%' + keyword + '%'
-            criteria.append(db.or_(
-                User.name.ilike(keyword),
-                User.email.ilike(keyword),
-            ))
-        q = reduce(db.and_, criteria)
-        return cls.query.filter(q)
+# -*- coding: utf-8 -*-
+
+from werkzeug import (generate_password_hash, check_password_hash,
+                      cached_property)
+from flaskext.login import UserMixin
+
+from fbone.extensions import db
+from fbone.models import DenormalizedText
+from fbone.utils import get_current_time, VARCHAR_LEN_128 
+
+
+class User(db.Model, UserMixin):
+
+    __tablename__ = 'users'
+
+    id = db.Column(db.Integer, primary_key=True)
+    name = db.Column(db.String(VARCHAR_LEN_128), nullable=False, unique=True)
+    email = db.Column(db.String(VARCHAR_LEN_128), nullable=False, unique=True)
+    _password = db.Column('password', db.String(VARCHAR_LEN_128), nullable=False)
+    activation_key = db.Column(db.String(VARCHAR_LEN_128))
+    followers = db.Column(DenormalizedText)
+    following = db.Column(DenormalizedText)
+    created_time = db.Column(db.DateTime, default=get_current_time)
+    updated_time = db.Column(db.DateTime, default=get_current_time,
+                             onupdate=get_current_time)
+
+    def __repr__(self):
+        return '<User %r>' % self.name
+
+    def _get_password(self):
+        return self._password
+
+    def _set_password(self, password):
+        self._password = generate_password_hash(password)
+
+    # Hide password encryption by exposing password field only.
+    password = db.synonym('_password',
+                          descriptor=property(_get_password,
+                                              _set_password))
+
+    def check_password(self, password):
+        if self.password is None:
+            return False
+        return check_password_hash(self.password, password)
+
+    @property
+    def num_followers(self):
+        if self.followers:
+            return len(self.followers)
+        return 0
+
+    @property
+    def num_following(self):
+        return len(self.following)
+    
+    def follow(self, user):
+        user.followers.add(self.id)
+        self.following.add(user.id)
+
+    def unfollow(self, user):
+        if self.id in user.followers:
+            user.followers.remove(self.id)
+
+        if user.id in self.following:
+            self.following.remove(user.id)
+
+    def get_following_query(self):
+        return User.query.filter(User.id.in_(self.following or set()))
+
+    def get_followers_query(self):
+        return User.query.filter(User.id.in_(self.followers or set()))
+
+    @classmethod
+    def authenticate(cls, login, password):
+        user = cls.query.filter(db.or_(User.name==login,
+                                  User.email==login)).first()
+
+        if user:
+            authenticated = user.check_password(password)
+        else:
+            authenticated = False
+
+        return user, authenticated
+
+    @classmethod
+    def search(cls, keywords):
+        criteria = []
+        for keyword in keywords.split():
+            keyword = '%' + keyword + '%'
+            criteria.append(db.or_(
+                User.name.ilike(keyword),
+                User.email.ilike(keyword),
+            ))
+        q = reduce(db.and_, criteria)
+        return cls.query.filter(q)

File fbone/static/css/button.css

-.button
-{        
-    display: inline-block;
-    white-space: nowrap;
-    background-color: #ddd;
-    background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#ccc));
-    background-image: -webkit-linear-gradient(top, #eee, #ccc);
-    background-image: -moz-linear-gradient(top, #eee, #ccc);
-    background-image: -ms-linear-gradient(top, #eee, #ccc);
-    background-image: -o-linear-gradient(top, #eee, #ccc);
-    background-image: linear-gradient(top, #eee, #ccc);
-    border: 1px solid #777;
-    padding: 0 1.5em;
-    margin: 0.5em;
-    font: bold 1em/2em Arial, Helvetica;
-    text-decoration: none;
-    color: #333;
-    text-shadow: 0 1px 0 rgba(255,255,255,.8);
-    -moz-border-radius: .2em;
-    -webkit-border-radius: .2em;
-    border-radius: .2em;
-    -moz-box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3);
-    -webkit-box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3);
-    box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3);
-}
-
-.button:hover
-{
-    background-color: #eee;        
-    background-image: -webkit-gradient(linear, left top, left bottom, from(#fafafa), to(#ddd));
-    background-image: -webkit-linear-gradient(top, #fafafa, #ddd);
-    background-image: -moz-linear-gradient(top, #fafafa, #ddd);
-    background-image: -ms-linear-gradient(top, #fafafa, #ddd);
-    background-image: -o-linear-gradient(top, #fafafa, #ddd);
-    background-image: linear-gradient(top, #fafafa, #ddd);
-}
-
-.button:active
-{
-    -moz-box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset;
-    -webkit-box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset;
-    box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset;
-    position: relative;
-    top: 1px;
-}
-
-.button:focus
-{
-    outline: 0;
-    background: #fafafa;
-}    
-
-.button:before
-{
-    background: #ccc;
-    background: rgba(0,0,0,.1);
-    float: left;        
-    width: 1em;
-    text-align: center;
-    font-size: 1.5em;
-    margin: 0 1em 0 -1em;
-    padding: 0 .2em;
-    -moz-box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5);
-    -webkit-box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5);
-    box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5);
-    -moz-border-radius: .15em 0 0 .15em;
-    -webkit-border-radius: .15em 0 0 .15em;
-    border-radius: .15em 0 0 .15em;     
-    pointer-events: none;       
-}
-
-/* Buttons and inputs */
-
-button.button, input.button 
-{ 
-    cursor: pointer;
-    overflow: visible; /* removes extra side spacing in IE */
-}
-
-/* removes extra inner spacing in Firefox */
-button::-moz-focus-inner 
-{
-  border: 0;
-  padding: 0;
-}
-
-/* If line-height can't be modified, then fix Firefox spacing with padding */
- input::-moz-focus-inner 
-{
-  padding: .4em;
-}
-
-/* The disabled styles */
-.button[disabled], .button[disabled]:hover, .button.disabled, .button.disabled:hover 
-{
-    background: #eee;
-    color: #aaa;
-    border-color: #aaa;
-    cursor: default;
-    text-shadow: none;
-    position: static;
-    -moz-box-shadow: none;
-    -webkit-box-shadow: none;
-    box-shadow: none;       
-}
-
-/* Hexadecimal entities for the icons */
-
-.add:before
-{
-    content: "\271A";
-}
-
-.edit:before
-{
-    content: "\270E";        
-}
-
-.delete:before
-{
-    content: "\2718";        
-}
-
-.save:before
-{
-    content: "\2714";        
-}
-
-.email:before
-{
-    content: "\2709";        
-}
-
-.like:before
-{
-    content: "\2764";        
-}
-
-.next:before
-{
-    content: "\279C";
-}
-
-.star:before
-{
-    content: "\2605";
-}
-
-.spark:before
-{
-    content: "\2737";
-}
-
-.play:before
-{
-    content: "\25B6";
-}
-
-
-/* Social media buttons */  
-.tw, .fb,
-.tw:hover, .fb:hover
-{
-    background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(255,255,255,.5)), to(rgba(255,255,255,0)));
-    background-image: -webkit-linear-gradient(top, rgba(255,255,255,.5), rgba(255,255,255,0));
-    background-image: -moz-linear-gradient(top, rgba(255,255,255,.5), rgba(255,255,255,0));
-    background-image: -ms-linear-gradient(top, rgba(255,255,255,.5), rgba(255,255,255,0));
-    background-image: -o-linear-gradient(top, rgba(255,255,255,.5), rgba(255,255,255,0));
-    background-image: linear-gradient(top, rgba(255,255,255,.5), rgba(255,255,255,0));
-}
-
-.tw, .tw:focus
-{
-  background-color: #88E1E6;
-}
-
-.fb, .fb:focus
-{
-  background-color: #3C5A98;
-  color: #fff;    
-  text-shadow: 0 1px 0 rgba(0,0,0,.4);      
-}
-
-.tw:hover
-{
-  background-color: #b1f0f3;
-}
-
-.fb:hover
-{
-  background-color: #879bc3; 
-}
-
-.tw:before
-{
-    content: "t";
-    background: #91cfd3;
-    background: rgba(0,0,0,.1);     
-    color: #fff;
-    font-family: verdana;
-    text-shadow: 0 1px 0 rgba(0,0,0,.4);
-}
-
-.fb:before
-{
-    content: "f";
-    background: #4467ac;
-    background: rgba(0,0,0,.1);     
-    color: #fff;    
-    text-shadow: 0 1px 0 rgba(0,0,0,.4);
-}
+.button
+{        
+    display: inline-block;
+    white-space: nowrap;
+    background-color: #ddd;
+    background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#ccc));
+    background-image: -webkit-linear-gradient(top, #eee, #ccc);
+    background-image: -moz-linear-gradient(top, #eee, #ccc);
+    background-image: -ms-linear-gradient(top, #eee, #ccc);
+    background-image: -o-linear-gradient(top, #eee, #ccc);
+    background-image: linear-gradient(top, #eee, #ccc);
+    border: 1px solid #777;
+    padding: 0 1.5em;
+    margin: 0.5em;
+    font: bold 1em/2em Arial, Helvetica;
+    text-decoration: none;
+    color: #333;
+    text-shadow: 0 1px 0 rgba(255,255,255,.8);
+    -moz-border-radius: .2em;
+    -webkit-border-radius: .2em;
+    border-radius: .2em;
+    -moz-box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3);
+    -webkit-box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3);
+    box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3);
+}
+
+.button:hover
+{
+    background-color: #eee;        
+    background-image: -webkit-gradient(linear, left top, left bottom, from(#fafafa), to(#ddd));
+    background-image: -webkit-linear-gradient(top, #fafafa, #ddd);
+    background-image: -moz-linear-gradient(top, #fafafa, #ddd);
+    background-image: -ms-linear-gradient(top, #fafafa, #ddd);
+    background-image: -o-linear-gradient(top, #fafafa, #ddd);
+    background-image: linear-gradient(top, #fafafa, #ddd);
+}
+
+.button:active
+{
+    -moz-box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset;
+    -webkit-box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset;
+    box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset;
+    position: relative;
+    top: 1px;
+}
+
+.button:focus
+{
+    outline: 0;
+    background: #fafafa;
+}    
+
+.button:before
+{
+    background: #ccc;
+    background: rgba(0,0,0,.1);
+    float: left;        
+    width: 1em;
+    text-align: center;
+    font-size: 1.5em;
+    margin: 0 1em 0 -1em;
+    padding: 0 .2em;
+    -moz-box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5);
+    -webkit-box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5);
+    box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5);
+    -moz-border-radius: .15em 0 0 .15em;
+    -webkit-border-radius: .15em 0 0 .15em;
+    border-radius: .15em 0 0 .15em;     
+    pointer-events: none;       
+}
+
+/* Buttons and inputs */
+
+button.button, input.button 
+{ 
+    cursor: pointer;
+    overflow: visible; /* removes extra side spacing in IE */
+}
+
+/* removes extra inner spacing in Firefox */
+button::-moz-focus-inner 
+{
+  border: 0;
+  padding: 0;
+}
+
+/* If line-height can't be modified, then fix Firefox spacing with padding */
+ input::-moz-focus-inner 
+{
+  padding: .4em;
+}
+
+/* The disabled styles */
+.button[disabled], .button[disabled]:hover, .button.disabled, .button.disabled:hover 
+{
+    background: #eee;
+    color: #aaa;
+    border-color: #aaa;
+    cursor: default;
+    text-shadow: none;
+    position: static;
+    -moz-box-shadow: none;
+    -webkit-box-shadow: none;
+    box-shadow: none;       
+}
+
+/* Hexadecimal entities for the icons */
+
+.add:before
+{
+    content: "\271A";
+}
+
+.edit:before
+{
+    content: "\270E";        
+}
+
+.delete:before
+{
+    content: "\2718";        
+}
+
+.save:before
+{
+    content: "\2714";        
+}
+
+.email:before
+{
+    content: "\2709";        
+}
+
+.like:before
+{
+    content: "\2764";        
+}
+
+.next:before
+{
+    content: "\279C";
+}
+
+.star:before
+{
+    content: "\2605";
+}
+
+.spark:before
+{
+    content: "\2737";
+}
+
+.play:before
+{
+    content: "\25B6";
+}
+
+
+/* Social media buttons */  
+.tw, .fb,
+.tw:hover, .fb:hover
+{
+    background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(255,255,255,.5)), to(rgba(255,255,255,0)));
+    background-image: -webkit-linear-gradient(top, rgba(255,255,255,.5), rgba(255,255,255,0));
+    background-image: -moz-linear-gradient(top, rgba(255,255,255,.5), rgba(255,255,255,0));
+    background-image: -ms-linear-gradient(top, rgba(255,255,255,.5), rgba(255,255,255,0));
+    background-image: -o-linear-gradient(top, rgba(255,255,255,.5), rgba(255,255,255,0));
+    background-image: linear-gradient(top, rgba(255,255,255,.5), rgba(255,255,255,0));
+}
+
+.tw, .tw:focus
+{
+  background-color: #88E1E6;
+}
+
+.fb, .fb:focus
+{
+  background-color: #3C5A98;
+  color: #fff;    
+  text-shadow: 0 1px 0 rgba(0,0,0,.4);      
+}
+
+.tw:hover
+{
+  background-color: #b1f0f3;
+}
+
+.fb:hover
+{
+  background-color: #879bc3; 
+}
+
+.tw:before
+{
+    content: "t";
+    background: #91cfd3;
+    background: rgba(0,0,0,.1);     
+    color: #fff;
+    font-family: verdana;
+    text-shadow: 0 1px 0 rgba(0,0,0,.4);
+}
+
+.fb:before
+{
+    content: "f";
+    background: #4467ac;
+    background: rgba(0,0,0,.1);     
+    color: #fff;    
+    text-shadow: 0 1px 0 rgba(0,0,0,.4);
+}

File fbone/static/css/style.css

-/*
- * HTML5 ✰ Boilerplate
- *
- * What follows is the result of much research on cross-browser styling.
- * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
- * Kroc Camen, and the H5BP dev community and team.
- *
- * Detailed information about this CSS: h5bp.com/css
- *
- * ==|== normalize ==========================================================
- */
-
-
-/* =============================================================================
-   HTML5 display definitions
-   ========================================================================== */
-
-article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; }
-audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; }
-audio:not([controls]) { display: none; }
-[hidden] { display: none; }
-
-
-/* =============================================================================
-   Base
-   ========================================================================== */
-
-/*
- * 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units
- * 2. Force vertical scrollbar in non-IE
- * 3. Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g
- */
-
-html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
-
-body { margin: 0; font-size: 13px; line-height: 1.231; }
-
-body, button, input, select, textarea { font-family: sans-serif; color: #222; }
-
-/*
- * Remove text-shadow in selection highlight: h5bp.com/i
- * These selection declarations have to be separate
- * Also: hot pink! (or customize the background color to match your design)
- */
-
-::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; }
-::selection { background: #fe57a1; color: #fff; text-shadow: none; }
-
-
-/* =============================================================================
-   Links
-   ========================================================================== */
-
-a { color: #00e; }
-a:visited { color: #551a8b; }
-a:hover { color: #06e; }
-a:focus { outline: thin dotted; }
-
-/* Improve readability when focused and hovered in all browsers: h5bp.com/h */
-a:hover, a:active { outline: 0; }
-
-
-/* =============================================================================
-   Typography
-   ========================================================================== */
-
-abbr[title] { border-bottom: 1px dotted; }
-
-b, strong { font-weight: bold; }
-
-blockquote { margin: 1em 40px; }
-
-dfn { font-style: italic; }
-
-hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
-
-ins { background: #ff9; color: #000; text-decoration: none; }
-
-mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
-
-/* Redeclare monospace font family: h5bp.com/j */
-pre, code, kbd, samp { font-family: monospace, serif; _font-family: 'courier new', monospace; font-size: 1em; }
-
-/* Improve readability of pre-formatted text in all browsers */
-pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; }
-
-q { quotes: none; }
-q:before, q:after { content: ""; content: none; }
-
-small { font-size: 85%; }
-
-/* Position subscript and superscript content without affecting line-height: h5bp.com/k */
-sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
-sup { top: -0.5em; }
-sub { bottom: -0.25em; }
-
-
-/* =============================================================================
-   Lists
-   ========================================================================== */
-
-ul, ol { margin: 1em 0; padding: 0 0 0 40px; }
-dd { margin: 0 0 0 40px; }
-nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; }
-
-
-/* =============================================================================
-   Embedded content
-   ========================================================================== */
-
-/*
- * 1. Improve image quality when scaled in IE7: h5bp.com/d
- * 2. Remove the gap between images and borders on image containers: h5bp.com/e
- */
-
-img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
-
-/*
- * Correct overflow not hidden in IE9
- */
-
-svg:not(:root) { overflow: hidden; }
-
-
-/* =============================================================================
-   Figures
-   ========================================================================== */
-
-figure { margin: 0; }
-
-
-/* =============================================================================
-   Forms
-   ========================================================================== */
-
-form { margin: 0; }
-fieldset { border: 0; margin: 0; padding: 0; }
-
-/* Indicate that 'label' will shift focus to the associated form element */
-label { cursor: pointer; }
-
-/*
- * 1. Correct color not inheriting in IE6/7/8/9
- * 2. Correct alignment displayed oddly in IE6/7
- */
-
-legend { border: 0; *margin-left: -7px; padding: 0; }
-
-/*
- * 1. Correct font-size not inheriting in all browsers
- * 2. Remove margins in FF3/4 S5 Chrome
- * 3. Define consistent vertical alignment display in all browsers
- */
-
-button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; }
-
-/*
- * 1. Define line-height as normal to match FF3/4 (set using !important in the UA stylesheet)
- * 2. Correct inner spacing displayed oddly in IE6/7
- */
-
-button, input { line-height: normal; *overflow: visible; }
-
-/*
- * Reintroduce inner spacing in 'table' to avoid overlap and whitespace issues in IE6/7
- */
-
-table button, table input { *overflow: auto; }
-
-/*
- * 1. Display hand cursor for clickable form elements
- * 2. Allow styling of clickable form elements in iOS
- */
-
-button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; }
-
-/*
- * Consistent box sizing and appearance
- */
-
-input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; }
-input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; }
-input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
-
-/*
- * Remove inner padding and border in FF3/4: h5bp.com/l
- */
-
-button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
-
-/*
- * 1. Remove default vertical scrollbar in IE6/7/8/9
- * 2. Allow only vertical resizing
- */
-
-textarea { overflow: auto; vertical-align: top; resize: vertical; }
-
-/* Colors for form validity */
-input:valid, textarea:valid {  }
-input:invalid, textarea:invalid { background-color: #f0dddd; }
-
-
-/* =============================================================================
-   Tables
-   ========================================================================== */
-
-table { border-collapse: collapse; border-spacing: 0; }
-td { vertical-align: top; }
-
-
-/* ==|== primary styles =====================================================
-   Author:
-   ========================================================================== */
-
-.nostyle_list { list-style: none; list-style-image: none; padding: 0; margin: 0; }
-
-.logo { height: 60px; }
-.logo h1, .logo h2 { padding: 0; margin: 0; display: inline; }
-
-.card_container { overflow: auto; list-style: none; list-style-image: none; padding: 0; margin: 0; }
-.card_container li { float: left; padding: 0 5px; }
-.lh48 { line-height: 48px; }
-.lh32 { line-height: 32px; }
-
-#flash_message_container { position: fixed; top: 0; left: 0; width: 100%; overflow: visible; z-index: 10000;  display:none;  }
-#flash_message_container .flash_message_container_bkg { background-color: #EEFDEA; opacity: .95; -ms-filter: "progid: DXImageTransform.Microsoft.Alpha(Opacity=95)"; filter: alpha(opacity=95); position: static;  }
-#flash_message_container .flash_messages {font-size:150%;}
-.error { color: red; }
-.success { color: #3CC721; }
-
-.pagination_list a, .pagination_list span { background-color: #ECEFF5; padding: 2px 6px; text-decoration: none; }
-.pagination_list a:hover { background-color: #6D84B4; color: white; text-decoration: none; }
-.pagination_list span { background-color: #6D84B4; color: white; }
-
-/* ==|== media queries ======================================================
-   PLACEHOLDER Media Queries for Responsive Design.
-   These override the primary ('mobile first') styles
-   Modify as content requires.
-   ========================================================================== */
-
-@media only screen and (min-width: 480px) {
-  /* Style adjustments for viewports 480px and over go here */
-
-}
-
-@media only screen and (min-width: 768px) {
-  /* Style adjustments for viewports 768px and over go here */
-
-}
-
-
-
-/* ==|== non-semantic helper classes ========================================
-   Please define your styles before this section.
-   ========================================================================== */
-
-/* For image replacement */
-.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; }
-.ir br { display: none; }
-
-/* Hide from both screenreaders and browsers: h5bp.com/u */
-.hidden { display: none !important; visibility: hidden; }
-
-/* Hide only visually, but have it available for screenreaders: h5bp.com/v */
-.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
-
-/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: h5bp.com/p */
-.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
-
-/* Hide visually and from screenreaders, but maintain layout */
-.invisible { visibility: hidden; }
-
-/* Contain floats: h5bp.com/q */
-.clearfix:before, .clearfix:after { content: ""; display: table; }
-.clearfix:after { clear: both; }
-.clearfix { *zoom: 1; }
-
-
-
-/* ==|== print styles =======================================================
-   Print styles.
-   Inlined to avoid required HTTP connection: h5bp.com/r
-   ========================================================================== */
-
-@media print {
-  * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } /* Black prints faster: h5bp.com/s */
-  a, a:visited { text-decoration: underline; }
-  a[href]:after { content: " (" attr(href) ")"; }
-  abbr[title]:after { content: " (" attr(title) ")"; }
-  .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }  /* Don't show links for images, or javascript/internal links */
-  pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
-  thead { display: table-header-group; } /* h5bp.com/t */
-  tr, img { page-break-inside: avoid; }
-  img { max-width: 100% !important; }
-  @page { margin: 0.5cm; }
-  p, h2, h3 { orphans: 3; widows: 3; }
-  h2, h3 { page-break-after: avoid; }
-}
+/*
+ * HTML5 ✰ Boilerplate
+ *
+ * What follows is the result of much research on cross-browser styling.
+ * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
+ * Kroc Camen, and the H5BP dev community and team.
+ *
+ * Detailed information about this CSS: h5bp.com/css
+ *
+ * ==|== normalize ==========================================================
+ */
+
+
+/* =============================================================================
+   HTML5 display definitions
+   ========================================================================== */
+
+article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; }
+audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; }
+audio:not([controls]) { display: none; }
+[hidden] { display: none; }
+
+
+/* =============================================================================
+   Base
+   ========================================================================== */
+
+/*
+ * 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units
+ * 2. Force vertical scrollbar in non-IE
+ * 3. Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g
+ */
+
+html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
+
+body { margin: 0; font-size: 13px; line-height: 1.231; }
+
+body, button, input, select, textarea { font-family: sans-serif; color: #222; }
+
+/*
+ * Remove text-shadow in selection highlight: h5bp.com/i
+ * These selection declarations have to be separate
+ * Also: hot pink! (or customize the background color to match your design)
+ */
+
+::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; }
+::selection { background: #fe57a1; color: #fff; text-shadow: none; }
+
+
+/* =============================================================================
+   Links
+   ========================================================================== */
+
+a { color: #00e; }
+a:visited { color: #551a8b; }
+a:hover { color: #06e; }
+a:focus { outline: thin dotted; }
+
+/* Improve readability when focused and hovered in all browsers: h5bp.com/h */
+a:hover, a:active { outline: 0; }
+
+
+/* =============================================================================
+   Typography
+   ========================================================================== */
+
+abbr[title] { border-bottom: 1px dotted; }
+
+b, strong { font-weight: bold; }
+
+blockquote { margin: 1em 40px; }
+
+dfn { font-style: italic; }
+
+hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
+
+ins { background: #ff9; color: #000; text-decoration: none; }
+
+mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
+
+/* Redeclare monospace font family: h5bp.com/j */
+pre, code, kbd, samp { font-family: monospace, serif; _font-family: 'courier new', monospace; font-size: 1em; }
+
+/* Improve readability of pre-formatted text in all browsers */
+pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; }
+
+q { quotes: none; }
+q:before, q:after { content: ""; content: none; }
+
+small { font-size: 85%; }
+
+/* Position subscript and superscript content without affecting line-height: h5bp.com/k */
+sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
+sup { top: -0.5em; }
+sub { bottom: -0.25em; }
+
+
+/* =============================================================================
+   Lists
+   ========================================================================== */
+
+ul, ol { margin: 1em 0; padding: 0 0 0 40px; }
+dd { margin: 0 0 0 40px; }
+nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; }
+
+
+/* =============================================================================
+   Embedded content
+   ========================================================================== */
+
+/*
+ * 1. Improve image quality when scaled in IE7: h5bp.com/d
+ * 2. Remove the gap between images and borders on image containers: h5bp.com/e
+ */
+
+img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
+
+/*
+ * Correct overflow not hidden in IE9
+ */
+
+svg:not(:root) { overflow: hidden; }
+
+
+/* =============================================================================
+   Figures
+   ========================================================================== */
+
+figure { margin: 0; }
+
+
+/* =============================================================================
+   Forms
+   ========================================================================== */
+
+form { margin: 0; }
+fieldset { border: 0; margin: 0; padding: 0; }
+
+/* Indicate that 'label' will shift focus to the associated form element */
+label { cursor: pointer; }
+
+/*
+ * 1. Correct color not inheriting in IE6/7/8/9
+ * 2. Correct alignment displayed oddly in IE6/7
+ */
+
+legend { border: 0; *margin-left: -7px; padding: 0; }
+
+/*
+ * 1. Correct font-size not inheriting in all browsers
+ * 2. Remove margins in FF3/4 S5 Chrome
+ * 3. Define consistent vertical alignment display in all browsers
+ */
+
+button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; }
+
+/*
+ * 1. Define line-height as normal to match FF3/4 (set using !important in the UA stylesheet)
+ * 2. Correct inner spacing displayed oddly in IE6/7
+ */
+
+button, input { line-height: normal; *overflow: visible; }
+
+/*
+ * Reintroduce inner spacing in 'table' to avoid overlap and whitespace issues in IE6/7
+ */
+
+table button, table input { *overflow: auto; }
+
+/*
+ * 1. Display hand cursor for clickable form elements
+ * 2. Allow styling of clickable form elements in iOS
+ */
+
+button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; }
+
+/*
+ * Consistent box sizing and appearance
+ */
+
+input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; }
+input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; }
+input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
+
+/*
+ * Remove inner padding and border in FF3/4: h5bp.com/l
+ */
+
+button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
+
+/*
+ * 1. Remove default vertical scrollbar in IE6/7/8/9
+ * 2. Allow only vertical resizing
+ */
+
+textarea { overflow: auto; vertical-align: top; resize: vertical; }
+
+/* Colors for form validity */
+input:valid, textarea:valid {  }
+input:invalid, textarea:invalid { background-color: #f0dddd; }
+
+
+/* =============================================================================
+   Tables
+   ========================================================================== */
+
+table { border-collapse: collapse; border-spacing: 0; }
+td { vertical-align: top; }
+
+
+/* ==|== primary styles =====================================================
+   Author:
+   ========================================================================== */
+
+.nostyle_list { list-style: none; list-style-image: none; padding: 0; margin: 0; }
+
+.logo { height: 60px; }
+.logo h1, .logo h2 { padding: 0; margin: 0; display: inline; }
+
+.card_container { overflow: auto; list-style: none; list-style-image: none; padding: 0; margin: 0; }
+.card_container li { float: left; padding: 0 5px; }
+.lh48 { line-height: 48px; }
+.lh32 { line-height: 32px; }
+
+#flash_message_container { position: fixed; top: 0; left: 0; width: 100%; overflow: visible; z-index: 10000;  display:none;  }
+#flash_message_container .flash_message_container_bkg { background-color: #EEFDEA; opacity: .95; -ms-filter: "progid: DXImageTransform.Microsoft.Alpha(Opacity=95)"; filter: alpha(opacity=95); position: static;  }
+#flash_message_container .flash_messages {font-size:150%;}
+.error { color: red; }
+.success { color: #3CC721; }
+
+.pagination_list a, .pagination_list span { background-color: #ECEFF5; padding: 2px 6px; text-decoration: none; }
+.pagination_list a:hover { background-color: #6D84B4; color: white; text-decoration: none; }
+.pagination_list span { background-color: #6D84B4; color: white; }
+
+/* ==|== media queries ======================================================
+   PLACEHOLDER Media Queries for Responsive Design.
+   These override the primary ('mobile first') styles
+   Modify as content requires.
+   ========================================================================== */
+
+@media only screen and (min-width: 480px) {
+  /* Style adjustments for viewports 480px and over go here */
+
+}
+
+@media only screen and (min-width: 768px) {
+  /* Style adjustments for viewports 768px and over go here */
+
+}
+
+
+
+/* ==|== non-semantic helper classes ========================================
+   Please define your styles before this section.
+   ========================================================================== */
+
+/* For image replacement */
+.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; }
+.ir br { display: none; }
+
+/* Hide from both screenreaders and browsers: h5bp.com/u */
+.hidden { display: none !important; visibility: hidden; }
+
+/* Hide only visually, but have it available for screenreaders: h5bp.com/v */
+.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
+
+/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: h5bp.com/p */
+.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
+
+/* Hide visually and from screenreaders, but maintain layout */
+.invisible { visibility: hidden; }
+
+/* Contain floats: h5bp.com/q */
+.clearfix:before, .clearfix:after { content: ""; display: table; }
+.clearfix:after { clear: both; }
+.clearfix { *zoom: 1; }
+
+
+
+/* ==|== print styles =======================================================
+   Print styles.
+   Inlined to avoid required HTTP connection: h5bp.com/r
+   ========================================================================== */
+
+@media print {
+  * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } /* Black prints faster: h5bp.com/s */
+  a, a:visited { text-decoration: underline; }
+  a[href]:after { content: " (" attr(href) ")"; }
+  abbr[title]:after { content: " (" attr(title) ")"; }
+  .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }  /* Don't show links for images, or javascript/internal links */
+  pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
+  thead { display: table-header-group; } /* h5bp.com/t */
+  tr, img { page-break-inside: avoid; }
+  img { max-width: 100% !important; }
+  @page { margin: 0.5cm; }
+  p, h2, h3 { orphans: 3; widows: 3; }
+  h2, h3 { page-break-after: avoid; }
+}

File fbone/static/humans.txt

-/* the humans responsible & colophon */
-/* humanstxt.org */
-
-
-/* TEAM */
-  Programmer: Wilson Xu
-  Site: http://imwilsonxu.net
-  Twitter: @imwilsonxu
-
-/* THANKS */
-  Names (& URL):
-
-/* SITE */
-  Standards: HTML5, CSS3
-  Components: Flask, Modernizr, jQuery
-  Software:
+/* the humans responsible & colophon */
+/* humanstxt.org */
+
+
+/* TEAM */
+  Programmer: Wilson Xu
+  Site: http://imwilsonxu.net
+  Twitter: @imwilsonxu
+
+/* THANKS */
+  Names (& URL):
+
+/* SITE */
+  Standards: HTML5, CSS3
+  Components: Flask, Modernizr, jQuery
+  Software:

File fbone/static/js/plugins.js

-
-// usage: log('inside coolFunc', this, arguments);
-// paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
-window.log = function(){
-  log.history = log.history || [];   // store logs to an array for reference
-  log.history.push(arguments);
-  if(this.console) {
-    arguments.callee = arguments.callee.caller;
-    var newarr = [].slice.call(arguments);
-    (typeof console.log === 'object' ? log.apply.call(console.log, console, newarr) : console.log.apply(console, newarr));
-  }
-};
-
-// make it safe to use console.log always
-(function(b){function c(){}for(var d="assert,clear,count,debug,dir,dirxml,error,exception,firebug,group,groupCollapsed,groupEnd,info,log,memoryProfile,memoryProfileEnd,profile,profileEnd,table,time,timeEnd,timeStamp,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try
-{console.log();return window.console;}catch(err){return window.console={};}})());
-
-
-// place any jQuery/helper plugins in here, instead of separate, slower script files.
-
+
+// usage: log('inside coolFunc', this, arguments);
+// paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
+window.log = function(){
+  log.history = log.history || [];   // store logs to an array for reference
+  log.history.push(arguments);
+  if(this.console) {
+    arguments.callee = arguments.callee.caller;
+    var newarr = [].slice.call(arguments);
+    (typeof console.log === 'object' ? log.apply.call(console.log, console, newarr) : console.log.apply(console, newarr));
+  }
+};
+
+// make it safe to use console.log always
+(function(b){function c(){}for(var d="assert,clear,count,debug,dir,dirxml,error,exception,firebug,group,groupCollapsed,groupEnd,info,log,memoryProfile,memoryProfileEnd,profile,profileEnd,table,time,timeEnd,timeStamp,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try
+{console.log();return window.console;}catch(err){return window.console={};}})());
+
+
+// place any jQuery/helper plugins in here, instead of separate, slower script files.
+

File fbone/static/js/script.js

-/* Author: Wilson Xu */
-
-function hide_flask_message_container() {
-    $('#flash_message_container').slideUp()
-}
-
-$(document).ready(function() {
-    if (! Modernizr.input.autofocus) {
-        $("form input[autofocus]").last().focus()
-    }
-    
-    flash_message_container = $('#flash_message_container')
-    if (flash_message_container) {
-        flash_message_container.slideDown(function() {
-            setTimeout(hide_flask_message_container, 3000)
-        })
-    }
-})
-
-
-
+/* Author: Wilson Xu */
+
+function hide_flask_message_container() {
+    $('#flash_message_container').slideUp()
+}
+
+$(document).ready(function() {
+    if (! Modernizr.input.autofocus) {
+        $("form input[autofocus]").last().focus()
+    }
+    
+    flash_message_container = $('#flash_message_container')
+    if (flash_message_container) {
+        flash_message_container.slideDown(function() {
+            setTimeout(hide_flask_message_container, 3000)
+        })
+    }
+})
+
+
+

File fbone/static/robots.txt

-# www.robotstxt.org/
-# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449
-
-User-agent: *
-
+# www.robotstxt.org/
+# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449
+
+User-agent: *
+

File fbone/templates/change_password.html

-{% from 'macros/_misc.html' import render_errors %}
-
-{% set page_title = _('Change password') %}
-
-{% extends 'layout.html' %}
-
-{% block main_nav %}{% endblock %}
-{% block search %}{% endblock %}
-{% block learn_more %}{% endblock %}
-
-{% block body %}
-<form method='POST' action='{{ url_for('frontend.change_password') }}'>
-    {{ form.hidden_tag() }}
-    <div style='display:none;'>
-        {{ form.activation_key }}
-    </div>
-    <ul>
-        <li>
-            {{ render_errors(form.password) }}
-            {{ form.password.label }}
-            {{ form.password }}
-        </li>
-        <li>
-            {{ render_errors(form.password_again) }}
-            {{ form.password_again.label }}
-            {{ form.password_again }}
-            </li>
-        <li>
-            {{ form.submit }}
-        </li>
-    </ul>
-</form>
-{% endblock %}
+{% from 'macros/_misc.html' import render_errors %}
+
+{% set page_title = _('Change password') %}
+
+{% extends 'layout.html' %}
+
+{% block main_nav %}{% endblock %}
+{% block search %}{% endblock %}
+{% block learn_more %}{% endblock %}
+
+{% block body %}
+<form method='POST' action='{{ url_for('frontend.change_password') }}'>
+    {{ form.hidden_tag() }}
+    <div style='display:none;'>
+        {{ form.activation_key }}
+    </div>
+    <ul>
+        <li>
+            {{ render_errors(form.password) }}
+            {{ form.password.label }}
+            {{ form.password }}
+        </li>
+        <li>
+            {{ render_errors(form.password_again) }}
+            {{ form.password_again.label }}
+            {{ form.password_again }}
+            </li>
+        <li>
+            {{ form.submit }}
+        </li>
+    </ul>
+</form>
+{% endblock %}

File fbone/templates/emails/reset_password.html

-{% trans change_password_url=url_for('frontend.change_password', email=user.email, activation_key=user.activation_key, _external=True) %}
-Forgot your password, imwilsonxu (@imwilsonxu)?
-
-Fbone received a request to reset the password for your Fbone account (@imwilsonxu).
-
-If you want to reset your password, click on the link below (or copy and paste the URL into your browser):
-{{ change_password_url }}
-
-This link takes you to a secure page where you can change your password. For tips on selecting a strong password, see our Safe Tweeting help page.
-
-If you don't want to reset your password, please ignore this message. Your password will not be reset. If you have any concerns, please contact us at Fbone Support.
-
-The Fbone Team
-{% endtrans %}
+{% trans change_password_url=url_for('frontend.change_password', email=user.email, activation_key=user.activation_key, _external=True) %}
+Forgot your password, imwilsonxu (@imwilsonxu)?
+
+Fbone received a request to reset the password for your Fbone account (@imwilsonxu).
+
+If you want to reset your password, click on the link below (or copy and paste the URL into your browser):
+{{ change_password_url }}
+
+This link takes you to a secure page where you can change your password. For tips on selecting a strong password, see our Safe Tweeting help page.
+
+If you don't want to reset your password, please ignore this message. Your password will not be reset. If you have any concerns, please contact us at Fbone Support.
+
+The Fbone Team
+{% endtrans %}