Merge branch 'master' into guardians

Conflicts:
	openbadger.js
This commit is contained in:
Andrew Hayward 2013-06-04 01:33:32 +01:00
Родитель 38030f0935 0086d01eee
Коммит ab9cb3b7f8
22 изменённых файлов: 1097 добавлений и 450 удалений

Просмотреть файл

@ -169,3 +169,10 @@ aestimia.defaultOptions = {
};
module.exports = aestimia;
module.exports.healthCheck = function(cb) {
// A random email should guarantee we bust through any caches.
var email = 'healthCheck_test_' +
Math.floor(Math.random() * 100000) + '@mozilla.org';
aestimia.get('/submissions?learner=' + encodeURIComponent(email), cb);
};

36
app.js
Просмотреть файл

@ -1,6 +1,7 @@
if ( process.env.NEW_RELIC_HOME ) {
require( 'newrelic' );
}
const colors = require('colors');
const path = require('path');
const http = require('http');
const express = require('express');
@ -9,16 +10,28 @@ const middleware = require('./middleware');
const helpers = require('./helpers');
const flash = require('connect-flash');
const logger = require('./logger');
const healthCheck = require('./controllers/health-check');
const port = parseInt(process.env.PORT || '3000');
const app = express();
const env = new nunjucks.Environment(new nunjucks.FileSystemLoader(path.join(__dirname, 'views')), {autoescape: false});
env.express(app);
const healthChecker = healthCheck({
auth: express.basicAuth('health_check', process.env.COOKIE_SECRET),
checks: {
s3: healthCheck.checker(require('./s3').healthCheck),
database: healthCheck.checker(require('./db').healthCheck),
openbadger: healthCheck.checker(require('./openbadger').healthCheck),
aestimia: healthCheck.checker(require('./aestimia').healthCheck)
}
});
app.use(express.cookieParser());
app.use(middleware.session());
app.use(middleware.csrf({
whitelist: [
'/applications'
'/applications',
'/health_check'
]
}));
app.use(express.logger({stream:{
@ -44,9 +57,28 @@ require('./controllers/program')(app);
require('./controllers/learn')(app);
require('./controllers/challenges')(app);
app.get('/health_check', healthChecker);
require('./lib/errors')(app, env);
if (!module.parent)
app.listen(3000);
app.listen(port, function(err) {
if (err) throw err;
console.log("Listening on port " + port + ".");
console.log("Performing health check.\n");
healthChecker.runChecks(function(results) {
var consoleStr = healthCheck.resultsToConsoleString(results);
console.log("Health check results:\n");
if (results.status != "OK") {
console.error(consoleStr + "\n");
console.error(("One or more critical services are down or " +
"misconfigured. Please fix them!").red.bold);
} else {
console.log(consoleStr);
console.log(("\nHealth check indicates all systems are " +
"functional.").green);
}
});
});
else
module.exports = http.createServer(app);

119
controllers/health-check.js Normal file
Просмотреть файл

@ -0,0 +1,119 @@
// TODO: Separate this module out into its own npm module/git repo.
var async = require('async');
var colors = require('colors');
const CHECKMARK = "\u2713";
function checker(fn) {
return function check(cb) {
var timeout = setTimeout(function() {
timeout = null;
cb(null, {status: "FAILED", reason: "TIMEOUT"});
}, module.exports.TIMEOUT);
try {
fn(function(err) {
if (timeout === null) return;
clearTimeout(timeout);
timeout = null;
if (err)
return cb(null, {status: "FAILED", reason: err.toString()});
cb(null, {status: "OK"});
});
} catch (e) {
clearTimeout(timeout);
timeout = null;
cb(null, {status: "FAILED", reason: e.toString()});
}
};
}
function sessionStorageChecker(sessionStore) {
return checker(function checkSessionStorage(cb) {
var randomNumber = Math.floor(Math.random() * 10000000);
var sid = "healthCheck_sessionStorage_" + randomNumber;
var session = {
n: randomNumber,
cookie: {maxAge: 3600}
};
async.series([
sessionStore.set.bind(sessionStore, sid, session),
function(cb) {
sessionStore.get(sid, function(err, val) {
if (err) return cb(err);
if (!(val && val.n == randomNumber))
return cb(new Error("session store read/write failure"));
cb();
});
},
sessionStore.destroy.bind(sessionStore, sid)
], cb);
});
}
function runChecks(checks, cb) {
async.parallel(checks, function(err, results) {
if (err !== null)
// This should never happen b/c checkers should catch all errors.
return cb({
status: "FAILED",
reason: "a checker threw an error: " + err
});
Object.keys(results).forEach(function(checkName) {
if (results[checkName].status != "OK")
results.status = "FAILED";
});
if (results.status != "FAILED")
results.status = "OK";
cb(results);
});
}
function resultsToConsoleString(results) {
var lines = [];
Object.keys(results).forEach(function(name) {
var info = results[name];
if (info && typeof(info) == "object" && info.status) {
if (info.status == "OK") {
lines.push(CHECKMARK.green + " " + name.grey);
} else {
lines.push("x".red + " " + name.grey + " " +
(info.reason ? info.reason : ""));
}
}
});
return lines.join('\n');
}
module.exports = function healthCheck(options) {
var authenticate = options.auth || function(req, res, next) { next(); };
var checks = options.checks;
var healthChecker = function healthChecker(req, res, next) {
if (req.query['elb'] == 'true')
return res.send(200, {status: "OK"});
authenticate(req, res, function(err) {
if (err) return next(err);
runChecks(checks, function(results) {
var statusCode = results.status == "OK" ? 200 : 500;
return res.json(statusCode, results);
});
});
};
healthChecker.runChecks = runChecks.bind(null, checks);
return healthChecker;
};
module.exports.TIMEOUT = 15000;
module.exports.resultsToConsoleString = resultsToConsoleString;
module.exports.runChecks = runChecks;
module.exports.sessionStorageChecker = sessionStorageChecker;
module.exports.checker = checker;

Просмотреть файл

@ -218,9 +218,11 @@ module.exports = function (app) {
});
});
app.get('/earn/:badgeName', function (req, res, next) {
app.get('/earn/:badgeName', badger.middleware('getBadgeRecommendations'), function (req, res, next) {
var data = req.remote;
res.render('badges/single.html', {
badge: req.params.badge
badge: req.params.badge,
relatedBadges: data.badges
});
});

8
db.js
Просмотреть файл

@ -97,4 +97,12 @@ db.model = function(name) {
}
db.type = Sequelize;
db.healthCheck = function(cb) {
db.model('Claim').find({
where: {id: 1234}
}).complete(function(err, claim) {
if (err) return cb(err);
cb();
});
};
module.exports = db;

Просмотреть файл

@ -323,6 +323,7 @@ var openbadger = new Api(ENDPOINT, {
});
},
claim: function claim (query, callback) {
var email = query.email;
var code = query.code;
@ -335,6 +336,22 @@ var openbadger = new Api(ENDPOINT, {
return callback(err, data);
});
},
getBadgeRecommendations: function getBadgeRecommendations (query, callback) {
var id = query.badgeName;
if (!id)
return callback(new errors.BadRequest('Invalid badge key'));
this.get('/badge/' + id + '/recommendations', function(err, data) {
if (err)
return callback(err, data);
return callback(null, {
badges: _.map(data.badges, normalizeBadge)
});
});
},
});
updateOrgs();
@ -370,3 +387,14 @@ module.exports.getFilters = function getFilters () {
};
}
module.exports.updateOrgs = updateOrgs;
module.exports.healthCheck = function(cb) {
// Use a privileged API call to ensure we're testing the JWT secret.
// A random email should guarantee we bust through any caches.
var email = 'healthCheck_test_' +
Math.floor(Math.random() * 100000) + '@mozilla.org';
openbadger.getUserBadges({
session: {user: {email: email}}
}, cb);
};

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "CSOL-site",
"version": "0.9.0",
"version": "0.9.14",
"description": "The website for the Chicago Summer of Learning, developed by Ocupop and Mozilla.",
"main": "app.js",
"dependencies": {
@ -19,12 +19,12 @@
"underscore": "~1.4.4",
"winston": "~0.7.1",
"newrelic": "~0.9.19",
"colors": "~0.6.0",
"jwt-simple": "~0.1.0"
},
"devDependencies": {
"sinon": "~1.7.2",
"injectr": "~0.4.0",
"colors": "~0.6.0",
"optimist": "~0.5.2",
"up": "~0.2.2"
},

Просмотреть файл

@ -47,11 +47,26 @@ FakeS3.prototype.get = function(urlPath) {
var abspath = this._toFilePath(urlPath);
var result = new EventEmitter();
process.nextTick(function() {
result.emit('response', fs.createReadStream(abspath));
});
result.end = function() {
process.nextTick(function() {
result.emit('response', fs.createReadStream(abspath));
});
};
return result;
};
FakeS3.prototype.deleteFile = function(urlPath, cb) {
var abspath = this._toFilePath(urlPath);
if (fs.existsSync(abspath))
fs.unlinkSync(abspath);
process.nextTick(function() {
// We don't currently do anything w/ the response, so we won't bother
// passing it in as the second param to the callback.
cb(null);
});
};
module.exports = FakeS3;

27
s3.js
Просмотреть файл

@ -24,4 +24,31 @@ if (process.env["CSOL_AWS_FAKE_S3_DIR"]) {
});
}
s3.healthCheck = function(cb) {
var async = require('async');
var rnd = Math.floor(Math.random() * 100000).toString();
var url = '/healthChecker_test_' + rnd;
async.series([
s3.putBuffer.bind(s3, new Buffer(rnd), url, {
'Content-Type': 'text/plain'
}),
function(cb) {
s3.get(url).on('response', function(proxy) {
var chunks = [];
proxy.on('data', function(chunk) {
chunks.push(chunk);
});
proxy.on('end', function() {
var buf = Buffer.concat(chunks);
if (buf.toString('ascii') != rnd)
return cb("expected " + rnd + ", got " + buf.toString('ascii'));
cb();
});
}).end();
},
s3.deleteFile.bind(s3, url)
], cb);
};
module.exports = s3;

Просмотреть файл

@ -85,6 +85,7 @@ ol {
}
.navbar .navbar-inner {
background-image: url('../img/chalkboard_bg.jpg');
background-repeat: repeat;
}
.navbar form.navbar-inner {
padding: 7px;
@ -107,24 +108,24 @@ ol {
background: #fff;
text-align: center;
}
.navbar .nav-wrap ul {
.navbar .nav-wrap ul.nav {
display: inline-block;
margin: 0 0 0 0;
}
.navbar .nav-wrap ul > li {
.navbar .nav-wrap ul.nav > li {
float: none;
display: inline-block;
}
.navbar .nav-wrap ul > li > a {
.navbar .nav-wrap ul.nav > li > a {
text-shadow: none;
color: #333333;
text-transform: uppercase;
}
.navbar .nav-wrap ul > li > a:focus,
.navbar .nav-wrap ul > li > a:hover {
.navbar .nav-wrap ul.nav > li > a:focus,
.navbar .nav-wrap ul.nav > li > a:hover {
color: #c0c0c0;
}
.navbar .nav-wrap ul > li:last-child {
.navbar .nav-wrap ul.nav > li:last-child {
border-right: none;
}
.navbar .brand {
@ -154,6 +155,7 @@ nav.pagination ul li a {
-webkit-box-shadow: inset 0px 10px 20px 0px rgba(0, 0, 0, 0.25);
-moz-box-shadow: inset 0px 10px 20px 0px rgba(0, 0, 0, 0.25);
box-shadow: inset 0px 10px 20px 0px rgba(0, 0, 0, 0.25);
background-repeat: repeat;
}
.footer a {
color: #fff;
@ -236,8 +238,110 @@ nav.pagination ul li a {
.wrapper.secondary {
position: relative;
color: #fff;
overflow: hidden;
}
/*mobile*/
@media only screen and (min-width: 320px) and (max-width: 599px) {
.container p,
.container h1,
.container h2,
.footer .upper p {
width: inherit;
}
#main {
padding-top: 15px;
}
.form-search label,
.form-inline label,
.form-search .btn-group,
.form-inline .btn-group {
display: block;
}
.filter label {
display: none;
}
.navbar .nav-wrap,
.container,
.navbar-static-top .container,
.navbar-fixed-top .container,
.navbar-fixed-bottom .container,
.navbar .brand {
width: 95%;
margin: 0 0 0 0;
padding: 0 0 0 0;
margin-left: auto;
margin-right: auto;
}
.form-horizontal .control-label {
text-align: left;
float: none;
}
.form-horizontal .controls {
margin-left: 0px;
}
.span6 {
width: inherit;
}
/*mobile navbar*/
.navbar div.navbar-inner {
padding-left: 0px;
padding-right: 0px;
padding-top: 10px;
padding-bottom: 10px;
border: none;
}
.navbar .nav-wrap {
width: 90%;
margin: 0 auto;
}
.navbar .nav-wrap ul.nav li {
display: block;
}
.navbar .nav-wrap ul.nav li a {
padding: 5px 0;
}
.navbar .nav-wrap ul.nav li:nth-child(even),
.navbar .nav-wrap ul.nav > .active > a,
.navbar .nav-wrap ul.nav > .active > a:hover {
border-left: none;
border-right: none;
}
/*mobile footer*/
.footer .upper {
padding-top: 15px;
}
.footer .upper p {
width: inherit;
background-image: none;
padding-left: 0px;
}
.footer .upper ul.pull-right {
width: 270px;
display: block;
float: none;
margin: 0 auto;
}
.footer .upper ul.pull-right li {
width: 90px;
float: left;
}
.footer .upper ul.pull-right li a {
margin: 0 auto;
}
.footer .lower {
border-top: none;
}
.footer .lower ul.pull-left {
display: none;
}
.footer .lower ul.pull-right {
float: none;
text-align: center;
}
.footer .lower ul.pull-right li {
display: inline;
}
}
/*CSOL-site specific*/
.poster a,
.poster img {
display: block;
@ -495,20 +599,62 @@ input[type="password"].metered:focus:invalid + .password-meter {
input[type="password"].metered:focus:invalid:focus + .password-meter {
border-color: #e9322d;
}
/*landing page specific*/
body.home {
background-image: url('../img/background-chalkboard-green.jpg');
background-repeat: no-repeat;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale');
-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale')";
overflow: visible;
/* ------------------------------ */
/* Mobile : width > iphone ------ */
/* ------------------------------ */
@media only screen and (min-width: 321px) and (max-width: 940px) {
.navbar .brand {
background-image: url('../img/csol_logo_480.png');
height: 166px;
}
}
body.home .container {
width: 960px;
/* ------------------------------ */
/* Mobile : width <= iphone ----- */
/* ------------------------------ */
@media only screen and (max-width: 320px) {
.navbar .brand {
background-image: url('../img/csol_logo_320.png');
height: 116px;
}
}
/* ------------------------------ */
/* CSOL-site SPECIFIC ----------- */
/* ------------------------------ */
/* ------------------------------ */
/* Mobile : width <= iphone ---- */
/* ------------------------------ */
@media only screen and (max-width: 320px) {
.span3 {
width: 150px;
}
ul.thumbnails li figure.thumbnail > a img {
width: 140px;
}
ul.thumbnails li figure.thumbnail {
margin: 0 0 0 0;
}
ul.thumbnails li figure.thumbnail > a img {
display: block;
width: 140px;
height: 140px;
background-image: url('../img/badge-default-320.png');
}
ul.thumbnails li:nth-child(2n+2) figure.thumbnail > a img {
background-position: 0 -140px;
}
ul.thumbnails li:nth-child(3n+3) figure.thumbnail > a img {
background-position: -140px 0px;
}
ul.thumbnails li:nth-child(4n+4) figure.thumbnail > a img {
background-position: -140px -140px;
}
}
/* ------------------------------ */
/* Landing page ---------------- */
/* ------------------------------ */
body.home #main {
padding: 0 0 0 0;
display: none;
}
body.home .navbar {
-webkit-box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.25);
@ -516,24 +662,9 @@ body.home .navbar {
box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.25);
}
body.home .navbar .navbar-inner {
background-image: url('../img/spacedimg.jpg');
background-repeat: no-repeat;
background-position: top center;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
padding-top: 0px;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale');
-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale')";
overflow: visible;
max-height: 515px;
}
body.home .navbar .navbar-inner > .container {
background-image: url('../img/home_drawing.png');
background-position: top center;
background-repeat: no-repeat;
}
body.home .navbar .nav > li.dropdown.open > .dropdown-toggle {
color: #fff;
}
@ -541,7 +672,7 @@ body.home .navbar .brand {
background-image: url('../img/csol_logo_sm.png');
position: absolute;
left: 50%;
margin-left: -102px;
margin-left: -93px;
z-index: 2;
top: 50px;
width: 189px;
@ -552,36 +683,35 @@ body.home .navbar .nav-wrap {
width: inherit;
margin-bottom: 0px;
height: 600px;
width: 310px;
width: 314px;
margin: 0 auto;
background: none;
background-image: url('../img/banner300.png');
background-image: url('../img/banner314.png');
background-position: top center;
background-repeat: no-repeat;
position: relative;
z-index: 1;
box-shadow: none;
}
body.home .navbar .nav-wrap ul {
body.home .navbar .nav-wrap ul.nav {
padding-top: 200px;
left: -6px;
}
body.home .navbar .nav-wrap ul > li {
body.home .navbar .nav-wrap ul.nav > li {
text-align: left;
display: block;
line-height: 9px;
}
body.home .navbar .nav-wrap ul > li > a {
body.home .navbar .nav-wrap ul.nav > li > a {
text-transform: inherit;
font-size: 22px;
color: #f4fc00;
text-transform: lowercase;
}
body.home .navbar .nav-wrap ul > li.learn > a:first-letter {
body.home .navbar .nav-wrap ul.nav > li.learn > a:first-letter {
text-transform: capitalize;
}
body.home .navbar .nav-wrap ul > li.log-in > a,
body.home .navbar .nav-wrap ul > li.about > a {
body.home .navbar .nav-wrap ul.nav > li.log-in > a,
body.home .navbar .nav-wrap ul.nav > li.about > a {
font-size: 18px;
color: #fff;
text-transform: lowercase;
@ -589,19 +719,19 @@ body.home .navbar .nav-wrap ul > li.about > a {
text-align: center;
line-height: 24px;
}
body.home .navbar .nav-wrap ul > li.claim > a {
body.home .navbar .nav-wrap ul.nav > li.claim > a {
text-align: center;
text-transform: capitalize;
margin-top: 5px;
}
body.home .navbar .nav-wrap ul > li.claim > a:before,
body.home .navbar .nav-wrap ul > li.claim > a:after {
body.home .navbar .nav-wrap ul.nav > li.claim > a:before,
body.home .navbar .nav-wrap ul.nav > li.claim > a:after {
content: '-';
}
body.home .navbar .nav-wrap ul > li.video {
body.home .navbar .nav-wrap ul.nav > li.video {
margin: 30px 0;
}
body.home .navbar .nav-wrap ul > li.video a {
body.home .navbar .nav-wrap ul.nav > li.video a {
color: #fff;
border-radius: 7px;
margin: auto;
@ -618,140 +748,207 @@ body.home .navbar .nav-wrap ul > li.video a {
background: linear-gradient(to bottom, #e82202 0%, #e5381d 44%, #e56854 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#e82202', endColorstr='#e56854', GradientType=0);
}
body.home .navbar .nav-wrap ul > li span {
body.home .navbar .nav-wrap ul.nav > li span {
color: #fff;
}
body.home .navbar .nav-wrap ul > li:nth-child(even) {
body.home .navbar .nav-wrap ul.nav > li:nth-child(even) {
border-left: none;
border-right: none;
}
body.home #main {
padding: 0 0 0 0;
display: none;
/* ------------------------------ */
/* Landing page : larger displays */
/* ------------------------------ */
@media screen and (min-width: 600px) {
body.home {
background-image: url('../img/background-chalkboard-green.jpg');
background-repeat: no-repeat;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale');
-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale')";
overflow: visible;
}
body.home .navbar .navbar-inner {
background-image: url('../img/spacedimg.jpg');
background-repeat: no-repeat;
background-position: top center;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
padding-top: 0px;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale');
-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale')";
}
body.home .navbar .navbar-inner > .container {
background-image: url('../img/home_drawing.png');
background-position: top center;
background-repeat: no-repeat;
}
body.home .container {
width: 960px;
}
body.home .navbar-static-top .container {
width: inherit;
}
body.home .secondary .upper {
padding-top: 15px;
position: relative;
}
body.home .secondary .upper > p.pull-left {
display: none;
}
body.home .secondary .upper > .pull-right {
float: left;
text-align: center;
width: 340px;
padding-top: 55px;
position: relative;
}
body.home .secondary .upper > .pull-right li:nth-child(even) {
margin: 12px;
}
body.home .secondary .lower {
border-top: none;
}
body.home .secondary .lower .pull-right li:first-child {
display: none;
}
body.home .secondary .lower .pull-right li:nth-child(2) {
border-left: none;
}
body.home .footer {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
body.home #rahm {
position: relative;
width: 210px;
padding-left: 100px;
padding-top: 15px;
background-image: url('../img/rahm.png');
background-repeat: no-repeat;
background-position: center left;
float: left;
-webkit-hyphens: none;
-moz-hyphens: none;
hyphens: none;
}
body.home #bubbles {
float: left;
background-image: url('../img/bubbles.png');
width: 310px;
height: 140px;
background-repeat: no-repeat;
background-position: 18px 0px;
margin-top: 15px;
}
body.home #bubbles span {
color: #000;
display: block;
width: 100px;
}
body.home #bubbles span.lt {
float: left;
margin-left: 34px;
margin-top: 12px;
}
body.home #bubbles span.lt a {
color: #3B5998;
}
body.home #bubbles span.rt {
margin-top: 52px;
margin-right: 7px;
float: right;
font-size: 24px;
line-height: 20px;
}
body.home .footer {
background-image: none;
}
body.home #bkg_fade {
position: fixed;
width: 100%;
height: 100%;
background-image: url('../img/65pb.png');
margin: 0 0 0 0;
padding: 0 0 0 0;
z-index: 3;
}
body.home #i_vid_wrap {
width: 640px;
height: 360px;
position: fixed;
top: 50%;
left: 50%;
margin-top: -204px;
margin-left: -348px;
z-index: 4;
background-color: #000;
padding: 28px;
border-radius: 7px;
}
body.home #cl_i_vid {
background-image: url('../img/close.png');
position: absolute;
top: -7px;
right: -7px;
display: block;
width: 25px;
height: 25px;
text-indent: -9000px;
}
}
body.home .navbar-static-top .container {
width: inherit;
}
body.home .secondary .upper {
padding-top: 15px;
position: relative;
}
body.home .secondary .upper > p.pull-left {
display: none;
}
body.home .secondary .upper > .pull-right {
float: left;
text-align: center;
width: 340px;
padding-top: 55px;
position: relative;
left: -7px;
}
body.home .secondary .upper > .pull-right li:nth-child(even) {
margin: 12px;
}
body.home .secondary .lower {
border-top: none;
}
body.home .secondary .lower .pull-right li:first-child {
display: none;
}
body.home .secondary .lower .pull-right li:nth-child(2) {
border-left: none;
}
body.home .footer {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
body.home #rahm {
position: relative;
width: 210px;
padding-left: 100px;
padding-top: 15px;
background-image: url('../img/rahm.png');
background-repeat: no-repeat;
background-position: center left;
float: left;
-webkit-hyphens: none;
-moz-hyphens: none;
hyphens: none;
}
body.home #bubbles {
float: left;
background-image: url('../img/bubbles.png');
width: 310px;
height: 140px;
background-repeat: no-repeat;
background-position: 18px 0px;
margin-top: 15px;
}
body.home #bubbles span {
color: #000;
display: block;
width: 100px;
}
body.home #bubbles span.lt {
float: left;
margin-left: 34px;
margin-top: 12px;
}
body.home #bubbles span.lt a {
color: #3B5998;
}
body.home #bubbles span.rt {
margin-top: 52px;
margin-right: 7px;
float: right;
font-size: 24px;
line-height: 20px;
}
body.home .footer {
background-image: none;
}
body.home #bkg_fade {
position: fixed;
width: 100%;
height: 100%;
background-image: url('../img/65pb.png');
margin: 0 0 0 0;
padding: 0 0 0 0;
z-index: 3;
}
body.home #i_vid_wrap {
width: 640px;
height: 360px;
position: fixed;
top: 50%;
left: 50%;
margin-top: -204px;
margin-left: -348px;
z-index: 4;
background-color: #000;
padding: 28px;
border-radius: 7px;
}
body.home #cl_i_vid {
background-image: url('../img/close.png');
position: absolute;
top: -7px;
right: -7px;
display: block;
width: 25px;
height: 25px;
text-indent: -9000px;
}
.filter {
color: #EEE;
}
.filter select {
margin-right: 10px;
width: 115px;
vertical-align: baseline;
}
.filter input {
vertical-align: baseline;
}
.filter .navbar-inner {
padding-left: 10px;
/* ------------------------------ */
/* Landing page : mobile -------- */
/* ------------------------------ */
@media only screen and (min-width: 320px) and (max-width: 599px) {
body.home {
background-image: url('../img/chalkboard_bg.jpg');
}
body.home .navbar {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
body.home .navbar div.navbar-inner {
padding-top: 0px;
background-image: none;
background: none;
border: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
body.home .navbar .nav-wrap ul.nav li {
display: block;
}
body.home .navbar .nav-wrap ul.nav li a {
padding: 10px 15px 10px;
}
body.home .footer {
background-image: none;
box-shadow: none;
}
body.home .footer .upper {
padding-top: 70px;
padding-bottom: 20px;
}
body.home .footer .upper p,
body.home .footer .upper div {
display: none;
}
body.home .footer .upper ul {
width: 90%;
display: block;
}
body.home .footer .upper ul li {
width: 32%;
}
body.home .footer .upper ul li a {
margin: 0 auto;
}
}

Двоичные данные
static/media/img/badge-default-320.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.6 KiB

Двоичные данные
static/media/img/banner314.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 17 KiB

Двоичные данные
static/media/img/csol_logo_320.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 20 KiB

Двоичные данные
static/media/img/csol_logo_480.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 35 KiB

Просмотреть файл

@ -1,6 +1,12 @@
$(document).ready(function(){
var mob = 0;
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) ) mob = 1
$('.show-tooltip').tooltip();
if($('body.home').length != 0) {
var vidLink = $('<a href="#">watch video</a>').click(function(){
if($('#i_vid').length == 0) {
var bkgFade = $('<div id="bkg_fade" style="display:none;"></div>');
@ -23,6 +29,11 @@ $(document).ready(function(){
/*landing page menu rearrange*/
$('<p id="rahm">This summer Mayor Rahm Emanuel is challenging all Chicago youth to participate in the Summer of Learning. School stops for the summer, but learning never should.</p>').prependTo('.footer .upper');
$('<div id="bubbles"><span class="lt">join the conversation on <a href="#">Facebook</a>.</span><span class="rt">share stories</span></div>').appendTo('.footer .upper');
if(mob) {
vidLink = '<a href="http://www.youtube.com/v/6WwpwtYNsNk">watch video</a>';
}
$('li.claim').after($('<li class="video"></li>').append(vidLink));
$('li.log-in').before($('.about'));

Просмотреть файл

@ -93,6 +93,7 @@ ol {
}
.navbar-inner {
background-image:url('../img/chalkboard_bg.jpg');
background-repeat:repeat;
}
form.navbar-inner {
padding:7px;
@ -114,8 +115,8 @@ ol {
box-shadow:0 0 8px 3px rgba(0, 0, 0, 0.25);
background:#fff;
text-align:center;
ul {
display:inline-block;
ul.nav {
display:inline-block;
margin:0 0 0 0;
& > li {
float:none;
@ -170,6 +171,7 @@ nav.pagination {
-webkit-box-shadow:inset 0px 10px 20px 0px rgba(0, 0, 0, 0.25);
-moz-box-shadow:inset 0px 10px 20px 0px rgba(0, 0, 0, 0.25);
box-shadow:inset 0px 10px 20px 0px rgba(0, 0, 0, 0.25);
background-repeat:repeat;
a {
color: #fff;
}
@ -261,12 +263,123 @@ nav.pagination {
}
&.secondary {
position:relative;
position:relative;
color:#fff;
overflow:hidden;
}
}
/*mobile*/
@media only screen
and (min-width : 320px)
and (max-width : 599px) {
.container p, .container h1, .container h2, .footer .upper p {
width:inherit;
}
#main {
padding-top:15px;
}
.form-search label, .form-inline label, .form-search .btn-group, .form-inline .btn-group {
display:block;
}
.filter label {
display:none;
}
.navbar .nav-wrap,
.container,
.navbar-static-top .container,
.navbar-fixed-top .container,
.navbar-fixed-bottom .container,
.navbar .brand {
width:95%;
margin:0 0 0 0;
padding:0 0 0 0;
margin-left:auto;
margin-right:auto;
}
.form-horizontal {
.control-label {
text-align:left;
float:none;
}
.controls {
margin-left:0px;
}
}
.span6 {
width:inherit;
}
/*mobile navbar*/
.navbar {
div.navbar-inner {
padding-left:0px;
padding-right:0px;
padding-top:10px;
padding-bottom:10px;
border:none;
}
.nav-wrap {
width:90%;
margin:0 auto;
ul.nav {
li {
display:block;
a {
padding: 5px 0;
}
}
li:nth-child(even),
& > .active > a,
& > .active > a:hover,
{
border-left:none;
border-right:none;
}
}
}
}
/*mobile footer*/
.footer {
.upper {
padding-top:15px;
p {
width:inherit;
background-image:none;
padding-left:0px;
}
ul.pull-right {
width:270px;
display:block;
float:none;
margin:0 auto;
li {
width:90px;
float:left;
a {
margin: 0 auto;
}
}
}
}
.lower {
border-top:none;
ul.pull-left {
display:none;
}
ul.pull-right {
float:none;
text-align:center;
li {
display:inline;
}
}
}
}
}
/*CSOL-site specific*/
.poster a,
.poster img {
display: block;
@ -591,43 +704,96 @@ input[type="password"].metered {
}
}
/*landing page specific*/
body.home {
background-image:url('../img/background-chalkboard-green.jpg');
background-repeat:no-repeat;
-webkit-background-size:cover;
-moz-background-size:cover;
-o-background-size:cover;
background-size:cover;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale');
-ms-filter:"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale')";
overflow:visible;
.container {
width:960px;
/* ------------------------------ */
/* Mobile : width > iphone ------ */
/* ------------------------------ */
@media only screen
and (min-width : 321px)
and (max-width : 940px ) {
.navbar .brand {
background-image: url('../img/csol_logo_480.png');
height:166px;
}
.navbar {
-webkit-box-shadow:0px 10px 20px 0px rgba(0, 0, 0, 0.25);
-moz-box-shadow:0px 10px 20px 0px rgba(0, 0, 0, 0.25);
box-shadow:0px 10px 20px 0px rgba(0, 0, 0, 0.25);
.navbar-inner {
background-image:url('../img/spacedimg.jpg');
background-repeat:no-repeat;
background-position:top center;
-webkit-background-size:cover;
-moz-background-size:cover;
-o-background-size:cover;
background-size:cover;
padding-top:0px;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale');
-ms-filter:"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale')";
overflow:visible;
max-height:515px;
}
/* ------------------------------ */
/* Mobile : width <= iphone ----- */
/* ------------------------------ */
@media only screen
and (max-width : 320px) {
.navbar .brand {
background-image: url('../img/csol_logo_320.png');
height:116px;
}
}
/* ------------------------------ */
/* CSOL-site SPECIFIC ----------- */
/* ------------------------------ */
/* ------------------------------ */
/* Mobile : width <= iphone ---- */
/* ------------------------------ */
@media only screen
and (max-width : 320px) {
.span3 {
width:150px;
}
ul.thumbnails li figure.thumbnail > a img {
width:140px;
}
ul.thumbnails {
li {
figure.thumbnail {
margin:0 0 0 0;
& > a img {
display:block;
width:140px;
height:140px;
background-image:url('../img/badge-default-320.png');
}
}
}
li:nth-child(2n+2) {
figure.thumbnail {
& > a img {
background-position:0 -140px;
}
}
}
li:nth-child(3n+3) {
figure.thumbnail {
& > a img {
background-position:-140px 0px;
}
}
}
li:nth-child(4n+4) {
figure.thumbnail {
& > a img {
background-position:-140px -140px;
}
}
}
}
}
/* ------------------------------ */
/* Landing page ---------------- */
/* ------------------------------ */
body.home {
#main {
padding:0 0 0 0;
display:none;
}
.navbar {
-webkit-box-shadow:0px 10px 20px 0px rgba(0, 0, 0, 0.25);
-moz-box-shadow:0px 10px 20px 0px rgba(0, 0, 0, 0.25);
box-shadow:0px 10px 20px 0px rgba(0, 0, 0, 0.25);
.navbar-inner {
overflow:visible;
max-height:515px;
& > .container {
background-image:url('../img/home_drawing.png');
background-position:top center;
background-repeat:no-repeat;
}
}
.nav {
& > li {
@ -640,7 +806,7 @@ body.home {
background-image:url('../img/csol_logo_sm.png');
position:absolute;
left:50%;
margin-left:-102px;
margin-left:-93px;
z-index:2;
top:50px;
width:189px;
@ -651,18 +817,17 @@ body.home {
width:inherit;
margin-bottom:0px;
height:600px;
width:310px;
width:314px;
margin:0 auto;
background:none;
background-image:url('../img/banner300.png');
background-image:url('../img/banner314.png');
background-position:top center;
background-repeat:no-repeat;
position:relative;
z-index:1;
box-shadow:none;
ul {
ul.nav {
padding-top:200px;
left:-6px;
& > li {
text-align:left;
display:block;
@ -723,145 +888,217 @@ body.home {
}
}
}
#main {
padding:0 0 0 0;
display:none;
}
}
.navbar-static-top {
.container {
width:inherit;
}
}
.secondary {
.upper {
padding-top:15px;
position:relative;
> p.pull-left {
display:none;
}
> .pull-right {
float:left;
text-align:center;
width:340px;
padding-top:55px;
position:relative;
left:-7px;
li:nth-child(even){
margin:12px;
}
}
}
.lower {
border-top:none;
.pull-right {
li:first-child {
display:none;
}
li:nth-child(2) {
border-left:none;
}
/* ------------------------------ */
/* Landing page : larger displays */
/* ------------------------------ */
@media screen and (min-width: 600px) {
body.home {
.navbar {
.navbar-inner {
background-image:url('../img/spacedimg.jpg');
background-repeat:no-repeat;
background-position:top center;
-webkit-background-size:cover;
-moz-background-size:cover;
-o-background-size:cover;
background-size:cover;
padding-top:0px;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale');
-ms-filter:"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/spacedimg.png', sizingMethod='scale')";
& > .container {
background-image:url('../img/home_drawing.png');
background-position:top center;
background-repeat:no-repeat;
}
}
}
background-image:url('../img/background-chalkboard-green.jpg');
background-repeat:no-repeat;
-webkit-background-size:cover;
-moz-background-size:cover;
-o-background-size:cover;
background-size:cover;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale');
-ms-filter:"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../img/background-chalkboard-green.jpg', sizingMethod='scale')";
overflow:visible;
.container {
width:960px;
}
.navbar-static-top {
.container {
width:inherit;
}
}
}
.footer {
-webkit-box-shadow:none;
-moz-box-shadow:none;
box-shadow:none;
}
#rahm {
position:relative;
width:210px;
padding-left:100px;
padding-top:15px;
background-image:url('../img/rahm.png');
background-repeat:no-repeat;
background-position:center left;
float:left;
-webkit-hyphens:none;
-moz-hyphens:none;
hyphens:none;
}
#bubbles {
float:left;
background-image:url('../img/bubbles.png');
width:310px;
height:140px;
background-repeat:no-repeat;
background-position:18px 0px;
margin-top:15px;
span {
color:#000;
display:block;
width:100px;
&.lt {
float:left;
margin-left:34px;
margin-top:12px;
a {
color:#3B5998;
}
}
&.rt {
margin-top:52px;
margin-right:7px;
float:right;
font-size:24px;
line-height:20px;
}
}
}
.footer {
background-image: none;
}
#bkg_fade {
position:fixed;
width:100%;
height:100%;
background-image:url('../img/65pb.png');
margin:0 0 0 0;
padding:0 0 0 0;
z-index:3;
}
#i_vid_wrap {
width:640px;
height:360px;
position:fixed;
top:50%;
left:50%;
margin-top:-204px;
margin-left:-348px;
z-index:4;
background-color:#000;
padding:28px;
border-radius:7px;
}
#cl_i_vid {
background-image:url('../img/close.png');
position:absolute;
top:-7px;
right:-7px;
display:block;
width:25px;
height:25px;
text-indent:-9000px;
}
}
.filter {
color: #EEE;
select {
margin-right: 10px;
width: 115px;
vertical-align: baseline;
}
input {
vertical-align: baseline;
}
.navbar-inner {
padding-left: 10px;
}
.secondary {
.upper {
padding-top:15px;
position:relative;
> p.pull-left {
display:none;
}
> .pull-right {
float:left;
text-align:center;
width:340px;
padding-top:55px;
position:relative;
li:nth-child(even){
margin:12px;
}
}
}
.lower {
border-top:none;
.pull-right {
li:first-child {
display:none;
}
li:nth-child(2) {
border-left:none;
}
}
}
}
.footer {
-webkit-box-shadow:none;
-moz-box-shadow:none;
box-shadow:none;
}
#rahm {
position:relative;
width:210px;
padding-left:100px;
padding-top:15px;
background-image:url('../img/rahm.png');
background-repeat:no-repeat;
background-position:center left;
float:left;
-webkit-hyphens:none;
-moz-hyphens:none;
hyphens:none;
}
#bubbles {
float:left;
background-image:url('../img/bubbles.png');
width:310px;
height:140px;
background-repeat:no-repeat;
background-position:18px 0px;
margin-top:15px;
span {
color:#000;
display:block;
width:100px;
&.lt {
float:left;
margin-left:34px;
margin-top:12px;
a {
color:#3B5998;
}
}
&.rt {
margin-top:52px;
margin-right:7px;
float:right;
font-size:24px;
line-height:20px;
}
}
}
.footer {
background-image: none;
}
#bkg_fade {
position:fixed;
width:100%;
height:100%;
background-image:url('../img/65pb.png');
margin:0 0 0 0;
padding:0 0 0 0;
z-index:3;
}
#i_vid_wrap {
width:640px;
height:360px;
position:fixed;
top:50%;
left:50%;
margin-top:-204px;
margin-left:-348px;
z-index:4;
background-color:#000;
padding:28px;
border-radius:7px;
}
#cl_i_vid {
background-image:url('../img/close.png');
position:absolute;
top:-7px;
right:-7px;
display:block;
width:25px;
height:25px;
text-indent:-9000px;
}
}
}
/* ------------------------------ */
/* Landing page : mobile -------- */
/* ------------------------------ */
@media only screen
and (min-width : 320px)
and (max-width : 599px) {
body.home {
background-image:url('../img/chalkboard_bg.jpg');
.navbar {
-webkit-box-shadow:none;
-moz-box-shadow:none;
box-shadow:none;
div.navbar-inner {
padding-top:0px;
background-image:none;
background:none;
border:none;
-webkit-box-shadow:none;
-moz-box-shadow:none;
box-shadow:none;
}
.nav-wrap {
ul.nav {
li {
display:block;
a {
padding: 10px 15px 10px;
}
}
}
}
}
.footer {
background-image:none;
box-shadow:none;
.upper {
padding-top:70px;
padding-bottom:20px;
p, div {
display:none;
}
ul {
width:90%;
display:block;
li {
width:32%;
a {
margin: 0 auto;
}
}
}
}
}
}
}

Просмотреть файл

@ -8,7 +8,7 @@ const FAKE_S3_DIR = path.join(__dirname, 's3-fake-storage-test');
function removeFakeS3Dir(t) {
var root = FAKE_S3_DIR;
var pathParts = ['', 'evidence', 'lol.txt'];
var pathParts = ['', 'evidence'];
for (var i = pathParts.length; i > 0; i--) {
var relpath = pathParts.slice(0, i).join(path.sep);
@ -27,7 +27,8 @@ test('FakeS3 works', function(t) {
removeFakeS3Dir();
s3.putBuffer(new Buffer('hai2u', 'binary'), '/evidence/lol.txt', {
'Content-Type': 'text/plain'
}, function() {
}, function(err) {
t.equal(err, null);
s3.get('/evidence/lol.txt').on('response', function(proxy) {
var chunks = [];
proxy.on('data', function(chunk) {
@ -36,9 +37,12 @@ test('FakeS3 works', function(t) {
proxy.on('end', function() {
var buf = Buffer.concat(chunks);
t.equal(buf.toString('ascii'), 'hai2u');
removeFakeS3Dir(t);
t.end();
s3.deleteFile('/evidence/lol.txt', function(err) {
t.equal(err, null);
removeFakeS3Dir(t);
t.end();
});
});
});
}).end();
});
});

Просмотреть файл

@ -1,2 +1,2 @@
{% extends 'layout.html' %}
{% set navItem = 'programs' %}
{% set navItem = 'badges' %}

Просмотреть файл

@ -1,5 +1,6 @@
{% extends 'filter.html' %}
{% set pageTitle = 'Badges' %}
{% set navItem = 'badges' %}
{% block content %}
<p>The City of Chicago Summer of Learning badges recognize the cool things you do over the summer. When you learn something new, you can earn a badge. As you earn more badges you can do more cool things. When you go back to school in the fall, you can show off all the badges you earned to your teachers, parents, potential employers and friends!</p>

Просмотреть файл

@ -14,7 +14,7 @@
<p class="text-right">
{% if badge.activityType == 'offline' %}
<a href="{{ badge.url }}/claim" class="btn">Claim this badge</a>
<a href="/claim" class="btn">Claim this badge</a>
{% else %}
<a href="{{ badge.url }}/apply" class="btn">Apply</a>
{% endif %}
@ -22,56 +22,23 @@
</div>
</div>
<h3><a href="/badges">Similar or Related Badges</a></h3>
<p>If you're interested in this badge, you might be interested in these too. These are badges on similar STEAM topics or ones that can take you to the next level.</p>
<ul class="thumbnails">
<li class="span3">
<figure class="thumbnail">
<a href="/badges/ae784f"><img src="/media/images/badge.png"></a>
<figcaption class="caption">
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
</li>
<li class="span3">
<figure class="thumbnail">
<a href="/badges/ae784f"><img src="/media/images/badge.png"></a>
<figcaption class="caption">
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
</li>
<li class="span3">
<figure class="thumbnail">
<a href="/badges/ae784f"><img src="/media/images/badge.png"></a>
<figcaption class="caption">
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
</li>
<li class="span3">
<figure class="thumbnail">
<a href="/badges/ae784f"><img src="/media/images/badge.png"></a>
<figcaption class="caption">
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a class="btn show-tooltip" title="Save this badge to your favorites" href="/badges/ae784f/favorite"><i class="icon-heart"></i></a>
</p>
</figcaption>
</figure>
</li>
</ul>
{% if relatedBadges and relatedBadges|length > 0 %}
<h3><a href="/badges">Similar or Related Badges</a></h3>
<p>If you're interested in this badge, you might be interested in these too. These are badges on similar STEAM topics or ones that can take you to the next level.</p>
<ul class="thumbnails">
{% for relatedBadge in relatedBadges %}
<li class="span3">
<figure class="thumbnail">
<a href=""><img src="{{relatedBadge.image}}"></a>
<figcaption class="caption">
<p>{{relatedBadge.description}}</p>
<p class="text-right">
<a href="" class="btn">Details</a>
</p>
</figcaption>
</figure>
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

Просмотреть файл

@ -24,7 +24,8 @@
</div>
</div>
{% else %}
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>To claim the badge you've earned, enter the "claim code" written on the paper badge you received from your teacher, counselor or mentor.</p>
<p>Then hit the "Claim This Badge" button on your screen. Once you do this, the badge will be added to your dashboard and you can throw the piece of paper away (better yet, recycle it!).</p>
<h3>Already have a claim code?</h3>
<form method="get" class="form-horizontal well span6 offset3">
@ -37,7 +38,7 @@
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn">Claim this badge</button>
<button type="submit" class="btn">Claim this Badge</button>
</div>
</div>
</fieldset>

Просмотреть файл

@ -8,7 +8,7 @@
<img src="{{program.imageUrl}}">
</div>
<div class="span8">
<p><strong>From <a href="/orgs/some-organization">Some Organization</a>.</strong></p>
<p><strong>From <a href="/orgs/some-organization">{{program.issuer.name}}</a>.</strong></p>
{% if program.description %}
<p>{{program.description}}</p>
{% endif %}
@ -19,27 +19,18 @@
</div>
<h3><a href="/badges">Badges</a></h3>
<ul class="thumbnails">
{% for shortname,badge in program.earnableBadges %}
<li class="span3">
<figure class="thumbnail">
<a href="/badges/ae784f"><img src="/media/images/badge.png"></a>
<a href="/earn/{{shortname}}"><img src="{{badge.image}}"></a>
<figcaption class="caption">
<p>Badge blah in voluptate velit...</p>
<p>{{badge.description}}</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
</p>
</figcaption>
</figure>
</li>
<li class="span3">
<figure class="thumbnail">
<a href="/badges/ae784f"><img src="/media/images/badge.png"></a>
<figcaption class="caption">
<p>Badge blah in voluptate velit...</p>
<p class="text-right">
<a href="/badges/ae784f" class="btn">Details</a>
<a href="/earn/{{shortname}}" class="btn">Details</a>
</p>
</figcaption>
</figure>
</li>
{% endfor %}
</ul>
{% endblock %}