Commits

Mark Sundstrom  committed cee949a

reorganized according to how I'm doing projects these days.. still a work in progress

  • Participants
  • Parent commits 23bcfff
  • Branches default

Comments (0)

Files changed (56)

File addpics.py

-#!/usr/bin/env python
-"""
-    Add new pictures to the database. I've modeled this after
-    Eric Floehr's daystrips project, i.e., the addpics management command.
-    I don't know yet if I want all the same data gathered from the image,
-    but this is a good place to start.
-
-    Now that I've switched to the D-Link camera, the DCS-930L, I don't
-    expect to go back to the images generated by the iSight, as the D-Link
-    is so much better.
-"""
-from __future__ import print_function
-import sys
-import os
-os.environ['DJANGO_SETTINGS_MODULE'] = 'web.settings'
-import datetime
-import time
-from collections import defaultdict, namedtuple
-from pics.models import Picture, Camera, Location
-from PIL import Image, ImageStat
-from django.utils.timezone import utc, get_default_timezone, make_aware
-from django.conf import settings
-from util import rgb_to_int
-
-#Picture.objects.all().delete(); sys.exit()
-
-# Choose defaults from the environment.
-# I set these defaults in the postactivate script of the virtualenv
-camera = Camera.objects.get(pk=os.environ['TIMELAPSE_CAMERA_ID'])
-location = Location.objects.get(pk=os.environ['TIMELAPSE_LOCATION_ID'])
-image_src = os.environ['TIMELAPSE_SOURCE']
-camera_center = (camera.width / 2, camera.height / 2)
-Item = namedtuple('Item', 'dirpath filename date')
-
-
-def generate_all_image_files():
-    """
-        filenames from the D-Link camera look like:
-            dlinkYYYYMMDDHHMMSSnn.jpg
-        with the time being in UTC.
-    """
-    FORMAT = 'dlink%Y%m%d%H%M%S'
-    for dirpath, dirnames, filenames in os.walk(image_src):
-        for name in filenames:
-            if name.startswith('dlink'):
-                date = make_aware(
-                    datetime.datetime.strptime(name[:19], FORMAT), utc)
-                yield Item(dirpath, name, date)
-
-
-print(Picture.objects.count(), 'pictures in the database')
-check_option = True
-
-# for item in sorted(generate_all_image_files(), key=lambda x:x.date, reverse=True):
-#     print(item.date.astimezone(get_default_timezone()), item.filename)
-#     break
-# sys.exit()
-
-for item in generate_all_image_files():
-    if check_option:
-        pic, created = Picture.objects.get_or_create(timestamp=item.date,
-                            camera=camera, location=location)
-    else:
-        pic = Picture(timestamp=item.date,
-                    camera=camera, location=location)
-    if not created:
-        continue  # we already have it, so skip it.
-    pic.filepath = os.path.join(item.dirpath, item.filename)
-    pic.filename = item.filename
-    filestat = os.stat(pic.filepath)
-    pic.file_timestamp = datetime.datetime.fromtimestamp(
-        int(filestat.st_mtime), utc)
-    pic.size = filestat.st_size
-
-    try:
-        im = Image.open(pic.filepath)
-        stats = ImageStat.Stat(im)
-        pic.center_color = rgb_to_int(im.getpixel(camera_center))
-        pic.mean_color = rgb_to_int(stats.mean)
-        pic.median_color = rgb_to_int(stats.median)
-        pic.stddev_red, pic.stddev_green, pic.stddev_blue = [
-            int(round(i))
-            for i in stats.stddev[0:3]
-            ]
-
-        exc = stats.extrema
-
-        pic.min_color = rgb_to_int((exc[0][0], exc[1][0], exc[2][0]))
-        pic.max_color = rgb_to_int((exc[0][1], exc[1][1], exc[2][1]))
-
-        pic.valid = True
-
-        if im.size != (640, 480):
-            pic.valid = False
-
-    except:
-        pic.valid = False
-    pic.save()
-    print(item.date.astimezone(get_default_timezone()), item.filename)
-    #break

File bin/backup_from_avocet.sh

-#!/bin/sh
-#set -o verbose
-# Use rsync to get time lapse images from the laptop with the images
-rsync -av mark@avocet.local:Desktop/timelapse/ /Volumes/Extra/timelapse/primary/
-
-#/usr/local/bin/growlnotify -m "time_lapse_sync completed"
-

File bin/install.sh

-#!/bin/bash
-cp ./bin/postactivate $VIRTUAL_ENV/bin

File bin/looper.py

-#!/usr/bin/env python
-"""
-    What's running on the laptop with the iSight camera to capture
-    images once a minute.
-"""
-from __future__ import print_function
-import os
-import subprocess
-import datetime
-import time
-
-base = '/Users/mark/Desktop/timelapse/'
-
-print("Time lapse photo program starting at", datetime.datetime.today())
-while True:
-    utc = int(time.time())
-    filename = 'img_{:d}.jpg'.format(utc)
-    dirpath = '{}{:d}/'.format(base, utc // 1000000)
-    if not os.path.exists(dirpath):
-        os.makedirs(dirpath)
-    path = dirpath + filename
-    subprocess.call(['/Users/mark/Desktop/isightcapture', path])
-    print(time.ctime(utc), path)
-    time.sleep(60 - (utc % 60))
-

File bin/postactivate

-#!/bin/bash
-# This hook is run after this virtualenv is activated.
-
-# Using this to specify some things that are likely true
-# only for my setup, to avoid putting them into the code.
-
-export TIMELAPSE_SOURCE=/Volumes/Extra/timelapse/primary/
-export TIMELAPSE_DAILY=/Volumes/Extra/Movies/daily
-export TIMELAPSE_CAMERA_ID=2  # the DCS-930L
-export TIMELAPSE_LOCATION_ID=1  # the office

File daily.py

-#!/usr/bin/env python
-# -*- coding: utf8
-"""
-    Create movies with all the images from one day.
-    This is so we can easily find things that might be interesting.
-    For now we limit to images where the Sun is 7° belowe the horizon
-    or higher.
-
-    We use TIMELAPSE_DAILY to find where to put these movies
-"""
-from __future__ import print_function
-import sys
-import os
-os.environ['DJANGO_SETTINGS_MODULE'] = 'web.settings'
-import datetime
-from pics.models import Picture, Camera, Location
-from django.utils.timezone import utc, get_default_timezone, make_aware
-from movies import ImageSequence
-import sky
-
-# Choose defaults from the environment.
-# I set these defaults in the postactivate script of the virtualenv
-camera = Camera.objects.get(pk=os.environ['TIMELAPSE_CAMERA_ID'])
-location = Location.objects.get(pk=os.environ['TIMELAPSE_LOCATION_ID'])
-tzinfo = get_default_timezone()
-observer = sky.observer_from_location(location)
-overwrite = False  # always write the output files?
-include_night = False
-
-
-def generate_dates():
-    "Generate all dates for our default camera and location"
-    for date in Picture.objects.dates().filter(
-           camera=camera, location=location):
-        yield date.date()
-
-
-for date in generate_dates():
-    print("Checking", date)
-    outpath = os.path.join(os.environ['TIMELAPSE_DAILY'],
-        '{:%Y-%m-%d}.mp4'.format(date))
-    if os.path.exists(outpath) and (not overwrite):
-        continue
-    dirpath = os.path.dirname(outpath)
-    if not os.path.exists(dirpath):
-        os.makedirs(dirpath)
-    if include_night:
-        start = make_aware(datetime.datetime.combine(date,
-            datetime.time(0, 0, 0)), tzinfo)
-        end = start + datetime.timedelta(days=1)
-    else:
-        suntimes = sky.suntimes_for_date(observer, date, '-8')
-        # seconds are floating point, so just ignore them
-        start = make_aware(datetime.datetime(*suntimes.dawn.tuple()[:5]), utc)
-        end = make_aware(datetime.datetime(*suntimes.dusk.tuple()[:5]), utc)
-    qs = Picture.objects.filter(camera__id=camera.id,
-            location__id=location.id,
-            timestamp__gte=start, timestamp__lt=end).order_by('timestamp')
-    if qs.count() > 0:
-        print(camera, location, date, qs.count())
-        movie = ImageSequence()
-        [movie.add_picture(obj) for obj in qs]
-        movie.make_timelapse(outpath)

File manage.py

-#!/usr/bin/env python
-import os
-import sys
-
-if __name__ == "__main__":
-    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "web.settings")
-
-    from django.core.management import execute_from_command_line
-
-    execute_from_command_line(sys.argv)

File movies.py

-#!/usr/bin/env python
-# -*- coding: utf8
-"""
-    use ffmpeg for creating timelapse movies from a sequence of images
-"""
-from __future__ import print_function
-import os
-import tempfile
-import shutil
-import subprocess
-
-
-TEMPDIR_BASE = '/var/tmp/timelapse'  # keep all the temp stuff in one spot
-if not os.path.exists(TEMPDIR_BASE):
-    os.mkdir(TEMPDIR_BASE)
-
-
-class ImageSequence(object):
-    """
-        Collect a sequence of images (assumed to be added in the
-        correct sequence) so that a timelapse movie can be created
-        from them.
-
-        TODO – we may eventually allow for adding images that are
-        not from the Picture table
-    """
-    def __init__(self):
-        self.images = []
-
-    def add_picture(self, picture):
-        self.images.append(picture)
-
-    def make_timelapse(self, name, frames_per_second=10):
-        # get a temporary place to write all the images
-        tempdir = tempfile.mkdtemp(dir=TEMPDIR_BASE)
-        for sequence, image in enumerate(self.images, start=1):
-            dst = '%s/%04d.jpg' % (tempdir, sequence)
-            shutil.copyfile(image.filepath, dst)
-
-        # I am clueless about all the ffmpeg options,
-        # but the following is working okay so far.
-        imagedir = '{}/%*.jpg'.format(tempdir)
-        args = ['/usr/local/bin/ffmpeg',
-            '-r', '12',
-            '-i', imagedir,
-            '-an',
-            '-vcodec', 'libx264',
-            '-b:v', '1200k',
-            '-s', '640x480',
-            name]
-        print(' '.join(args))
-        subprocess.call(args)
-
-if __name__ == '__main__':
-    # Clean up the temp directory
-    shutil.rmtree(TEMPDIR_BASE)

File pics/management/__init__.py

Empty file added.

File pics/management/commands/__init__.py

Empty file added.

File pics/management/commands/addpics.py

+"""
+    Add new pictures to the database. I've modeled this after
+    Eric Floehr's daystrips project, i.e., the addpics management command.
+    I don't know yet if I want all the same data gathered from the image,
+    but this is a good place to start.
+
+    Now that I've switched to the D-Link camera, the DCS-930L, I don't
+    expect to go back to the images generated by the iSight, as the D-Link
+    is so much better.
+"""
+import sys
+import os
+from django.core.management.base import NoArgsCommand, CommandError
+import datetime
+from collections import defaultdict, namedtuple
+from pics.models import Picture, Camera, Location
+from PIL import Image, ImageStat
+from django.utils.timezone import utc, get_default_timezone, make_aware
+from .util import rgb_to_int
+import logging
+
+logger = logging.getLogger(__name__)
+
+# Set up defaults
+camera = Camera.objects.get(pk=os.environ['TIMELAPSE_CAMERA_ID'])
+location = Location.objects.get(pk=os.environ['TIMELAPSE_LOCATION_ID'])
+image_src = os.environ['TIMELAPSE_SOURCE']
+camera_center = (camera.width / 2, camera.height / 2)
+Item = namedtuple('Item', 'dirpath filename date')
+check_option = True
+
+
+def generate_all_image_files():
+    """
+        filenames from the D-Link camera look like:
+            dlinkYYYYMMDDHHMMSSnn.jpg
+        with the time being in UTC.
+    """
+    FORMAT = 'dlink%Y%m%d%H%M%S'
+    for dirpath, dirnames, filenames in os.walk(image_src):
+        for name in filenames:
+            if name.startswith('dlink'):
+                date = make_aware(
+                    datetime.datetime.strptime(name[:19], FORMAT), utc)
+                yield Item(dirpath, name, date)
+
+class Command(NoArgsCommand):
+    def handle_noargs(self, **options):
+        logger.debug("addpics start: {} pictures".format(Picture.objects.count()))
+        for item in generate_all_image_files():
+            if check_option:
+                pic, created = Picture.objects.get_or_create(timestamp=item.date,
+                                    camera=camera, location=location)
+            else:
+                pic = Picture(timestamp=item.date,
+                            camera=camera, location=location)
+            if not created:
+                continue  # we already have it, so skip it.
+            pic.filepath = os.path.join(item.dirpath, item.filename)
+            pic.filename = item.filename
+            filestat = os.stat(pic.filepath)
+            pic.file_timestamp = datetime.datetime.fromtimestamp(
+                int(filestat.st_mtime), utc)
+            pic.size = filestat.st_size
+
+            try:
+                im = Image.open(pic.filepath)
+                stats = ImageStat.Stat(im)
+                pic.center_color = rgb_to_int(im.getpixel(camera_center))
+                pic.mean_color = rgb_to_int(stats.mean)
+                pic.median_color = rgb_to_int(stats.median)
+                pic.stddev_red, pic.stddev_green, pic.stddev_blue = [
+                    int(round(i))
+                    for i in stats.stddev[0:3]
+                    ]
+
+                exc = stats.extrema
+
+                pic.min_color = rgb_to_int((exc[0][0], exc[1][0], exc[2][0]))
+                pic.max_color = rgb_to_int((exc[0][1], exc[1][1], exc[2][1]))
+
+                pic.valid = True
+
+                if im.size != (640, 480):
+                    pic.valid = False
+
+            except:
+                pic.valid = False
+            pic.save()
+            logger.info('{} {}'.format(
+                item.date.astimezone(get_default_timezone()), item.filename))
+        logger.debug("addpics start: {} pictures".format(Picture.objects.count()))
+

File pics/util.py

+#!/usr/bin/env python
+# -*- coding: utf8
+"""
+    We store color values as integers in the database.
+    These functions do the conversions back and forth.
+"""
+from __future__ import print_function
+from collections import namedtuple
+
+
+def rgb_to_int(rgb):
+    "(red, green, blue) tuple --> integer"
+    return int(round(rgb[0] * 256 * 256 + rgb[1] * 256 + rgb[2]))
+
+
+def int_to_rgb(i):
+    "integer --> (red, green, blue)"
+    r = (i >> 16) & 255
+    g = (i >> 8) & 255
+    b = i & 255
+    return (r, g, b)

File requirements.txt

--e file:///Users/mark/hg/timelapse
+# pip install -r requirements.txt
+
 pyephem==3.7.5.1
-Django==1.4.1
-pytz==2012d
-# for admin-docs
-docutils==0.8.1
+Django==1.5a1
+pytz
+# the following useful for debugging
+logging_tree==1.1
+bpython==0.11
+Pygments==1.4
 
 # PIL is installed globally via Homebrew
 # ffmpeg is installed via Homebrew for help in making the movies
-# I'm using Python 2.7
+# I'm using Python 2.7.3

File sandbox.py

-#!/usr/bin/env python
-from __future__ import print_function
-import os
-import datetime
-import time
-from collections import defaultdict
-
-image_src = os.path.join(os.path.expanduser('~'), 'Backups', 'timelapse')
-
-def generate_all_image_files():
-    for dirpath, dirnames, filenames in os.walk(image_src):
-        for name in filenames:
-            if name.startswith('img_'):
-                yield (dirpath, name)
-
-"""
-Is it better to use YMDHMS or the timestamp for the image filenames?
-Don't know, don't have to decide yet.
-"""
-def image_filename_to_date(filename):
-    return datetime.datetime.strptime(name, 'img_%Y%m%d%H%M%S.jpg')
-
-def date_to_timestamp(when):
-    return time.mktime(when.timetuple())
-
-
-"""
-Count the # of images I have for each day
-"""
-days = defaultdict(int)
-for dirpath, name in generate_all_image_files():
-    when = image_filename_to_date(name)
-    days[when.date()] += 1
-for date in sorted(days.keys(), reverse=True):
-    print(date, days[date])
-

File scripts/backup_from_avocet.sh

+#!/bin/sh
+# Use rsync to get time lapse images from the laptop with the images
+rsync -av mark@avocet.local:Desktop/timelapse/ $TIMELAPSE_SOURCE

File scripts/daily.py

+#!/usr/bin/env python
+# -*- coding: utf8
+"""
+    Create movies with all the images from one day.
+    This is so we can easily find things that might be interesting.
+    For now we limit to images where the Sun is 7° belowe the horizon
+    or higher.
+
+    We use TIMELAPSE_DAILY to find where to put these movies
+"""
+from __future__ import print_function
+import sys
+import os
+import datetime
+from pics.models import Picture, Camera, Location
+from django.utils.timezone import utc, get_default_timezone, make_aware
+from movies import ImageSequence
+import sky
+
+# Choose defaults from the environment.
+# I set these defaults in the postactivate script of the virtualenv
+camera = Camera.objects.get(pk=os.environ['TIMELAPSE_CAMERA_ID'])
+location = Location.objects.get(pk=os.environ['TIMELAPSE_LOCATION_ID'])
+tzinfo = get_default_timezone()
+observer = sky.observer_from_location(location)
+overwrite = False  # always write the output files?
+include_night = False
+
+
+def generate_dates():
+    "Generate all dates for our default camera and location"
+    for date in Picture.objects.dates().filter(
+           camera=camera, location=location):
+        yield date.date()
+
+
+for date in generate_dates():
+    print("Checking", date)
+    outpath = os.path.join(os.environ['TIMELAPSE_DAILY'],
+        '{:%Y-%m-%d}.mp4'.format(date))
+    if os.path.exists(outpath) and (not overwrite):
+        continue
+    dirpath = os.path.dirname(outpath)
+    if not os.path.exists(dirpath):
+        os.makedirs(dirpath)
+    if include_night:
+        start = make_aware(datetime.datetime.combine(date,
+            datetime.time(0, 0, 0)), tzinfo)
+        end = start + datetime.timedelta(days=1)
+    else:
+        suntimes = sky.suntimes_for_date(observer, date, '-8')
+        # seconds are floating point, so just ignore them
+        start = make_aware(datetime.datetime(*suntimes.dawn.tuple()[:5]), utc)
+        end = make_aware(datetime.datetime(*suntimes.dusk.tuple()[:5]), utc)
+    qs = Picture.objects.filter(camera__id=camera.id,
+            location__id=location.id,
+            timestamp__gte=start, timestamp__lt=end).order_by('timestamp')
+    if qs.count() > 0:
+        print(camera, location, date, qs.count())
+        movie = ImageSequence()
+        [movie.add_picture(obj) for obj in qs]
+        movie.make_timelapse(outpath)

File scripts/movies.py

+#!/usr/bin/env python
+# -*- coding: utf8
+"""
+    use ffmpeg for creating timelapse movies from a sequence of images
+"""
+from __future__ import print_function
+import os
+import tempfile
+import shutil
+import subprocess
+
+
+TEMPDIR_BASE = '/var/tmp/timelapse'  # keep all the temp stuff in one spot
+if not os.path.exists(TEMPDIR_BASE):
+    os.mkdir(TEMPDIR_BASE)
+
+
+class ImageSequence(object):
+    """
+        Collect a sequence of images (assumed to be added in the
+        correct sequence) so that a timelapse movie can be created
+        from them.
+
+        TODO – we may eventually allow for adding images that are
+        not from the Picture table
+    """
+    def __init__(self):
+        self.images = []
+
+    def add_picture(self, picture):
+        self.images.append(picture)
+
+    def make_timelapse(self, name, frames_per_second=10):
+        # get a temporary place to write all the images
+        tempdir = tempfile.mkdtemp(dir=TEMPDIR_BASE)
+        for sequence, image in enumerate(self.images, start=1):
+            dst = '%s/%04d.jpg' % (tempdir, sequence)
+            shutil.copyfile(image.filepath, dst)
+
+        # I am clueless about all the ffmpeg options,
+        # but the following is working okay so far.
+        imagedir = '{}/%*.jpg'.format(tempdir)
+        args = ['/usr/local/bin/ffmpeg',
+            '-r', '12',
+            '-i', imagedir,
+            '-an',
+            '-vcodec', 'libx264',
+            '-b:v', '1200k',
+            '-s', '640x480',
+            name]
+        print(' '.join(args))
+        subprocess.call(args)
+
+if __name__ == '__main__':
+    # Clean up the temp directory
+    shutil.rmtree(TEMPDIR_BASE)

File scripts/sandbox.py

+#!/usr/bin/env python
+from __future__ import print_function
+import os
+import datetime
+import time
+from collections import defaultdict
+
+image_src = os.path.join(os.path.expanduser('~'), 'Backups', 'timelapse')
+
+def generate_all_image_files():
+    for dirpath, dirnames, filenames in os.walk(image_src):
+        for name in filenames:
+            if name.startswith('img_'):
+                yield (dirpath, name)
+
+"""
+Is it better to use YMDHMS or the timestamp for the image filenames?
+Don't know, don't have to decide yet.
+"""
+def image_filename_to_date(filename):
+    return datetime.datetime.strptime(name, 'img_%Y%m%d%H%M%S.jpg')
+
+def date_to_timestamp(when):
+    return time.mktime(when.timetuple())
+
+
+"""
+Count the # of images I have for each day
+"""
+days = defaultdict(int)
+for dirpath, name in generate_all_image_files():
+    when = image_filename_to_date(name)
+    days[when.date()] += 1
+for date in sorted(days.keys(), reverse=True):
+    print(date, days[date])
+

File scripts/sky.py

+#!/usr/bin/env python
+# -*- coding: utf8
+"""
+    Calculate the approximate times of full darkness for the
+    next day, so we can set the schedule for the webcam. For the
+    current location, pictures taken at night are not that
+    interesting and don't vary from one night to the next, so
+    I'll turn the camera off when it gets dark and turn it on
+    in the morning when it starts getting light.
+
+    Civil twilight is defined as the Sun being 6° below the
+    horizon. From examining images, it looks like choosing
+    7° is a good choice for me.
+"""
+from __future__ import print_function
+import sys
+import os
+os.environ['DJANGO_SETTINGS_MODULE'] = 'web.settings'
+import datetime
+from collections import namedtuple
+from pics.models import Location
+import ephem
+
+HORIZON = '-8'
+
+sun = ephem.Sun()
+
+
+def observer_from_location(location):
+    "Create an ephem.Observer object from a location"
+    observer = ephem.Observer()
+    observer.lat = str(location.latitude)
+    observer.lon = str(location.longitude)
+    observer.elevation = location.elevation
+    observer.date = ephem.now()  # useful default?
+    return observer
+
+SunTimes = namedtuple('SunTimes', 'dawn sunrise sunset dusk')
+
+
+def suntimes_for_date(observer, date, horizon=HORIZON):
+    """
+    Calculate rise and set times for a given date and observer,
+    along with the times of twilight using our default horizon.
+    Civil twilight is usually calculated for 6°.
+    """
+    observer.date = ephem.Date((date.year, date.month, date.day, 12, 0, 0))
+    sunrise = observer.previous_rising(sun)
+    sunset = observer.next_setting(sun)
+    observer.horizon = horizon
+    dawn = observer.previous_rising(sun, use_center=True)
+    dusk = observer.next_setting(sun, use_center=True)
+    return SunTimes(dawn, sunrise, sunset, dusk)
+
+if __name__ == '__main__':
+    print("Sun times for today, horizon of {}°".format(HORIZON))
+    location = Location.objects.get(pk=os.environ['TIMELAPSE_LOCATION_ID'])
+    observer = observer_from_location(location)
+    times = suntimes_for_date(observer, datetime.date.today())
+    for field in times._fields:
+        value = getattr(times, field)
+        print('{:7} {} UTC or {:%H:%M:%S} local'.format(
+            field, value, ephem.localtime(value)))

File scripts/update.sh

+#!/bin/bash
+cd $WORKON_HOME/timelapse
+source bin/activate
+cd $HOME/hg/timelapse
+bin/backup_from_avocet.sh
+django-admin.py addpics
+/usr/local/bin/growlnotify -m "timelapse update complete"

File setup.py

-from distutils.core import setup
-
-setup(
-    name = 'timelapse',
-    version = '0.1',
-    description = 'My time lapse photography project',
-    author = 'Mark Sundstrom',
-    author_email = 'mark@mhsundstrom.com',
-    url = 'http://localhost:8000/',
-    packages = [
-        'web',
-        'pics',
-        ],
-    package_data = {
-        'web': [
-            'templates/*.html',
-            'static/css/*.css',
-            'static/img/*',
-            'static/js/*.js',
-        ],
-        },
-    scripts = [
-        'manage.py',
-    ],
-)

File sky.py

-#!/usr/bin/env python
-# -*- coding: utf8
-"""
-    Calculate the approximate times of full darkness for the
-    next day, so we can set the schedule for the webcam. For the
-    current location, pictures taken at night are not that
-    interesting and don't vary from one night to the next, so
-    I'll turn the camera off when it gets dark and turn it on
-    in the morning when it starts getting light.
-
-    Civil twilight is defined as the Sun being 6° below the
-    horizon. From examining images, it looks like choosing
-    7° is a good choice for me.
-"""
-from __future__ import print_function
-import sys
-import os
-os.environ['DJANGO_SETTINGS_MODULE'] = 'web.settings'
-import datetime
-from collections import namedtuple
-from pics.models import Location
-import ephem
-
-HORIZON = '-8'
-
-sun = ephem.Sun()
-
-
-def observer_from_location(location):
-    "Create an ephem.Observer object from a location"
-    observer = ephem.Observer()
-    observer.lat = str(location.latitude)
-    observer.lon = str(location.longitude)
-    observer.elevation = location.elevation
-    observer.date = ephem.now()  # useful default?
-    return observer
-
-SunTimes = namedtuple('SunTimes', 'dawn sunrise sunset dusk')
-
-
-def suntimes_for_date(observer, date, horizon=HORIZON):
-    """
-    Calculate rise and set times for a given date and observer,
-    along with the times of twilight using our default horizon.
-    Civil twilight is usually calculated for 6°.
-    """
-    observer.date = ephem.Date((date.year, date.month, date.day, 12, 0, 0))
-    sunrise = observer.previous_rising(sun)
-    sunset = observer.next_setting(sun)
-    observer.horizon = horizon
-    dawn = observer.previous_rising(sun, use_center=True)
-    dusk = observer.next_setting(sun, use_center=True)
-    return SunTimes(dawn, sunrise, sunset, dusk)
-
-if __name__ == '__main__':
-    print("Sun times for today, horizon of {}°".format(HORIZON))
-    location = Location.objects.get(pk=os.environ['TIMELAPSE_LOCATION_ID'])
-    observer = observer_from_location(location)
-    times = suntimes_for_date(observer, datetime.date.today())
-    for field in times._fields:
-        value = getattr(times, field)
-        print('{:7} {} UTC or {:%H:%M:%S} local'.format(
-            field, value, ephem.localtime(value)))

File static/css/bootstrap-responsive.css

+/*!
+ * Bootstrap Responsive v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+.clearfix {
+  *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.hide-text {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.input-block-level {
+  display: block;
+  width: 100%;
+  min-height: 30px;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+.hidden {
+  display: none;
+  visibility: hidden;
+}
+
+.visible-phone {
+  display: none !important;
+}
+
+.visible-tablet {
+  display: none !important;
+}
+
+.hidden-desktop {
+  display: none !important;
+}
+
+.visible-desktop {
+  display: inherit !important;
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+  .hidden-desktop {
+    display: inherit !important;
+  }
+  .visible-desktop {
+    display: none !important ;
+  }
+  .visible-tablet {
+    display: inherit !important;
+  }
+  .hidden-tablet {
+    display: none !important;
+  }
+}
+
+@media (max-width: 767px) {
+  .hidden-desktop {
+    display: inherit !important;
+  }
+  .visible-desktop {
+    display: none !important;
+  }
+  .visible-phone {
+    display: inherit !important;
+  }
+  .hidden-phone {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .row {
+    margin-left: -30px;
+    *zoom: 1;
+  }
+  .row:before,
+  .row:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row:after {
+    clear: both;
+  }
+  [class*="span"] {
+    float: left;
+    min-height: 1px;
+    margin-left: 30px;
+  }
+  .container,
+  .navbar-static-top .container,
+  .navbar-fixed-top .container,
+  .navbar-fixed-bottom .container {
+    width: 1170px;
+  }
+  .span12 {
+    width: 1170px;
+  }
+  .span11 {
+    width: 1070px;
+  }
+  .span10 {
+    width: 970px;
+  }
+  .span9 {
+    width: 870px;
+  }
+  .span8 {
+    width: 770px;
+  }
+  .span7 {
+    width: 670px;
+  }
+  .span6 {
+    width: 570px;
+  }
+  .span5 {
+    width: 470px;
+  }
+  .span4 {
+    width: 370px;
+  }
+  .span3 {
+    width: 270px;
+  }
+  .span2 {
+    width: 170px;
+  }
+  .span1 {
+    width: 70px;
+  }
+  .offset12 {
+    margin-left: 1230px;
+  }
+  .offset11 {
+    margin-left: 1130px;
+  }
+  .offset10 {
+    margin-left: 1030px;
+  }
+  .offset9 {
+    margin-left: 930px;
+  }
+  .offset8 {
+    margin-left: 830px;
+  }
+  .offset7 {
+    margin-left: 730px;
+  }
+  .offset6 {
+    margin-left: 630px;
+  }
+  .offset5 {
+    margin-left: 530px;
+  }
+  .offset4 {
+    margin-left: 430px;
+  }
+  .offset3 {
+    margin-left: 330px;
+  }
+  .offset2 {
+    margin-left: 230px;
+  }
+  .offset1 {
+    margin-left: 130px;
+  }
+  .row-fluid {
+    width: 100%;
+    *zoom: 1;
+  }
+  .row-fluid:before,
+  .row-fluid:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row-fluid:after {
+    clear: both;
+  }
+  .row-fluid [class*="span"] {
+    display: block;
+    float: left;
+    width: 100%;
+    min-height: 30px;
+    margin-left: 2.564102564102564%;
+    *margin-left: 2.5109110747408616%;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .row-fluid [class*="span"]:first-child {
+    margin-left: 0;
+  }
+  .row-fluid .span12 {
+    width: 100%;
+    *width: 99.94680851063829%;
+  }
+  .row-fluid .span11 {
+    width: 91.45299145299145%;
+    *width: 91.39979996362975%;
+  }
+  .row-fluid .span10 {
+    width: 82.90598290598291%;
+    *width: 82.8527914166212%;
+  }
+  .row-fluid .span9 {
+    width: 74.35897435897436%;
+    *width: 74.30578286961266%;
+  }
+  .row-fluid .span8 {
+    width: 65.81196581196582%;
+    *width: 65.75877432260411%;
+  }
+  .row-fluid .span7 {
+    width: 57.26495726495726%;
+    *width: 57.21176577559556%;
+  }
+  .row-fluid .span6 {
+    width: 48.717948717948715%;
+    *width: 48.664757228587014%;
+  }
+  .row-fluid .span5 {
+    width: 40.17094017094017%;
+    *width: 40.11774868157847%;
+  }
+  .row-fluid .span4 {
+    width: 31.623931623931625%;
+    *width: 31.570740134569924%;
+  }
+  .row-fluid .span3 {
+    width: 23.076923076923077%;
+    *width: 23.023731587561375%;
+  }
+  .row-fluid .span2 {
+    width: 14.52991452991453%;
+    *width: 14.476723040552828%;
+  }
+  .row-fluid .span1 {
+    width: 5.982905982905983%;
+    *width: 5.929714493544281%;
+  }
+  .row-fluid .offset12 {
+    margin-left: 105.12820512820512%;
+    *margin-left: 105.02182214948171%;
+  }
+  .row-fluid .offset12:first-child {
+    margin-left: 102.56410256410257%;
+    *margin-left: 102.45771958537915%;
+  }
+  .row-fluid .offset11 {
+    margin-left: 96.58119658119658%;
+    *margin-left: 96.47481360247316%;
+  }
+  .row-fluid .offset11:first-child {
+    margin-left: 94.01709401709402%;
+    *margin-left: 93.91071103837061%;
+  }
+  .row-fluid .offset10 {
+    margin-left: 88.03418803418803%;
+    *margin-left: 87.92780505546462%;
+  }
+  .row-fluid .offset10:first-child {
+    margin-left: 85.47008547008548%;
+    *margin-left: 85.36370249136206%;
+  }
+  .row-fluid .offset9 {
+    margin-left: 79.48717948717949%;
+    *margin-left: 79.38079650845607%;
+  }
+  .row-fluid .offset9:first-child {
+    margin-left: 76.92307692307693%;
+    *margin-left: 76.81669394435352%;
+  }
+  .row-fluid .offset8 {
+    margin-left: 70.94017094017094%;
+    *margin-left: 70.83378796144753%;
+  }
+  .row-fluid .offset8:first-child {
+    margin-left: 68.37606837606839%;
+    *margin-left: 68.26968539734497%;
+  }
+  .row-fluid .offset7 {
+    margin-left: 62.393162393162385%;
+    *margin-left: 62.28677941443899%;
+  }
+  .row-fluid .offset7:first-child {
+    margin-left: 59.82905982905982%;
+    *margin-left: 59.72267685033642%;
+  }
+  .row-fluid .offset6 {
+    margin-left: 53.84615384615384%;
+    *margin-left: 53.739770867430444%;
+  }
+  .row-fluid .offset6:first-child {
+    margin-left: 51.28205128205128%;
+    *margin-left: 51.175668303327875%;
+  }
+  .row-fluid .offset5 {
+    margin-left: 45.299145299145295%;
+    *margin-left: 45.1927623204219%;
+  }
+  .row-fluid .offset5:first-child {
+    margin-left: 42.73504273504273%;
+    *margin-left: 42.62865975631933%;
+  }
+  .row-fluid .offset4 {
+    margin-left: 36.75213675213675%;
+    *margin-left: 36.645753773413354%;
+  }
+  .row-fluid .offset4:first-child {
+    margin-left: 34.18803418803419%;
+    *margin-left: 34.081651209310785%;
+  }
+  .row-fluid .offset3 {
+    margin-left: 28.205128205128204%;
+    *margin-left: 28.0987452264048%;
+  }
+  .row-fluid .offset3:first-child {
+    margin-left: 25.641025641025642%;
+    *margin-left: 25.53464266230224%;
+  }
+  .row-fluid .offset2 {
+    margin-left: 19.65811965811966%;
+    *margin-left: 19.551736679396257%;
+  }
+  .row-fluid .offset2:first-child {
+    margin-left: 17.094017094017094%;
+    *margin-left: 16.98763411529369%;
+  }
+  .row-fluid .offset1 {
+    margin-left: 11.11111111111111%;
+    *margin-left: 11.004728132387708%;
+  }
+  .row-fluid .offset1:first-child {
+    margin-left: 8.547008547008547%;
+    *margin-left: 8.440625568285142%;
+  }
+  input,
+  textarea,
+  .uneditable-input {
+    margin-left: 0;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 30px;
+  }
+  input.span12,
+  textarea.span12,
+  .uneditable-input.span12 {
+    width: 1156px;
+  }
+  input.span11,
+  textarea.span11,
+  .uneditable-input.span11 {
+    width: 1056px;
+  }
+  input.span10,
+  textarea.span10,
+  .uneditable-input.span10 {
+    width: 956px;
+  }
+  input.span9,
+  textarea.span9,
+  .uneditable-input.span9 {
+    width: 856px;
+  }
+  input.span8,
+  textarea.span8,
+  .uneditable-input.span8 {
+    width: 756px;
+  }
+  input.span7,
+  textarea.span7,
+  .uneditable-input.span7 {
+    width: 656px;
+  }
+  input.span6,
+  textarea.span6,
+  .uneditable-input.span6 {
+    width: 556px;
+  }
+  input.span5,
+  textarea.span5,
+  .uneditable-input.span5 {
+    width: 456px;
+  }
+  input.span4,
+  textarea.span4,
+  .uneditable-input.span4 {
+    width: 356px;
+  }
+  input.span3,
+  textarea.span3,
+  .uneditable-input.span3 {
+    width: 256px;
+  }
+  input.span2,
+  textarea.span2,
+  .uneditable-input.span2 {
+    width: 156px;
+  }
+  input.span1,
+  textarea.span1,
+  .uneditable-input.span1 {
+    width: 56px;
+  }
+  .thumbnails {
+    margin-left: -30px;
+  }
+  .thumbnails > li {
+    margin-left: 30px;
+  }
+  .row-fluid .thumbnails {
+    margin-left: 0;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+  .row {
+    margin-left: -20px;
+    *zoom: 1;
+  }
+  .row:before,
+  .row:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row:after {
+    clear: both;
+  }
+  [class*="span"] {
+    float: left;
+    min-height: 1px;
+    margin-left: 20px;
+  }
+  .container,
+  .navbar-static-top .container,
+  .navbar-fixed-top .container,
+  .navbar-fixed-bottom .container {
+    width: 724px;
+  }
+  .span12 {
+    width: 724px;
+  }
+  .span11 {
+    width: 662px;
+  }
+  .span10 {
+    width: 600px;
+  }
+  .span9 {
+    width: 538px;
+  }
+  .span8 {
+    width: 476px;
+  }
+  .span7 {
+    width: 414px;
+  }
+  .span6 {
+    width: 352px;
+  }
+  .span5 {
+    width: 290px;
+  }
+  .span4 {
+    width: 228px;
+  }
+  .span3 {
+    width: 166px;
+  }
+  .span2 {
+    width: 104px;
+  }
+  .span1 {
+    width: 42px;
+  }
+  .offset12 {
+    margin-left: 764px;
+  }
+  .offset11 {
+    margin-left: 702px;
+  }
+  .offset10 {
+    margin-left: 640px;
+  }
+  .offset9 {
+    margin-left: 578px;
+  }
+  .offset8 {
+    margin-left: 516px;
+  }
+  .offset7 {
+    margin-left: 454px;
+  }
+  .offset6 {
+    margin-left: 392px;
+  }
+  .offset5 {
+    margin-left: 330px;
+  }
+  .offset4 {
+    margin-left: 268px;
+  }
+  .offset3 {
+    margin-left: 206px;
+  }
+  .offset2 {
+    margin-left: 144px;
+  }
+  .offset1 {
+    margin-left: 82px;
+  }
+  .row-fluid {
+    width: 100%;
+    *zoom: 1;
+  }
+  .row-fluid:before,
+  .row-fluid:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row-fluid:after {
+    clear: both;
+  }
+  .row-fluid [class*="span"] {
+    display: block;
+    float: left;
+    width: 100%;
+    min-height: 30px;
+    margin-left: 2.7624309392265194%;
+    *margin-left: 2.709239449864817%;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .row-fluid [class*="span"]:first-child {
+    margin-left: 0;
+  }
+  .row-fluid .span12 {
+    width: 100%;
+    *width: 99.94680851063829%;
+  }
+  .row-fluid .span11 {
+    width: 91.43646408839778%;
+    *width: 91.38327259903608%;
+  }
+  .row-fluid .span10 {
+    width: 82.87292817679558%;
+    *width: 82.81973668743387%;
+  }
+  .row-fluid .span9 {
+    width: 74.30939226519337%;
+    *width: 74.25620077583166%;
+  }
+  .row-fluid .span8 {
+    width: 65.74585635359117%;
+    *width: 65.69266486422946%;
+  }
+  .row-fluid .span7 {
+    width: 57.18232044198895%;
+    *width: 57.12912895262725%;
+  }
+  .row-fluid .span6 {
+    width: 48.61878453038674%;
+    *width: 48.56559304102504%;
+  }
+  .row-fluid .span5 {
+    width: 40.05524861878453%;
+    *width: 40.00205712942283%;
+  }
+  .row-fluid .span4 {
+    width: 31.491712707182323%;
+    *width: 31.43852121782062%;
+  }
+  .row-fluid .span3 {
+    width: 22.92817679558011%;
+    *width: 22.87498530621841%;
+  }
+  .row-fluid .span2 {
+    width: 14.3646408839779%;
+    *width: 14.311449394616199%;
+  }
+  .row-fluid .span1 {
+    width: 5.801104972375691%;
+    *width: 5.747913483013988%;
+  }
+  .row-fluid .offset12 {
+    margin-left: 105.52486187845304%;
+    *margin-left: 105.41847889972962%;
+  }
+  .row-fluid .offset12:first-child {
+    margin-left: 102.76243093922652%;
+    *margin-left: 102.6560479605031%;
+  }
+  .row-fluid .offset11 {
+    margin-left: 96.96132596685082%;
+    *margin-left: 96.8549429881274%;
+  }
+  .row-fluid .offset11:first-child {
+    margin-left: 94.1988950276243%;
+    *margin-left: 94.09251204890089%;
+  }
+  .row-fluid .offset10 {
+    margin-left: 88.39779005524862%;
+    *margin-left: 88.2914070765252%;
+  }
+  .row-fluid .offset10:first-child {
+    margin-left: 85.6353591160221%;
+    *margin-left: 85.52897613729868%;
+  }
+  .row-fluid .offset9 {
+    margin-left: 79.8342541436464%;
+    *margin-left: 79.72787116492299%;
+  }
+  .row-fluid .offset9:first-child {
+    margin-left: 77.07182320441989%;
+    *margin-left: 76.96544022569647%;
+  }
+  .row-fluid .offset8 {
+    margin-left: 71.2707182320442%;
+    *margin-left: 71.16433525332079%;
+  }
+  .row-fluid .offset8:first-child {
+    margin-left: 68.50828729281768%;
+    *margin-left: 68.40190431409427%;
+  }
+  .row-fluid .offset7 {
+    margin-left: 62.70718232044199%;
+    *margin-left: 62.600799341718584%;
+  }
+  .row-fluid .offset7:first-child {
+    margin-left: 59.94475138121547%;
+    *margin-left: 59.838368402492065%;
+  }
+  .row-fluid .offset6 {
+    margin-left: 54.14364640883978%;
+    *margin-left: 54.037263430116376%;
+  }
+  .row-fluid .offset6:first-child {
+    margin-left: 51.38121546961326%;
+    *margin-left: 51.27483249088986%;
+  }
+  .row-fluid .offset5 {
+    margin-left: 45.58011049723757%;
+    *margin-left: 45.47372751851417%;
+  }
+  .row-fluid .offset5:first-child {
+    margin-left: 42.81767955801105%;
+    *margin-left: 42.71129657928765%;
+  }
+  .row-fluid .offset4 {
+    margin-left: 37.01657458563536%;
+    *margin-left: 36.91019160691196%;
+  }
+  .row-fluid .offset4:first-child {
+    margin-left: 34.25414364640884%;
+    *margin-left: 34.14776066768544%;
+  }
+  .row-fluid .offset3 {
+    margin-left: 28.45303867403315%;
+    *margin-left: 28.346655695309746%;
+  }
+  .row-fluid .offset3:first-child {
+    margin-left: 25.69060773480663%;
+    *margin-left: 25.584224756083227%;
+  }
+  .row-fluid .offset2 {
+    margin-left: 19.88950276243094%;
+    *margin-left: 19.783119783707537%;
+  }
+  .row-fluid .offset2:first-child {
+    margin-left: 17.12707182320442%;
+    *margin-left: 17.02068884448102%;
+  }
+  .row-fluid .offset1 {
+    margin-left: 11.32596685082873%;
+    *margin-left: 11.219583872105325%;
+  }
+  .row-fluid .offset1:first-child {
+    margin-left: 8.56353591160221%;
+    *margin-left: 8.457152932878806%;
+  }
+  input,
+  textarea,
+  .uneditable-input {
+    margin-left: 0;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 20px;
+  }
+  input.span12,
+  textarea.span12,
+  .uneditable-input.span12 {
+    width: 710px;
+  }
+  input.span11,
+  textarea.span11,
+  .uneditable-input.span11 {
+    width: 648px;
+  }
+  input.span10,
+  textarea.span10,
+  .uneditable-input.span10 {
+    width: 586px;
+  }
+  input.span9,
+  textarea.span9,
+  .uneditable-input.span9 {
+    width: 524px;
+  }
+  input.span8,
+  textarea.span8,
+  .uneditable-input.span8 {
+    width: 462px;
+  }
+  input.span7,
+  textarea.span7,
+  .uneditable-input.span7 {
+    width: 400px;
+  }
+  input.span6,
+  textarea.span6,
+  .uneditable-input.span6 {
+    width: 338px;
+  }
+  input.span5,
+  textarea.span5,
+  .uneditable-input.span5 {
+    width: 276px;
+  }
+  input.span4,
+  textarea.span4,
+  .uneditable-input.span4 {
+    width: 214px;
+  }
+  input.span3,
+  textarea.span3,
+  .uneditable-input.span3 {
+    width: 152px;
+  }
+  input.span2,
+  textarea.span2,
+  .uneditable-input.span2 {
+    width: 90px;
+  }
+  input.span1,
+  textarea.span1,
+  .uneditable-input.span1 {
+    width: 28px;
+  }
+}
+
+@media (max-width: 767px) {
+  body {
+    padding-right: 20px;
+    padding-left: 20px;
+  }
+  .navbar-fixed-top,
+  .navbar-fixed-bottom,
+  .navbar-static-top {
+    margin-right: -20px;
+    margin-left: -20px;
+  }
+  .container-fluid {
+    padding: 0;
+  }
+  .dl-horizontal dt {
+    float: none;
+    width: auto;
+    clear: none;
+    text-align: left;
+  }
+  .dl-horizontal dd {
+    margin-left: 0;
+  }
+  .container {
+    width: auto;
+  }
+  .row-fluid {
+    width: 100%;
+  }
+  .row,
+  .thumbnails {
+    margin-left: 0;
+  }
+  .thumbnails > li {
+    float: none;
+    margin-left: 0;
+  }
+  [class*="span"],
+  .row-fluid [class*="span"] {
+    display: block;
+    float: none;
+    width: 100%;
+    margin-left: 0;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .span12,
+  .row-fluid .span12 {
+    width: 100%;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .input-large,
+  .input-xlarge,
+  .input-xxlarge,
+  input[class*="span"],
+  select[class*="span"],
+  textarea[class*="span"],
+  .uneditable-input {
+    display: block;
+    width: 100%;
+    min-height: 30px;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .input-prepend input,
+  .input-append input,
+  .input-prepend input[class*="span"],
+  .input-append input[class*="span"] {
+    display: inline-block;
+    width: auto;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 0;
+  }
+  .modal {
+    position: fixed;
+    top: 20px;
+    right: 20px;
+    left: 20px;
+    width: auto;
+    margin: 0;
+  }
+  .modal.fade.in {
+    top: auto;
+  }
+}
+
+@media (max-width: 480px) {
+  .nav-collapse {
+    -webkit-transform: translate3d(0, 0, 0);
+  }
+  .page-header h1 small {
+    display: block;
+    line-height: 20px;
+  }
+  input[type="checkbox"],
+  input[type="radio"] {
+    border: 1px solid #ccc;
+  }
+  .form-horizontal .control-label {
+    float: none;
+    width: auto;
+    padding-top: 0;
+    text-align: left;
+  }
+  .form-horizontal .controls {
+    margin-left: 0;
+  }
+  .form-horizontal .control-list {
+    padding-top: 0;
+  }
+  .form-horizontal .form-actions {
+    padding-right: 10px;
+    padding-left: 10px;
+  }
+  .modal {
+    top: 10px;
+    right: 10px;
+    left: 10px;
+  }
+  .modal-header .close {
+    padding: 10px;
+    margin: -10px;
+  }
+  .carousel-caption {
+    position: static;
+  }
+}
+
+@media (max-width: 979px) {
+  body {
+    padding-top: 0;
+  }
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    position: static;
+  }
+  .navbar-fixed-top {
+    margin-bottom: 20px;
+  }
+  .navbar-fixed-bottom {
+    margin-top: 20px;
+  }
+  .navbar-fixed-top .navbar-inner,
+  .navbar-fixed-bottom .navbar-inner {
+    padding: 5px;
+  }
+  .navbar .container {
+    width: auto;
+    padding: 0;
+  }
+  .navbar .brand {
+    padding-right: 10px;
+    padding-left: 10px;
+    margin: 0 0 0 -5px;
+  }
+  .nav-collapse {
+    clear: both;
+  }
+  .nav-collapse .nav {
+    float: none;
+    margin: 0 0 10px;
+  }
+  .nav-collapse .nav > li {
+    float: none;
+  }
+  .nav-collapse .nav > li > a {
+    margin-bottom: 2px;
+  }
+  .nav-collapse .nav > .divider-vertical {
+    display: none;
+  }
+  .nav-collapse .nav .nav-header {
+    color: #777777;
+    text-shadow: none;
+  }
+  .nav-collapse .nav > li > a,
+  .nav-collapse .dropdown-menu a {
+    padding: 9px 15px;
+    font-weight: bold;
+    color: #777777;
+    -webkit-border-radius: 3px;
+       -moz-border-radius: 3px;
+            border-radius: 3px;
+  }
+  .nav-collapse .btn {
+    padding: 4px 10px 4px;
+    font-weight: normal;
+    -webkit-border-radius: 4px;
+       -moz-border-radius: 4px;
+            border-radius: 4px;
+  }
+  .nav-collapse .dropdown-menu li + li a {
+    margin-bottom: 2px;
+  }
+  .nav-collapse .nav > li > a:hover,
+  .nav-collapse .dropdown-menu a:hover {
+    background-color: #f2f2f2;
+  }
+  .navbar-inverse .nav-collapse .nav > li > a:hover,
+  .navbar-inverse .nav-collapse .dropdown-menu a:hover {
+    background-color: #111111;
+  }
+  .nav-collapse.in .btn-group {
+    padding: 0;
+    margin-top: 5px;
+  }
+  .nav-collapse .dropdown-menu {
+    position: static;
+    top: auto;
+    left: auto;
+    display: block;
+    float: none;
+    max-width: none;
+    padding: 0;
+    margin: 0 15px;
+    background-color: transparent;
+    border: none;
+    -webkit-border-radius: 0;
+       -moz-border-radius: 0;
+            border-radius: 0;
+    -webkit-box-shadow: none;
+       -moz-box-shadow: none;
+            box-shadow: none;
+  }
+  .nav-collapse .dropdown-menu:before,
+  .nav-collapse .dropdown-menu:after {
+    display: none;
+  }
+  .nav-collapse .dropdown-menu .divider {
+    display: none;
+  }
+  .nav-collapse .nav > li > .dropdown-menu:before,
+  .nav-collapse .nav > li > .dropdown-menu:after {
+    display: none;
+  }
+  .nav-collapse .navbar-form,
+  .nav-collapse .navbar-search {
+    float: none;
+    padding: 10px 15px;
+    margin: 10px 0;
+    border-top: 1px solid #f2f2f2;
+    border-bottom: 1px solid #f2f2f2;
+    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+       -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+  .navbar-inverse .nav-collapse .navbar-form,
+  .navbar-inverse .nav-collapse .navbar-search {
+    border-top-color: #111111;
+    border-bottom-color: #111111;
+  }
+  .navbar .nav-collapse .nav.pull-right {
+    float: none;
+    margin-left: 0;
+  }
+  .nav-collapse,
+  .nav-collapse.collapse {
+    height: 0;
+    overflow: hidden;
+  }
+  .navbar .btn-navbar {
+    display: block;
+  }
+  .navbar-static .navbar-inner {
+    padding-right: 10px;
+    padding-left: 10px;
+  }
+}
+
+@media (min-width: 980px) {
+  .nav-collapse.collapse {
+    height: auto !important;
+    overflow: visible !important;
+  }
+}

File static/css/bootstrap-responsive.min.css

+/*!
+ * Bootstrap Responsive v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade.in{top:auto}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:block;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}

File static/css/bootstrap.css

+/*!
+ * Bootstrap v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+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;
+}
+
+html {
+  font-size: 100%;
+  -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+}
+
+a:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+a:hover,
+a:active {
+  outline: 0;
+}
+
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+img {
+  width: auto\9;
+  height: auto;
+  max-width: 100%;
+  vertical-align: middle;
+  border: 0;
+  -ms-interpolation-mode: bicubic;
+}
+
+#map_canvas img {
+  max-width: none;
+}
+
+button,
+input,
+select,
+textarea {
+  margin: 0;
+  font-size: 100%;
+  vertical-align: middle;
+}
+
+button,
+input {
+  *overflow: visible;
+  line-height: normal;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  cursor: pointer;
+  -webkit-appearance: button;
+}
+
+input[type="search"] {
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+  -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-cancel-button {
+  -webkit-appearance: none;
+}
+
+textarea {
+  overflow: auto;
+  vertical-align: top;
+}
+
+.clearfix {
+  *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;