bandido 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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; }