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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +674 -0
- data/README.rdoc +77 -0
- data/Rakefile +34 -0
- data/bandit.gemspec +22 -0
- data/lib/bandit/config.rb +33 -0
- data/lib/bandit/date_hour.rb +82 -0
- data/lib/bandit/exceptions.rb +10 -0
- data/lib/bandit/experiment.rb +63 -0
- data/lib/bandit/extensions/array.rb +3 -0
- data/lib/bandit/extensions/controller_concerns.rb +33 -0
- data/lib/bandit/extensions/string.rb +5 -0
- data/lib/bandit/extensions/time.rb +5 -0
- data/lib/bandit/extensions/view_concerns.rb +40 -0
- data/lib/bandit/memoizable.rb +32 -0
- data/lib/bandit/players/base.rb +37 -0
- data/lib/bandit/players/epsilon_greedy.rb +32 -0
- data/lib/bandit/players/round_robin.rb +7 -0
- data/lib/bandit/storage/base.rb +134 -0
- data/lib/bandit/storage/memcache.rb +44 -0
- data/lib/bandit/storage/memory.rb +31 -0
- data/lib/bandit/storage/redis.rb +47 -0
- data/lib/bandit/version.rb +3 -0
- data/lib/bandit.rb +69 -0
- data/lib/generators/bandit/USAGE +3 -0
- data/lib/generators/bandit/dashboard_generator.rb +31 -0
- data/lib/generators/bandit/install_generator.rb +22 -0
- data/lib/generators/bandit/templates/bandit.rake +20 -0
- data/lib/generators/bandit/templates/bandit.rb +18 -0
- data/lib/generators/bandit/templates/bandit.yml +16 -0
- data/lib/generators/bandit/templates/bandit_controller.rb +30 -0
- data/lib/generators/bandit/templates/dashboard/bandit.html.erb +43 -0
- data/lib/generators/bandit/templates/dashboard/css/application.css +7 -0
- data/lib/generators/bandit/templates/dashboard/css/base.css +28 -0
- data/lib/generators/bandit/templates/dashboard/css/toupee/buttons.css +101 -0
- data/lib/generators/bandit/templates/dashboard/css/toupee/forms.css +89 -0
- data/lib/generators/bandit/templates/dashboard/css/toupee/modules.css +30 -0
- data/lib/generators/bandit/templates/dashboard/css/toupee/reset.css +42 -0
- data/lib/generators/bandit/templates/dashboard/css/toupee/structure.css +124 -0
- data/lib/generators/bandit/templates/dashboard/css/toupee/typography.css +103 -0
- data/lib/generators/bandit/templates/dashboard/helpers/bandit_helper.rb +23 -0
- data/lib/generators/bandit/templates/dashboard/js/bandit.js +51 -0
- data/lib/generators/bandit/templates/dashboard/js/highstock.js +215 -0
- data/lib/generators/bandit/templates/dashboard/js/jquery.min.js +154 -0
- data/lib/generators/bandit/templates/dashboard/view/_experiment_table.html.erb +19 -0
- data/lib/generators/bandit/templates/dashboard/view/index.html.erb +14 -0
- data/lib/generators/bandit/templates/dashboard/view/show.html.erb +19 -0
- data/players.rdoc +21 -0
- data/test/config.yml +7 -0
- data/test/helper.rb +18 -0
- data/test/memcache_storage_test.rb +17 -0
- data/test/memory_storage_test.rb +16 -0
- data/test/redis_storage_test.rb +17 -0
- data/test/storage_test_base.rb +54 -0
- data/whybandit.rdoc +31 -0
- 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
|
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,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,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; }
|