bandido 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +674 -0
  4. data/README.rdoc +77 -0
  5. data/Rakefile +34 -0
  6. data/bandit.gemspec +22 -0
  7. data/lib/bandit/config.rb +33 -0
  8. data/lib/bandit/date_hour.rb +82 -0
  9. data/lib/bandit/exceptions.rb +10 -0
  10. data/lib/bandit/experiment.rb +63 -0
  11. data/lib/bandit/extensions/array.rb +3 -0
  12. data/lib/bandit/extensions/controller_concerns.rb +33 -0
  13. data/lib/bandit/extensions/string.rb +5 -0
  14. data/lib/bandit/extensions/time.rb +5 -0
  15. data/lib/bandit/extensions/view_concerns.rb +40 -0
  16. data/lib/bandit/memoizable.rb +32 -0
  17. data/lib/bandit/players/base.rb +37 -0
  18. data/lib/bandit/players/epsilon_greedy.rb +32 -0
  19. data/lib/bandit/players/round_robin.rb +7 -0
  20. data/lib/bandit/storage/base.rb +134 -0
  21. data/lib/bandit/storage/memcache.rb +44 -0
  22. data/lib/bandit/storage/memory.rb +31 -0
  23. data/lib/bandit/storage/redis.rb +47 -0
  24. data/lib/bandit/version.rb +3 -0
  25. data/lib/bandit.rb +69 -0
  26. data/lib/generators/bandit/USAGE +3 -0
  27. data/lib/generators/bandit/dashboard_generator.rb +31 -0
  28. data/lib/generators/bandit/install_generator.rb +22 -0
  29. data/lib/generators/bandit/templates/bandit.rake +20 -0
  30. data/lib/generators/bandit/templates/bandit.rb +18 -0
  31. data/lib/generators/bandit/templates/bandit.yml +16 -0
  32. data/lib/generators/bandit/templates/bandit_controller.rb +30 -0
  33. data/lib/generators/bandit/templates/dashboard/bandit.html.erb +43 -0
  34. data/lib/generators/bandit/templates/dashboard/css/application.css +7 -0
  35. data/lib/generators/bandit/templates/dashboard/css/base.css +28 -0
  36. data/lib/generators/bandit/templates/dashboard/css/toupee/buttons.css +101 -0
  37. data/lib/generators/bandit/templates/dashboard/css/toupee/forms.css +89 -0
  38. data/lib/generators/bandit/templates/dashboard/css/toupee/modules.css +30 -0
  39. data/lib/generators/bandit/templates/dashboard/css/toupee/reset.css +42 -0
  40. data/lib/generators/bandit/templates/dashboard/css/toupee/structure.css +124 -0
  41. data/lib/generators/bandit/templates/dashboard/css/toupee/typography.css +103 -0
  42. data/lib/generators/bandit/templates/dashboard/helpers/bandit_helper.rb +23 -0
  43. data/lib/generators/bandit/templates/dashboard/js/bandit.js +51 -0
  44. data/lib/generators/bandit/templates/dashboard/js/highstock.js +215 -0
  45. data/lib/generators/bandit/templates/dashboard/js/jquery.min.js +154 -0
  46. data/lib/generators/bandit/templates/dashboard/view/_experiment_table.html.erb +19 -0
  47. data/lib/generators/bandit/templates/dashboard/view/index.html.erb +14 -0
  48. data/lib/generators/bandit/templates/dashboard/view/show.html.erb +19 -0
  49. data/players.rdoc +21 -0
  50. data/test/config.yml +7 -0
  51. data/test/helper.rb +18 -0
  52. data/test/memcache_storage_test.rb +17 -0
  53. data/test/memory_storage_test.rb +16 -0
  54. data/test/redis_storage_test.rb +17 -0
  55. data/test/storage_test_base.rb +54 -0
  56. data/whybandit.rdoc +31 -0
  57. metadata +166 -0
@@ -0,0 +1,44 @@
1
+ module Bandit
2
+ class MemCacheStorage < BaseStorage
3
+ def initialize(config)
4
+ require 'memcache'
5
+ config[:namespace] ||= 'bandit'
6
+ @memcache = MemCache.new(config.fetch(:host, 'localhost:11211'), config)
7
+ end
8
+
9
+ # increment key by count
10
+ def incr(key, count=1)
11
+ # memcache incr seems to be broken in memcache-client gem
12
+ with_failure_grace(count) {
13
+ set(key, get(key, 0) + count)
14
+ }
15
+ end
16
+
17
+ # initialize key if not set
18
+ def init(key, value)
19
+ with_failure_grace(value) {
20
+ @memcache.add(key, value)
21
+ }
22
+ end
23
+
24
+ # get key if exists, otherwise 0
25
+ def get(key, default=0)
26
+ with_failure_grace(default) {
27
+ @memcache.get(key) || default
28
+ }
29
+ end
30
+
31
+ # set key with value, regardless of whether it is set or not
32
+ def set(key, value)
33
+ with_failure_grace(value) {
34
+ @memcache.set(key, value)
35
+ }
36
+ end
37
+
38
+ def clear!
39
+ with_failure_grace(nil) {
40
+ @memcache.flush_all
41
+ }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ module Bandit
2
+ class MemoryStorage < BaseStorage
3
+ def initialize(config)
4
+ @memory = Hash.new nil
5
+ end
6
+
7
+ # increment key by count
8
+ def incr(key, count=1)
9
+ @memory[key] = get(key) + count
10
+ end
11
+
12
+ # initialize key if not set
13
+ def init(key, value)
14
+ @memory[key] = value if @memory[key].nil?
15
+ end
16
+
17
+ # get key if exists, otherwise 0
18
+ def get(key, default=0)
19
+ @memory.fetch(key, default)
20
+ end
21
+
22
+ # set key with value, regardless of whether it is set or not
23
+ def set(key, value)
24
+ @memory[key] = value
25
+ end
26
+
27
+ def clear!
28
+ @memory = Hash.new nil
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,47 @@
1
+ module Bandit
2
+ class RedisStorage < BaseStorage
3
+ def initialize(config)
4
+ require 'redis'
5
+ config[:host] ||= 'localhost'
6
+ config[:port] ||= 6379
7
+ config[:db] ||= "bandit"
8
+ @redis = Redis.new config
9
+ end
10
+
11
+ # increment key by count
12
+ def incr(key, count=1)
13
+ with_failure_grace(count) {
14
+ @redis.incrby(key, count)
15
+ }
16
+ end
17
+
18
+ # initialize key if not set
19
+ def init(key, value)
20
+ with_failure_grace(value) {
21
+ @redis.set(key, value) if get(key, nil).nil?
22
+ }
23
+ end
24
+
25
+ # get key if exists, otherwise 0
26
+ def get(key, default=0)
27
+ with_failure_grace(default) {
28
+ val = @redis.get(key)
29
+ return default if val.nil?
30
+ val.numeric? ? val.to_i : val
31
+ }
32
+ end
33
+
34
+ # set key with value, regardless of whether it is set or not
35
+ def set(key, value)
36
+ with_failure_grace(value) {
37
+ @redis.set(key, value)
38
+ }
39
+ end
40
+
41
+ def clear!
42
+ with_failure_grace(nil) {
43
+ @redis.flushdb
44
+ }
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ module Bandit
2
+ VERSION = "0.0.6"
3
+ end
data/lib/bandit.rb ADDED
@@ -0,0 +1,69 @@
1
+ require "bandit/version"
2
+ require "bandit/exceptions"
3
+ require "bandit/config"
4
+ require "bandit/experiment"
5
+ require "bandit/date_hour"
6
+ require "bandit/memoizable"
7
+
8
+ require "bandit/players/base"
9
+ require "bandit/players/round_robin"
10
+ require "bandit/players/epsilon_greedy"
11
+
12
+ require "bandit/storage/base"
13
+ require "bandit/storage/memory"
14
+ require "bandit/storage/memcache"
15
+ require "bandit/storage/redis"
16
+
17
+ require "bandit/extensions/controller_concerns"
18
+ require "bandit/extensions/array"
19
+ require "bandit/extensions/view_concerns"
20
+ require "bandit/extensions/time"
21
+ require "bandit/extensions/string"
22
+
23
+ module Bandit
24
+ @@storage_failure_at = nil
25
+
26
+ def self.config
27
+ @config ||= Config.new
28
+ end
29
+
30
+ def self.setup(&block)
31
+ yield config
32
+ config.check!
33
+ # intern keys in storage config
34
+ config.storage_config = config.storage_config.inject({}) { |n,o| n[o.first.intern] = o.last; n }
35
+ end
36
+
37
+ def self.storage
38
+ # try using configured storage at least once every 5 minutes until resolved
39
+ if @@storage_failure_at.nil? or (Time.now.to_i - @@storage_failure_at) > 300
40
+ @storage ||= BaseStorage.get_storage(Bandit.config.storage.intern, Bandit.config.storage_config)
41
+ else
42
+ Rails.logger.warn "storage failure detected #{Time.now.to_i - @@storage_failure_at} seconds ago - using memory storage for 5 minutes"
43
+ BaseStorage.get_storage(:memory, Bandit.config.storage_config)
44
+ end
45
+ end
46
+
47
+ def self.player
48
+ @player ||= BasePlayer.get_player(Bandit.config.player.intern, Bandit.config.player_config)
49
+ end
50
+
51
+ def self.storage_failed!
52
+ @@storage_failure_at = Time.now.to_i
53
+ end
54
+
55
+ def self.get_experiment(name)
56
+ exp = Experiment.instances.select { |e| e.name == name }
57
+ exp.length > 0 ? exp.first : nil
58
+ end
59
+
60
+ def self.experiments
61
+ Experiment.instances
62
+ end
63
+ end
64
+
65
+ require 'action_controller'
66
+ ActionController::Base.send :include, Bandit::ControllerConcerns
67
+
68
+ require 'action_view'
69
+ ActionView::Base.send :include, Bandit::ViewConcerns
@@ -0,0 +1,3 @@
1
+ To copy bandit config and initializer:
2
+
3
+ rails generate bandit:install
@@ -0,0 +1,31 @@
1
+ module Bandit
2
+ module Generators
3
+
4
+ class DashboardGenerator < Rails::Generators::Base
5
+ desc "Create a bandit dashboard controller"
6
+ source_root File.expand_path('../templates', __FILE__)
7
+
8
+ def copy_controller
9
+ copy_file 'bandit_controller.rb', 'app/controllers/bandit_controller.rb'
10
+ end
11
+
12
+ def copy_view
13
+ directory 'dashboard/view', 'app/views/bandit'
14
+ copy_file 'dashboard/bandit.html.erb', 'app/views/layouts/bandit.html.erb'
15
+ directory 'dashboard/helpers', 'app/helpers'
16
+ end
17
+
18
+ def copy_assets
19
+ directory 'dashboard/js', 'public/javascripts/bandit'
20
+ directory 'dashboard/css', 'public/stylesheets/bandit'
21
+ end
22
+
23
+ def message
24
+ say "\n\tNow, add the following to your config/routes.rb file:"
25
+ say "\t\tresources :bandit\n\n"
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ module Bandit
2
+ module Generators
3
+
4
+ class InstallGenerator < Rails::Generators::Base
5
+ desc "Copy Bandit default config/initialization files"
6
+ source_root File.expand_path('../templates', __FILE__)
7
+
8
+ def copy_initializers
9
+ copy_file 'bandit.rb', 'config/initializers/bandit.rb'
10
+ end
11
+
12
+ def copy_config
13
+ copy_file 'bandit.yml', 'config/bandit.yml'
14
+ end
15
+
16
+ def copy_rakefile
17
+ copy_file 'bandit.rake', 'lib/tasks/bandit.rake'
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ namespace :bandit do
2
+
3
+ desc "Generate fake data for a named experiment"
4
+ task :populate_data, [:experiment] => [:environment] do |t, args|
5
+ storage = Bandit.storage
6
+ exp = Bandit.get_experiment args[:experiment].intern
7
+ raise "No such experiment defined: #{args[:experiment]}" if exp.nil?
8
+
9
+ start = Bandit::DateHour.new((Date.today - 7), 0)
10
+ start.upto(Bandit::DateHour.now) { |dh|
11
+ puts "Adding data for alternatives on #{dh}"
12
+ exp.alternatives.each { |alt|
13
+ count = 1000 + rand(1000)
14
+ storage.incr_participants(exp, alt, count, dh)
15
+ storage.incr_conversions(exp, alt, (rand * count).floor, dh)
16
+ }
17
+ }
18
+ end
19
+
20
+ end
@@ -0,0 +1,18 @@
1
+ # Use this setup block to configure all options for Bandit.
2
+ Bandit.setup do |config|
3
+ yml = YAML.load_file("#{Rails.root}/config/bandit.yml")[Rails.env]
4
+
5
+ config.player = yml['player']
6
+ config.player_config = yml['player_config']
7
+
8
+ config.storage = yml['storage']
9
+ config.storage_config = yml['storage_config']
10
+ end
11
+
12
+ # Create your experiments here - like this:
13
+ # Bandit::Experiment.create(:click_test) { |exp|
14
+ # exp.alternatives = [ 20, 30, 40 ]
15
+ # exp.title = "Click Test"
16
+ # exp.description = "A test of clicks on purchase page with varying link sizes."
17
+ # }
18
+
@@ -0,0 +1,16 @@
1
+ development:
2
+ player: round_robin
3
+ storage: memcache
4
+
5
+ test:
6
+ player: round_robin
7
+ storage: memcache
8
+
9
+ production:
10
+ player: epsilon_greedy
11
+ player_config:
12
+ epsilon: 0.1
13
+ storage: redis
14
+ storage_config:
15
+ host: localhost
16
+ port: 6379
@@ -0,0 +1,30 @@
1
+ class BanditController < ApplicationController
2
+ layout 'bandit'
3
+
4
+ def index
5
+ @experiments = Bandit.experiments
6
+ end
7
+
8
+ def show
9
+ @experiment = Bandit.get_experiment params[:id].intern
10
+ respond_to do |format|
11
+ format.html
12
+ format.csv { render :text => experiment_csv(@experiment) }
13
+ end
14
+ end
15
+
16
+ private
17
+ def experiment_csv(experiment)
18
+ rows = []
19
+ experiment.alternatives.each { |alt|
20
+ start = experiment.alternative_start(alt)
21
+ next if start.nil?
22
+ start.date.upto(Date.today) { |d|
23
+ pcount = Bandit::DateHour.date_inject(d, 0) { |sum,dh| sum + experiment.participant_count(alt, dh) }
24
+ ccount = Bandit::DateHour.date_inject(d, 0) { |sum,dh| sum + experiment.conversion_count(alt, dh) }
25
+ rows << [ alt, d.year, d.month, d.day, pcount, ccount ].join("\t")
26
+ }
27
+ }
28
+ rows.join("\n")
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
3
+ <head>
4
+ <title>Bandit Dashboard</title>
5
+ <%= stylesheet_link_tag "bandit/application.css" %>
6
+ <%= javascript_include_tag "bandit/jquery.min.js" %>
7
+ <%= javascript_include_tag "bandit/highstock.js" %>
8
+ <%= javascript_include_tag "bandit/bandit.js" %>
9
+ </head>
10
+ <body>
11
+ <header>
12
+ <div class="container">
13
+ <div class="span-10 brand">
14
+ <h1><%= link_to "Bandit Dashboard", :controller => 'bandit', :action => 'index' %></h1>
15
+ </div>
16
+ <nav class="span-14 last">
17
+ <ul>
18
+ <li><%= link_to "index", :controller => 'bandit', :action => 'index' %></li>
19
+ </ul>
20
+ </nav>
21
+ </div>
22
+ </header>
23
+ <div class="container">
24
+ <%= yield %>
25
+ <aside class='span-3 last'>
26
+ <section class='mod'>
27
+ <header>
28
+ <h2>Experiments</h2>
29
+ </header>
30
+ <article>
31
+ <ul>
32
+ <% Bandit.experiments.each { |experiment| %>
33
+ <li><%= link_to experiment.name.to_s.titleize, url_for(:action => 'show', :id => experiment.name) %></li>
34
+ <% } %>
35
+ </ul>
36
+ </article>
37
+ </section>
38
+ </aside>
39
+ </div>
40
+ <footer>
41
+ </footer>
42
+ </body>
43
+ </html>
@@ -0,0 +1,7 @@
1
+ @import url(toupee/reset.css);
2
+ @import url(base.css);
3
+ @import url(toupee/buttons.css);
4
+ @import url(toupee/forms.css);
5
+ @import url(toupee/modules.css);
6
+ @import url(toupee/structure.css);
7
+ @import url(toupee/typography.css);
@@ -0,0 +1,28 @@
1
+
2
+ a { text-decoration: none; cursor: pointer; }
3
+ a:hover { text-decoration: underline; }
4
+ p { font-size:1.4em; }
5
+
6
+ /* HEADER
7
+ ---------------------------- */
8
+ header { margin-bottom: 20px; }
9
+ header h1 { padding: 20px 60px 10px 10px; margin: 0; background: #f1f1f1; display: inline-block; }
10
+ header h1 a { color: #aaa; text-decoration: none; }
11
+
12
+ /* NAVIGATION
13
+ ---------------------------- */
14
+ nav { margin-top: 42px; }
15
+ nav ul { display: block; margin: 0; padding: 0 0 0 8px;}
16
+ nav ul li { margin-right: 10px; display: inline; float: left; border-bottom: 5px solid #f1f1f1; }
17
+ nav ul li.current { background-color: #ccc; color: #fff; }
18
+ nav ul li a { display: block; padding: 0 30px 0 0; text-decoration: none; }
19
+
20
+ /* FOOTER
21
+ ---------------------------- */
22
+ footer { border-top: 3px solid #f1f1f1; padding: 15px 0; margin-top: 20px; }
23
+
24
+ table { margin-top: 30px; margin-bottom: 30px; }
25
+
26
+ .graph { margin-top: 20px; }
27
+
28
+ table { padding-left: 30px; }
@@ -0,0 +1,101 @@
1
+ /* BUTTONS
2
+ ========================= */
3
+ .button {
4
+ display: inline-block;
5
+ zoom: 1; /* zoom and *display = ie7 hack for display:inline-block */
6
+ *display: inline;
7
+ vertical-align: top;
8
+ margin: 1px 2px 0;
9
+ outline: none;
10
+ cursor: pointer;
11
+ text-align: center;
12
+ text-decoration: none;
13
+ font: 14px/100% Arial, Helvetica, sans-serif;
14
+ padding: .5em 2em .55em;
15
+
16
+ -webkit-border-radius: .5em;
17
+ -moz-border-radius: .5em;
18
+ border-radius: .5em;
19
+
20
+ -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2);
21
+ -moz-box-shadow: 0 1px 2px rgba(0,0,0,.2);
22
+ box-shadow: 0 1px 2px rgba(0,0,0,.2);
23
+
24
+ color: #e9e9e9;
25
+ border: solid 1px #555;
26
+ background: #6e6e6e;
27
+ background: -webkit-gradient(linear, left top, left bottom, from(#888), to(#575757));
28
+ background: -moz-linear-gradient(top, #888, #575757);
29
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#888888', endColorstr='#575757');
30
+ }
31
+
32
+ .button:hover {
33
+ text-decoration: none;
34
+ color: #e9e9e9;
35
+ background: #616161;
36
+ background: -webkit-gradient(linear, left top, left bottom, from(#757575), to(#4b4b4b));
37
+ background: -moz-linear-gradient(top, #757575, #4b4b4b);
38
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#757575', endColorstr='#4b4b4b');
39
+ }
40
+
41
+ .button:active {
42
+ color: #afafaf;
43
+ background: -webkit-gradient(linear, left top, left bottom, from(#575757), to(#888));
44
+ background: -moz-linear-gradient(top, #575757, #888);
45
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#575757', endColorstr='#888888');
46
+ }
47
+
48
+ .button a:hover { border: none; text-decoration: none; }
49
+ .button.rounded { -moz-border-radius: 2em; -webkit-border-radius: 2em; border-radius: 2em; }
50
+ .button.medium { font-size: 12px; padding: .4em 1.5em .42em; }
51
+ .button.small { font-size: 11px; padding: .2em 1em .275em; }
52
+
53
+
54
+ /* BUTTON VARIATIONS
55
+ ========================= */
56
+
57
+ /* Third level button == Black */
58
+ .second {
59
+ color: #e8f0de;
60
+ border: solid 1px #538312;
61
+ background: #64991e;
62
+ background: -webkit-gradient(linear, left top, left bottom, from(#7db72f), to(#4e7d0e));
63
+ background: -moz-linear-gradient(top, #7db72f, #4e7d0e);
64
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#7db72f', endColorstr='#4e7d0e');
65
+ }
66
+ .second:hover {
67
+ background: #538018;
68
+ background: -webkit-gradient(linear, left top, left bottom, from(#6b9d28), to(#436b0c));
69
+ background: -moz-linear-gradient(top, #6b9d28, #436b0c);
70
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#6b9d28', endColorstr='#436b0c');
71
+ }
72
+ .second:active {
73
+ color: #a9c08c;
74
+ background: -webkit-gradient(linear, left top, left bottom, from(#4e7d0e), to(#7db72f));
75
+ background: -moz-linear-gradient(top, #4e7d0e, #7db72f);
76
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#4e7d0e', endColorstr='#7db72f');
77
+ }
78
+
79
+
80
+ /* Third level button == White */
81
+ .third {
82
+ color: #606060;
83
+ border: solid 1px #b7b7b7;
84
+ background: #fff;
85
+ background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#ededed));
86
+ background: -moz-linear-gradient(top, #fff, #ededed);
87
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#ededed');
88
+ }
89
+ .third:hover {
90
+ color: #606060;
91
+ background: #ededed;
92
+ background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#dcdcdc));
93
+ background: -moz-linear-gradient(top, #fff, #dcdcdc);
94
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#dcdcdc');
95
+ }
96
+ .third:active {
97
+ color: #999;
98
+ background: -webkit-gradient(linear, left top, left bottom, from(#ededed), to(#fff));
99
+ background: -moz-linear-gradient(top, #ededed, #fff);
100
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed', endColorstr='#ffffff');
101
+ }
@@ -0,0 +1,89 @@
1
+ /* BASIC FORM STRUCTURE
2
+ ======================== */
3
+ form ul { margin: 0; padding: 0; list-style: none; }
4
+ form ul li { margin: 0; padding: 10px 0; min-height: 30px; border-bottom: 1px double #f1f1f1; }
5
+ form ul li.last { border: none; }
6
+ form label { margin: 6px 20px 2px 0; display: block; font-size: 1em; line-height: 1.5em; font-weight: bold; }
7
+ form label span { margin-right: 10px; font-size: .8em; font-weight: normal; color: #777; }
8
+ form label.remember { display: inline; font-size: 1.2em; font-weight: normal; }
9
+ form fieldset { margin-bottom: 20px; border-top: 1px solid #e1e1e1;}
10
+ form fieldset legend { padding: 0 10px 0 0; font-size: 16px; background-color: #fff;}
11
+
12
+ /* DISPLAYING A FORM INLINE */
13
+ form .inline label { float: left; }
14
+ form .inline.center label { width: 150px; text-align: right; }
15
+ form .inline.center li.submit .button { margin-left: 170px; }
16
+
17
+ /* FORM MESSAGING */
18
+ form p.message { margin: 5px 0 0 174px; font-size: .8em; line-height: 1.5em; }
19
+ form p.message strong { font-weight: bold; color: #666; }
20
+
21
+ /* INPUT TEXT
22
+ ======================== */
23
+ form fieldset input.text {
24
+ width: 215px;
25
+ padding: 5px 8px;
26
+ border-top: 1px solid #e1e1e1;
27
+ border-left: none;
28
+ border-bottom: 1px solid #fff;
29
+ border-right: 1px solid #fff;
30
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
31
+ font-size: 1.4em;
32
+ color: #333;
33
+ background-color: #f1f1f1;
34
+ -webkit-border-radius: 6px; -moz-border-radius: 6px; border-radius: 6px;
35
+ }
36
+
37
+ /* INPUT TYPE FILE
38
+ ======================== */
39
+ form fieldset input.file { padding: 5px 8px; border: 1px solid #e1e1e1; background-color: #f1f1f1; }
40
+ label.file-label { width: 79px; height: 22px; background: url("/images/btn-choose-file.gif") 0 0 no-repeat; display: block; overflow: hidden; cursor: pointer; }
41
+ label.file-label input.file { position: relative; height: 100%; width: auto; opacity: 0; -moz-opacity: 0; filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0); }
42
+
43
+ /* TEXTAREA
44
+ ======================== */
45
+
46
+ form fieldset textarea { width: 400px; height: 100px; padding: 5px; border-top: 1px solid #c3c3c3; border-left: none; border-bottom: 1px solid #fff; border-right: 1px solid #fff; background-color: #f1f1f1;
47
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 1.2em; color: #333;
48
+ -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; }
49
+
50
+
51
+ /* SELECT BOX
52
+ ======================== */
53
+ form select { padding: 2px 0 2px 4px; border: 1px solid #e1e1e1; background-color: #f1f1f1; min-width: 230px;
54
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 1.4em; color: #333;
55
+ -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; }
56
+
57
+ form .inline.date select { min-width: inherit; }
58
+
59
+ /* FORM FOCUS STYLES
60
+ ======================== */
61
+ form fieldset input.text:focus,
62
+ form fieldset textarea:focus { border-top-color: #ddd; background: #d0eeff; outline-width: 0; }
63
+
64
+ /* SEARCH FORM
65
+ ======================== */
66
+ .mod ul li.searchform { padding: 5px 5px 3px 5px; }
67
+ .mod.search form fieldset { border: none; }
68
+
69
+ .searchform {
70
+ display: inline-block;
71
+ zoom: 1; /* ie7 hack for display:inline-block */
72
+ *display: inline;
73
+ border: solid 1px #d2d2d2;
74
+ padding: 5px;
75
+
76
+ -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px;
77
+ -webkit-box-shadow: 0 1px 0px rgba(0,0,0,.1); -moz-box-shadow: 0 1px 0px rgba(0,0,0,.1); box-shadow: 0 1px 0px rgba(0,0,0,.1);
78
+
79
+ background: #f1f1f1;
80
+ background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#ededed));
81
+ background: -moz-linear-gradient(top, #fff, #ededed);
82
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#ededed'); /* ie7 */
83
+ -ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#ededed'); /* ie8 */
84
+ }
85
+
86
+ .searchform .searchfield { background: #fff; padding: 8px; width: 470px; border: solid 1px #bcbbbb; outline: none;
87
+ -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px;
88
+ -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,.2); -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,.2); box-shadow: inset 0 1px 2px rgba(0,0,0,.2);
89
+ }
@@ -0,0 +1,30 @@
1
+ /* GENERIC MODULE STYLES
2
+ ============================= */
3
+ section.mod { margin-bottom: 20px; }
4
+ section.mod header { position: relative; margin: 0; padding: 0; }
5
+ section.mod header h2 { margin-bottom: 5px; border-bottom: 1px solid #e1e1e1; position: relative; }
6
+ section.mod header h2 a { border: none; }
7
+ section.mod header h2 .secondary { position: absolute; right: 0; bottom: 7px; font-size: 12px; }
8
+ section.mod article { position: relative; margin: 0; padding: 0; }
9
+ section.mod article ul { list-style: none; margin: 0 0 10px; padding: 0; }
10
+ section.mod article ul li { position: relative; border-bottom: 1px solid #f1f1f1; padding: 5px 0; }
11
+ section.mod article ul li img { float: left; margin-top: 6px; margin-left: 4px;}
12
+ section.mod footer { border: none; margin: 0; padding: 0; }
13
+
14
+ /* PLACES A BOX AROUND MODULE CONTENT */
15
+ .box {
16
+ background: rgba(0, 0, 0, .03);
17
+ border-radius: 3px;
18
+ -webkit-border-radius: 3px;
19
+ -moz-border-radius: 3px;
20
+ boder-radius: 3px;
21
+ -webkit-box-sizing: border-box;
22
+ -moz-box-sizing: border-box;
23
+ -o-box-sizing: border-box;
24
+ box-sizing: border-box;
25
+ border: 1px solid #ccc;
26
+ }
27
+
28
+ .mod.box header,
29
+ .mod.box article,
30
+ .mod.box footer { padding: 0 10px; }