diff --git a/.eslintrc b/.eslintrc
index e84581005..6f9c2e96d 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,11 +1,15 @@
{
"rules": {
"curly": [2, "multi-line"],
+ "eol-last": [2],
"indent": [2, 4],
- "quotes": [2, "single"],
"linebreak-style": [2, "unix"],
"max-len": [2, 120, 4],
+ "no-trailing-spaces": [2, { "skipBlankLines": true }],
+ "no-unused-vars": [2, {"args": "after-used", "varsIgnorePattern": "^_"}],
+ "quotes": [2, "single"],
"semi": [2, "always"],
+ "space-before-function-paren": [2, "always"],
"strict": [2, "never"]
},
"env": {
diff --git a/.gitignore b/.gitignore
index a7bef3917..5751771c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,8 @@ npm-*
# Build
/build
+
+# Elastic Beanstalk Files
+.elasticbeanstalk/*
+!.elasticbeanstalk/*.cfg.yml
+!.elasticbeanstalk/*.global.yml
diff --git a/Makefile b/Makefile
index 1e200a439..2f1255b66 100644
--- a/Makefile
+++ b/Makefile
@@ -22,6 +22,25 @@ webpack:
# ------------------------------------
+watch:
+ $(WATCH) "make clean && make static" ./static &
+ $(WEBPACK) -d --watch &
+ wait
+
+stop:
+ pkill -f "node $(WEBPACK) -d --watch"
+ pkill -f "node $(WATCH) make clean && make static ./static"
+
+start:
+ $(NODE) ./server/index.js
+
+# ------------------------------------
+
+nginx_conf:
+ node server/nginx.js
+
+# ------------------------------------
+
test:
@make lint
@@ -35,14 +54,4 @@ lint:
# ------------------------------------
-watch:
- $(WATCH) "make clean && make static" ./static &
- $(WEBPACK) -d --watch &
- wait
-
-start:
- $(NODE) ./server/index.js
-
-# ------------------------------------
-
-.PHONY: build clean static webpack test lint watch start
+.PHONY: build clean static webpack watch stop start nginx_conf test lint
diff --git a/package.json b/package.json
index 715caa635..f0ad612da 100644
--- a/package.json
+++ b/package.json
@@ -2,12 +2,13 @@
"name": "www",
"version": "1.0.0",
"description": "Standalone WWW client for Scratch",
- "main": "index.js",
"scripts": {
"start": "make start",
"test": "make test",
"watch": "make watch",
- "build": "make build"
+ "stop-watch": "make stop-watch",
+ "build": "make build",
+ "prestart": "make build"
},
"repository": {
"type": "git",
@@ -28,16 +29,24 @@
},
"devDependencies": {
"autoprefixer-loader": "2.1.0",
+ "classnames": "2.1.3",
"css-loader": "0.17.0",
"eslint": "1.3.1",
"eslint-plugin-react": "3.3.1",
+ "file-loader": "0.8.4",
"json-loader": "0.5.2",
"jsx-loader": "0.13.2",
"node-sass": "3.3.2",
"react": "0.13.3",
+ "react-modal": "0.3.0",
+ "react-onclickoutside": "0.3.1",
+ "react-slick": "0.7.0",
+ "routes-to-nginx-conf": "0.0.4",
"sass-loader": "2.0.1",
+ "slick-carousel": "1.5.8",
"style-loader": "0.12.3",
"tape": "4.2.0",
+ "url-loader": "0.5.6",
"watch": "0.16.0",
"webpack": "1.12.0",
"xhr": "2.0.4"
diff --git a/server/index.js b/server/index.js
index b12576a5d..24ef86e42 100644
--- a/server/index.js
+++ b/server/index.js
@@ -1,6 +1,6 @@
var compression = require('compression');
var express = require('express');
-var path = require('path');
+var _path = require('path');
var handler = require('./handler');
var log = require('./log');
@@ -11,15 +11,15 @@ var app = express();
app.disable('x-powered-by');
app.use(log());
app.use(compression());
-app.use(express.static(path.resolve(__dirname, '../build'), {
- lastModified: true,
- maxAge: '1y'
-}));
// Bind routes
for (var item in routes) {
var route = routes[item];
- app.get(route.pattern, handler(route));
+ if ( route.static ) {
+ app.use( express.static( eval( route.resolve ), route.attributes ) );
+ } else {
+ app.get(route.pattern, handler(route));
+ }
}
// Start listening
diff --git a/server/nginx.js b/server/nginx.js
new file mode 100644
index 000000000..2ab89a172
--- /dev/null
+++ b/server/nginx.js
@@ -0,0 +1,5 @@
+var routes = require('./routes.json');
+var nginx_conf = require('routes-to-nginx-conf');
+
+nginx_conf.generate_nginx_conf( routes, function ( v ) { process.stdout.write(v); } );
+
diff --git a/server/routes.json b/server/routes.json
index 1e07b5438..73030da8d 100644
--- a/server/routes.json
+++ b/server/routes.json
@@ -1,15 +1,24 @@
[
{
"pattern": "/",
- "view": "splash"
+ "view": "splash",
+ "static": false
},
{
"pattern": "/about",
"view": "about",
- "title": "About"
+ "title": "About",
+ "static": false
},
{
"pattern": "/components",
- "view": "components"
+ "view": "components",
+ "static": false
+ },
+ {
+ "static": true,
+ "resolve": "_path.resolve(__dirname, '../build')",
+ "attributes": { "lastModified": true, "maxAge": "1y" },
+ "_todo": " TODO: Define a specification for how each entry is used/expected to look like, given the nginx conf generator's needs and stand-alone run-time needs. An outline of this so far: static requires resolve/attributes but could use pattern too. ..."
}
]
diff --git a/src/components/carousel/carousel.json b/src/components/carousel/carousel.json
new file mode 100644
index 000000000..4100b69f3
--- /dev/null
+++ b/src/components/carousel/carousel.json
@@ -0,0 +1,49 @@
+[
+ {
+ "id": 1,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 2,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 3,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 4,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 5,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 6,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ }
+]
diff --git a/src/components/carousel/carousel.jsx b/src/components/carousel/carousel.jsx
new file mode 100644
index 000000000..089ad04d7
--- /dev/null
+++ b/src/components/carousel/carousel.jsx
@@ -0,0 +1,42 @@
+var React = require('react');
+var Slider = require('react-slick');
+var Thumbnail = require('../thumbnail/thumbnail.jsx');
+
+require('slick-carousel/slick/slick.scss');
+require('slick-carousel/slick/slick-theme.scss');
+require('./carousel.scss');
+
+module.exports = React.createClass({
+ propTypes: {
+ items: React.PropTypes.array
+ },
+ getDefaultProps: function () {
+ return {
+ items: require('./carousel.json'),
+ settings: {
+ arrows: true,
+ dots: false,
+ infinite: false,
+ lazyLoad: true,
+ slidesToShow: 5,
+ slidesToScroll: 5,
+ variableWidth: true
+ }
+ };
+ },
+ render: function () {
+ return (
+
+ {this.props.items.map(function (item) {
+ return (
+
+ );
+ })}
+
+ );
+ }
+});
diff --git a/src/components/carousel/carousel.scss b/src/components/carousel/carousel.scss
new file mode 100644
index 000000000..0d8c78723
--- /dev/null
+++ b/src/components/carousel/carousel.scss
@@ -0,0 +1,47 @@
+.carousel {
+ $icon-size: 40px;
+ $button-offset: $icon-size + 5px;
+ $box-content-offset: 20px;
+
+ padding: 0 $button-offset;
+
+ .box-content & {
+ padding: 0 $button-offset - 20px;
+ }
+
+ .slick-next, .slick-prev {
+ width: $icon-size;
+ height: $icon-size;
+ margin-top: -$icon-size/2;
+
+ &:before {
+ font-size: $icon-size;
+ color: #ddd;
+ }
+
+ &.slick-disabled:before{
+ opacity: 1;
+ }
+ }
+
+ .slick-prev {
+ left: 0;
+
+ .box-content & {
+ left: -$box-content-offset;
+ }
+ }
+
+ .slick-next {
+ right: 0;
+
+ .box-content & {
+ right: -$box-content-offset;
+ }
+ }
+
+ .slick-slide {
+ padding-right: 30px;
+ }
+
+}
diff --git a/src/components/intro/intro.jsx b/src/components/intro/intro.jsx
new file mode 100644
index 000000000..01993933d
--- /dev/null
+++ b/src/components/intro/intro.jsx
@@ -0,0 +1,93 @@
+var React = require('react');
+var Modal = require('../modal/modal.jsx');
+
+require('./intro.scss');
+
+Modal.setAppElement(document.getElementById('view'));
+
+module.exports = React.createClass({
+ propTypes: {
+ projectCount: React.PropTypes.number
+ },
+ getDefaultProps: function () {
+ return {
+ projectCount: 10569070
+ };
+ },
+ getInitialState: function () {
+ return {
+ videoOpen: false
+ };
+ },
+ showVideo: function () {
+ this.setState({videoOpen: true});
+ },
+ closeVideo: function () {
+ this.setState({videoOpen: false});
+ },
+ render: function () {
+ return (
+
+
+
+ Create stories, games, and animations
+ Share with others around the world
+
+
+
+ A creative learning community with
+ {this.props.projectCount.toLocaleString()}
+ projects shared
+
+
+
+
+
+
![](//cdn.scratch.mit.edu/scratchr2/static/images/hp-video-screenshot.png)
+
+
+
+
+
+ );
+ }
+});
diff --git a/src/components/intro/intro.scss b/src/components/intro/intro.scss
new file mode 100644
index 000000000..594818be9
--- /dev/null
+++ b/src/components/intro/intro.scss
@@ -0,0 +1,202 @@
+.intro {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ justify-content: space-between;
+ align-content: flex-start;
+ margin-top: 20px;
+ margin-bottom: 20px;
+
+ .content {
+ display: inline-block;
+ width: calc(66% - 20px);
+
+ h1 {
+ color: #F9A739;
+ font-weight: 400;
+ }
+ }
+
+ .sprites {
+ position: relative;
+ clear: both;
+ overflow: hidden;
+ &:after {
+ display: block;
+ visibility: hidden;
+ content: " ";
+ clear: both;
+ height: 0;
+ }
+ }
+
+ .sprite {
+ position: relative;
+ float: left;
+ width: 193px;
+ height: 136px;
+ overflow: hidden;
+
+ .costume, .circle, .text {
+ position: absolute;
+ }
+
+ .costume {
+ z-index: 2;
+ left: 0;
+ }
+
+ .costume-2 {
+ display: none;
+ }
+
+ &:hover .costume-1 {
+ display: none;
+ }
+
+ &:hover .costume-2 {
+ display: block;
+ }
+
+ .circle {
+ z-index: 0;
+ border-radius: 50%;
+ display: block;
+ width: 112px;
+ height: 112px;
+ box-shadow: 0px 0px 5px #fff;
+ top: 15px;
+ left: 43px;
+ }
+
+ $text-bg-color: #F1F3F4;
+ .text {
+ z-index: 1;
+ font-size: 12px;
+ font-weight: 700;
+ left: 35px;
+ padding-left: 40px;
+ padding-right: 10px;
+ white-space: nowrap;
+ background-color: $text-bg-color;
+ border: 2px solid $text-bg-color;
+ }
+
+ .subtext {
+ background-color: transparent;
+ font-weight: 400;
+ font-size: 12px;
+ text-shadow: none;
+ border: none;
+ }
+
+ $sprite-1-bgcolor: #9C0;
+ $sprite-2-bgcolor: #C2479D;
+ $sprite-3-bgcolor: #199ED7;
+ &.sprite-1 .circle { background-color: $sprite-1-bgcolor; }
+ &.sprite-2 .circle { background-color: $sprite-2-bgcolor; }
+ &.sprite-3 .circle { background-color: $sprite-3-bgcolor; }
+ &:hover.sprite-1 .circle { box-shadow: 0 0 10px 2px $sprite-1-bgcolor; }
+ &:hover.sprite-2 .circle { box-shadow: 0 0 10px 2px $sprite-2-bgcolor; }
+ &:hover.sprite-3 .circle { box-shadow: 0 0 10px 2px $sprite-3-bgcolor; }
+ &.sprite-1 .text { color: $sprite-1-bgcolor; top: 60px; left: 50px; }
+ &.sprite-2 .text { color: $sprite-2-bgcolor; top: 77px; left: 50px; }
+ &.sprite-3 .text { color: $sprite-3-bgcolor; top: 37px; left: 45px; }
+ &.sprite-3 .subtext {
+ top: 63px;
+ left: 60px;
+ color: #fff;
+ }
+ }
+
+ .description {
+ font-size: 17px;
+ margin-top: 10px;
+ }
+
+ .project-count {
+ color: hsl(318, 50%, 52%);
+ font-weight: 700;
+ font-size: 18px;
+ }
+
+ .links {
+ font-size: 12px;
+ margin-top: 20px;
+
+ a {
+ border-right: 1px solid #000;
+ padding: 0 5px;
+
+ &:last-child { border-right: 0; }
+ &:first-child { padding-left: 0; }
+ }
+ }
+
+ .video {
+ display: inline-block;
+ height: 208px;
+ width: 34%;
+ position: relative;
+ padding: 10px;
+ border: 1px solid #eee;
+ border-radius: 5px;
+ background-color: #f7f7f7;
+ text-align: center;
+ box-shadow: 0 2px 3px;
+ }
+
+ .play-button {
+ border-radius: 20px;
+ display: block;
+ width: 70px;
+ height: 50px;
+ left: calc(50% - 35px);
+ top: calc(50% - 25px);
+ background-color: #666;
+ border: 5px solid #ccc;
+ opacity: 0.8;
+
+ &, &:after {
+ position: absolute;
+ cursor: pointer;
+ margin: 0;
+ padding: 0;
+ }
+
+ &:after {
+ left: 28px;
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ pointer-events: none;
+ border-color: rgba(255, 255, 255, 0);
+ border-left-color: #fff;
+ border-width: 18px;
+ top: 37px;
+ margin-top: -30px;
+ }
+ }
+
+}
+.video-modal {
+ $video-width: 570px;
+ $video-height: 357px;
+ $padding: 15px;
+ width: $video-width;
+ height: $video-height;
+ padding: $padding;
+ top: 50%;
+ bottom: auto;
+ left: 50%;
+ right: auto;
+ margin-left: -($video-width + $padding * 2)/2;
+ margin-top: -($video-height + $padding * 2)/2;
+
+ iframe {
+ width: $video-width;
+ height: $video-height;
+ border: 0;
+ }
+}
diff --git a/src/components/login/login.jsx b/src/components/login/login.jsx
new file mode 100644
index 000000000..48551d3b7
--- /dev/null
+++ b/src/components/login/login.jsx
@@ -0,0 +1,27 @@
+var React = require('react');
+
+require('./login.scss');
+
+module.exports = React.createClass({
+ propTypes: {
+ onLogIn: React.PropTypes.func
+ },
+ handleSubmit: function (event) {
+ event.preventDefault();
+ this.props.onLogIn();
+ },
+ render: function () {
+ return (
+
+ );
+ }
+});
diff --git a/src/components/login/login.scss b/src/components/login/login.scss
new file mode 100644
index 000000000..f0c2342ae
--- /dev/null
+++ b/src/components/login/login.scss
@@ -0,0 +1,7 @@
+.login {
+ padding: 14px 9px;
+
+ .submit-button {
+ margin-right: 3px;
+ }
+}
\ No newline at end of file
diff --git a/src/components/modal/modal.jsx b/src/components/modal/modal.jsx
new file mode 100644
index 000000000..a4af8d59b
--- /dev/null
+++ b/src/components/modal/modal.jsx
@@ -0,0 +1,22 @@
+var React = require('react');
+var Modal = require('react-modal');
+
+require('./modal.scss');
+
+
+module.exports = React.createClass({
+ statics: {
+ setAppElement: Modal.setAppElement
+ },
+ requestClose: function () {
+ return this.refs.modal.portal.requestClose();
+ },
+ render: function () {
+ return (
+
+
+ {this.props.children}
+
+ );
+ }
+});
diff --git a/src/components/modal/modal.scss b/src/components/modal/modal.scss
new file mode 100644
index 000000000..7f1bfa4b5
--- /dev/null
+++ b/src/components/modal/modal.scss
@@ -0,0 +1,50 @@
+/* Copied from the un-styleable react-modal */
+
+.ReactModal__Overlay {
+ background-color: rgba(0, 0, 0, 0.75);
+ z-index: 100;
+}
+.ReactModal__Content {
+ position: absolute;
+ top: 40px;
+ left: 40px;
+ right: 40px;
+ bottom: 40px;
+ background: #fff;
+ overflow: visible;
+ -webkit-overflow-scrolling: touch;
+ border-radius: 6px;
+ outline: none;
+ padding: 20px;
+}
+@media (max-width: 768px) {
+ .ReactModal__Content {
+ top: 10px;
+ left: 10px;
+ right: 10px;
+ bottom: 10px;
+ padding: 10px;
+ }
+}
+
+.modal-close {
+ $modal-close-size: 20px;
+ position: absolute;
+ right: 0;
+ top: 0;
+ border-radius: $modal-close-size/2;
+ border: 2px solid #ddd;
+ background-color: #666;
+ color: #fff;
+ width: $modal-close-size;
+ height: $modal-close-size;
+ margin-top: -$modal-close-size/2;
+ margin-right: -$modal-close-size/2;
+ text-align: center;
+ line-height: $modal-close-size;
+ font-size: $modal-close-size;
+ cursor: pointer;
+ &:before {
+ content: "x";
+ }
+}
diff --git a/src/components/navigation/_colors.scss b/src/components/navigation/_colors.scss
new file mode 100644
index 000000000..4f6923b5d
--- /dev/null
+++ b/src/components/navigation/_colors.scss
@@ -0,0 +1,3 @@
+$base-background-color: #0f8bc0;
+$active-background-color: rgb(1, 96, 135);
+$border-color: rgb(20, 154, 203);
diff --git a/src/components/navigation/dropdown.jsx b/src/components/navigation/dropdown.jsx
new file mode 100644
index 000000000..168530c15
--- /dev/null
+++ b/src/components/navigation/dropdown.jsx
@@ -0,0 +1,37 @@
+var React = require('react');
+var classNames = require('classnames');
+
+require('./dropdown.scss');
+
+module.exports = React.createClass({
+ mixins: [
+ require('react-onclickoutside')
+ ],
+ propTypes: {
+ onRequestClose: React.PropTypes.func,
+ isOpen: React.PropTypes.bool
+ },
+ getDefaultProps: function () {
+ return {
+ as: 'div',
+ isOpen: false
+ };
+ },
+ handleClickOutside: function () {
+ if (this.props.isOpen) {
+ this.props.onRequestClose();
+ }
+ },
+ render: function () {
+ var classes = classNames(
+ 'dropdown',
+ this.props.className,
+ {open: this.props.isOpen}
+ );
+ return (
+
+ {this.props.children}
+
+ );
+ }
+});
diff --git a/src/components/navigation/dropdown.scss b/src/components/navigation/dropdown.scss
new file mode 100644
index 000000000..87bfe75f7
--- /dev/null
+++ b/src/components/navigation/dropdown.scss
@@ -0,0 +1,74 @@
+@import 'colors';
+
+.dropdown {
+ position: absolute;
+ right: 0;
+ min-width: 160px;
+ max-width: 220px;
+ background-color: $base-background-color;
+ overflow: hidden;
+ border-radius: 0px 0px 4px 4px;
+ box-shadow: inset 0 1px 1px rgba(100,100,100,.25),0 1px 1px rgba(0,0,0,.25);
+ color: white;
+ font-weight: normal;
+ font-size: 0.8125rem;
+
+ display: none;
+ &.open {
+ display: block;
+ }
+
+ a {
+ color: white;
+ }
+
+ input {
+ // 100% minus border and padding
+ width: calc(100% - 2px - 8px);
+ margin-bottom: 9px;
+ }
+
+ label {
+ display: block;
+ margin-bottom: 5px;
+ }
+
+ > li {
+ display: block;
+ line-height: 30px;
+
+ &.divider {
+ border-top: 1px solid #149acb;
+ margin-top: 10px;
+ }
+
+ a {
+ display: block;
+ padding: 0 10px;
+
+ &:hover {
+ background-color: $active-background-color;
+ text-decoration: none;
+ }
+ }
+ }
+
+ &.with-arrow {
+ $arrow-border-width: 11px;
+ overflow: visible;
+ margin-top: $arrow-border-width;
+ border-radius: 4px;
+ &:before {
+ position: absolute;
+ display: block;
+ right: 10%;
+ top: -$arrow-border-width;
+ left: auto;
+ border-color: transparent;
+ border-bottom-color: $base-background-color;
+ border-style: solid;
+ border-width: 0 $arrow-border-width $arrow-border-width $arrow-border-width;
+ content: " ";
+ }
+ }
+}
diff --git a/src/components/navigation/navigation.jsx b/src/components/navigation/navigation.jsx
index 57bfc6b55..b9a0f6759 100644
--- a/src/components/navigation/navigation.jsx
+++ b/src/components/navigation/navigation.jsx
@@ -1,11 +1,48 @@
var React = require('react');
+var classNames = require('classnames');
+var Login = require('../login/login.jsx');
+var Dropdown = require('./dropdown.jsx');
require('./navigation.scss');
module.exports = React.createClass({
+ getInitialState: function () {
+ return {
+ 'loginOpen': false,
+ 'loggedIn': false,
+ 'loggedInUser': {
+ 'username': 'raimondious',
+ 'thumbnail': '//cdn2.scratch.mit.edu/get_image/user/2584924_32x32.png'
+ },
+ 'accountNavOpen': false
+ };
+ },
+ handleLoginClick: function (e) {
+ e.preventDefault();
+ this.setState({'loginOpen': true});
+ },
+ closeLogin: function () {
+ this.setState({'loginOpen': false});
+ },
+ handleLogIn: function () {
+ this.setState({'loggedIn': true});
+ },
+ handleLogOut: function () {
+ this.setState({'loggedIn': false});
+ },
+ handleClickAccountNav: function () {
+ this.setState({'accountNavOpen': true});
+ },
+ closeAccountNav: function () {
+ this.setState({'accountNavOpen': false});
+ },
render: function () {
+ var classes = classNames({
+ 'inner': true,
+ 'logged-in': this.state.loggedIn
+ });
return (
-
+
);
diff --git a/src/components/navigation/navigation.scss b/src/components/navigation/navigation.scss
index f351dee23..604b812f3 100644
--- a/src/components/navigation/navigation.scss
+++ b/src/components/navigation/navigation.scss
@@ -1,15 +1,18 @@
+@import 'colors';
+
#navigation {
position: fixed;
+ z-index: 10;
display: block;
top: 0;
left: 0;
width: 100%;
- background-color: #0f8bc0;
+ background-color: $base-background-color;
/* NOTE: Height should match offset settings in main.scss file */
height: 35px;
- ul {
+ .inner > ul {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
@@ -20,12 +23,13 @@
padding: 0;
list-style: none;
- li {
+ > li {
display: inline-block;
align-self: flex-start;
float: left;
height: 100%;
- border-left: 1px solid rgb(20, 154, 203);
+ border-left: 1px solid $border-color;
+ position: relative;
}
.logo {
@@ -48,7 +52,7 @@
}
.link {
- a {
+ > a {
display: block;
height: 28px;
padding: 7px 15px 0 15px;
@@ -59,8 +63,8 @@
white-space: nowrap;
}
- a:hover {
- background-color: rgb(1, 96, 135);
+ > a:hover {
+ background-color: $active-background-color;
}
}
@@ -74,7 +78,7 @@
input {
display: inline-block;
- height: 20px;
+ height: 14px;
outline: none;
border: none;
}
@@ -85,7 +89,7 @@
height: 22px;
background-color: white;
- background-image: url(/images/nav-search-glass.png);
+ background-image: url('/images/nav-search-glass.png');
background-repeat: no-repeat;
background-position: center center;
border-right: 1px solid #efefef;
@@ -110,13 +114,77 @@
margin-left: auto;
font-weight: bold;
- a:hover {
- background-color: #f79231;
- }
-
&:last-child {
border-right: 1px solid rgb(20, 154, 203);
}
}
+
+ .join > a:hover {
+ background-color: #f79231;
+ }
+
+ .messages, .mystuff {
+ > a {
+ background-repeat: no-repeat;
+ background-position: center center;
+ padding-left: 10px;
+ padding-right: 10px;
+ text-indent: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+ }
+
+ .messages {
+ > a {
+ background-image: url('/images/nav-notifications.png');
+ width: 22px;
+ }
+ }
+
+ .mystuff {
+ > a {
+ background-image: url('/images/mystuff.png');
+ width: 25px;
+ }
+ }
+
+ .login-dropdown {
+ width: 200px;
+ }
+
+ .account-nav {
+ > a {
+ font-weight: normal;
+ font-size: 0.8125rem;
+
+ img {
+ width: 24px;
+ height: 24px;
+ margin-right: 5px;
+ vertical-align: middle;
+ }
+
+ &:after {
+ $caret-border-width: 4px;
+ margin-left: $caret-border-width;
+ border: $caret-border-width solid transparent;
+ border-bottom-width: 0;
+ border-top-color: white;
+ content: " ";
+ opacity: 0.5;
+ vertical-align: middle;
+ width: 0;
+ height: 0;
+ display: inline-block;
+ }
+ }
+
+ .dropdown {
+ width: 100%;
+ padding: 0;
+ padding-top: 5px;
+ }
+ }
}
}
diff --git a/src/components/news/news.jsx b/src/components/news/news.jsx
index 71316a1ce..d07145e69 100644
--- a/src/components/news/news.jsx
+++ b/src/components/news/news.jsx
@@ -15,9 +15,9 @@ module.exports = React.createClass({
},
render: function () {
return (
-
diff --git a/src/components/thumbnail/thumbnail.jsx b/src/components/thumbnail/thumbnail.jsx
new file mode 100644
index 000000000..d24ab679f
--- /dev/null
+++ b/src/components/thumbnail/thumbnail.jsx
@@ -0,0 +1,28 @@
+var React = require('react');
+
+require('./thumbnail.scss');
+
+module.exports = React.createClass({
+ propTypes: {
+ src: React.PropTypes.string
+ },
+ getDefaultProps: function () {
+ return {
+ href: '/projects/1000/',
+ title: 'Example Project',
+ src: 'http://www.lorempixel.com/144/108/',
+ extra: 'by raimondious'
+ };
+ },
+ render: function () {
+ return (
+
+ );
+ }
+});
diff --git a/src/components/thumbnail/thumbnail.scss b/src/components/thumbnail/thumbnail.scss
new file mode 100644
index 000000000..64c1cf75e
--- /dev/null
+++ b/src/components/thumbnail/thumbnail.scss
@@ -0,0 +1,33 @@
+.thumbnail {
+ .thumbnail-image,
+ .thumbnail-title,
+ .thumbnail-extra {
+ display: block;
+ }
+
+ .thumbnail-image img {
+ margin-bottom: 2px;
+ border: 1px solid #ddd;
+ }
+
+ .thumbnail-title,
+ .thumbnail-extra {
+ line-height: normal;
+ word-wrap: break-word;
+ }
+
+ .thumbnail-title {
+ margin-bottom: 1px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ font-weight: 800;
+ font-size: .9230em;
+ }
+
+ .thumbnail-extra {
+ font-size: .8462em;
+ color: #666;
+ }
+
+}
diff --git a/src/main.scss b/src/main.scss
index 9e49b9390..ba358e821 100644
--- a/src/main.scss
+++ b/src/main.scss
@@ -20,6 +20,11 @@ h1, h2, h3, h4 {
font-weight: 700;
}
+h1 {
+ font-size: 1.625rem;
+ line-height: 2.125rem;
+}
+
h4 {
font-size: 1.0rem;
line-height: 1.1rem;
@@ -48,3 +53,38 @@ a:hover {
/* NOTE: Margin should match height in navigation.scss */
margin-top: 35px;
}
+
+/* Forms */
+input {
+ height: 18px;
+ line-height: 18px;
+ display: inline-block;
+ padding: 4px;
+ margin-bottom: 9px;
+ font-size: 13px;
+ color: #555;
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
+ transition: border linear .2s,box-shadow linear .2s;
+}
+
+button {
+ cursor: pointer;
+ line-height: 30px;
+ border: 1px solid #eee;
+ border-radius: 5px;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);
+ transition: all .25s ease-in-out;
+ height: 32px;
+ text-shadow: 0 1px #fff;
+ font-weight: normal;
+ color: #666;
+ border-color: #999;
+ display: inline-block;
+ background: linear-gradient(to bottom, #fff, #ccc)
+}
+
+button:hover {
+ background-image: linear-gradient(#e6e6e6,#e6e6e6);
+}
diff --git a/src/views/components/components.jsx b/src/views/components/components.jsx
index c0cca7e1a..3fa834e0e 100644
--- a/src/views/components/components.jsx
+++ b/src/views/components/components.jsx
@@ -1,6 +1,7 @@
var React = require('react');
var Box = require('../../components/box/box.jsx');
+var Carousel = require('../../components/carousel/carousel.jsx');
require('./components.scss');
@@ -9,13 +10,19 @@ var View = React.createClass({
return (
Box Component
-
Things go in here
Lorem ipsum dolor sit amet.
+
Carousel Component
+
+
+
+
);
}
diff --git a/src/views/splash/featured.json b/src/views/splash/featured.json
new file mode 100644
index 000000000..773d6ef53
--- /dev/null
+++ b/src/views/splash/featured.json
@@ -0,0 +1,314 @@
+[
+ {
+ "title": "Featured Projects",
+ "items": [
+ {
+ "id": 1,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 2,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 3,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 4,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 5,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 6,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ }
+ ]
+ },
+ {
+ "title": "Featured Studios",
+ "items": [
+ {
+ "id": 1,
+ "type": "studio",
+ "title": "Example Studio",
+ "thumbnailUrl": "http://www.lorempixel.com/170/100/",
+ "href": "/studios/1000/"
+ },
+ {
+ "id": 2,
+ "type": "studio",
+ "title": "Example Studio",
+ "thumbnailUrl": "http://www.lorempixel.com/170/100/",
+ "href": "/studios/1000/"
+ },
+ {
+ "id": 3,
+ "type": "studio",
+ "title": "Example Studio",
+ "thumbnailUrl": "http://www.lorempixel.com/170/100/",
+ "href": "/studios/1000/"
+ },
+ {
+ "id": 4,
+ "type": "studio",
+ "title": "Example Studio",
+ "thumbnailUrl": "http://www.lorempixel.com/170/100/",
+ "href": "/studios/1000/"
+ },
+ {
+ "id": 5,
+ "type": "studio",
+ "title": "Example Studio",
+ "thumbnailUrl": "http://www.lorempixel.com/170/100/",
+ "href": "/studios/1000/"
+ },
+ {
+ "id": 6,
+ "type": "studio",
+ "title": "Example Studio",
+ "thumbnailUrl": "http://www.lorempixel.com/170/100/",
+ "href": "/studios/1000/"
+ }
+ ]
+ },
+ {
+ "title": "Projects Curated by raimondious",
+ "items": [
+ {
+ "id": 1,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 2,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 3,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 4,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 5,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 6,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ }
+ ]
+ },
+ {
+ "title": "Scratch Design Studio - \"Custom Block Madness!\"",
+ "items": [
+ {
+ "id": 1,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 2,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 3,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 4,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 5,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 6,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ }
+ ]
+ },
+ {
+ "title": "What the Community is Remixing",
+ "items": [
+ {
+ "id": 1,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 2,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 3,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 4,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 5,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 6,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ }
+ ]
+ },
+ {
+ "title": "What the Community is Loving",
+ "items": [
+ {
+ "id": 1,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 2,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 3,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 4,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 5,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ },
+ {
+ "id": 6,
+ "type": "project",
+ "title": "Example Project",
+ "thumbnailUrl": "http://www.lorempixel.com/144/108/",
+ "creator": "raimondious",
+ "href": "/projects/1000/"
+ }
+ ]
+ }
+]
diff --git a/src/views/splash/splash.jsx b/src/views/splash/splash.jsx
index e8b9f1563..5252588f2 100644
--- a/src/views/splash/splash.jsx
+++ b/src/views/splash/splash.jsx
@@ -4,6 +4,9 @@ var Api = require('../../mixins/api.jsx');
var Session = require('../../mixins/session.jsx');
var Activity = require('../../components/activity/activity.jsx');
+var Box = require('../../components/box/box.jsx');
+var Carousel = require('../../components/carousel/carousel.jsx');
+var Intro = require('../../components/intro/intro.jsx');
var News = require('../../components/news/news.jsx');
require('./splash.scss');
@@ -15,9 +18,10 @@ var View = React.createClass({
],
getInitialState: function () {
return {
+ projectCount: 10569070,
activity: [],
news: [],
- featured: []
+ featured: require('./featured.json')
};
},
componentDidMount: function () {
@@ -28,11 +32,20 @@ var View = React.createClass({
render: function () {
return (
+
-
+ {this.state.featured.map(function (set) {
+ return (
+
+
+
+ );
+ })}
);
}
diff --git a/src/views/splash/splash.scss b/src/views/splash/splash.scss
index de14cf5a3..5302eb974 100644
--- a/src/views/splash/splash.scss
+++ b/src/views/splash/splash.scss
@@ -15,4 +15,8 @@
width: 40%;
}
}
+
+ .box {
+ margin-bottom: 20px;
+ }
}
diff --git a/static/images/mystuff.png b/static/images/mystuff.png
new file mode 100644
index 000000000..6c2a99acc
Binary files /dev/null and b/static/images/mystuff.png differ
diff --git a/static/images/nav-notifications.png b/static/images/nav-notifications.png
new file mode 100644
index 000000000..5f0769caa
Binary files /dev/null and b/static/images/nav-notifications.png differ
diff --git a/webpack.config.js b/webpack.config.js
index 791c7e2d7..6f4640be9 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -7,7 +7,9 @@ var entry = {
main: './src/main.jsx'
};
routes.forEach(function (route) {
- entry[route.view] = './src/views/' + route.view + '/' + route.view + '.jsx';
+ if ( ! route.static ) {
+ entry[route.view] = './src/views/' + route.view + '/' + route.view + '.jsx';
+ }
});
// Config
@@ -36,7 +38,11 @@ module.exports = {
},
{
test: /\.scss$/,
- loader: 'style!css!sass!autoprefixer-loader?browsers=last 3 versions'
+ loader: 'style!css!autoprefixer-loader?browsers=last 3 versions!sass'
+ },
+ {
+ test: /\.(png|jpg|gif|eot|svg|ttf|woff)$/,
+ loader: 'url-loader'
}
]
},