bandido 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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; }
|