+ };
+}
+
+function processPage() {
+ return {
+ url: function(o) {
+ return o.sectionName + '/' + o.fileName.split('.')[0]
+ },
+ content: function(o) {
+ return markdown().process(o.file.__content, highlight);
+ },
+ anchors: function(o) {
+ return markdown().getAnchors(o.file.__content);
+ },
+ contributors: function(o) {
+ return Array.isArray(o.file.contributors) && o.file.contributors.length && o.file.contributors.slice().sort();
+ }
+ };
+}
+
+function combineContexts(context1, context2) {
+ function webpackContext(req) {
+ try {
+ return context1(req);
+ } catch (e) {
+ return context2(req);
+ }
+ }
+ webpackContext.keys = () => {
+ let keys1 = context1.keys();
+ let keys2 = context2.keys();
+ return _.chain(keys1).concat(keys2).uniq().value();
+ };
+ return webpackContext;
+}
diff --git a/docs/assets/afs-screenshot-cropped.png b/docs/assets/afs-screenshot-cropped.png
new file mode 100644
index 000000000..9745ea111
Binary files /dev/null and b/docs/assets/afs-screenshot-cropped.png differ
diff --git a/docs/assets/afs-screenshot.png b/docs/assets/afs-screenshot.png
new file mode 100644
index 000000000..dda0f606d
Binary files /dev/null and b/docs/assets/afs-screenshot.png differ
diff --git a/docs/assets/angular-fullstack-boxes.svg b/docs/assets/angular-fullstack-boxes.svg
new file mode 100644
index 000000000..a3eb36d9d
--- /dev/null
+++ b/docs/assets/angular-fullstack-boxes.svg
@@ -0,0 +1,8883 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/angular-fullstack-logo.svg b/docs/assets/angular-fullstack-logo.svg
new file mode 100644
index 000000000..bf40810ac
--- /dev/null
+++ b/docs/assets/angular-fullstack-logo.svg
@@ -0,0 +1,9045 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/favicon.ico b/docs/assets/favicon.ico
new file mode 100644
index 000000000..450113bc5
Binary files /dev/null and b/docs/assets/favicon.ico differ
diff --git a/docs/assets/foo-route-navbar.jpg b/docs/assets/foo-route-navbar.jpg
new file mode 100644
index 000000000..0cca187bd
Binary files /dev/null and b/docs/assets/foo-route-navbar.jpg differ
diff --git a/docs/assets/foo-route.jpg b/docs/assets/foo-route.jpg
new file mode 100644
index 000000000..53de246a7
Binary files /dev/null and b/docs/assets/foo-route.jpg differ
diff --git a/docs/assets/geomanist-medium.woff b/docs/assets/geomanist-medium.woff
new file mode 100644
index 000000000..091266981
Binary files /dev/null and b/docs/assets/geomanist-medium.woff differ
diff --git a/docs/assets/geomanist-medium.woff2 b/docs/assets/geomanist-medium.woff2
new file mode 100644
index 000000000..026dafe9d
Binary files /dev/null and b/docs/assets/geomanist-medium.woff2 differ
diff --git a/docs/assets/github-logo.svg b/docs/assets/github-logo.svg
new file mode 100644
index 000000000..9dd8eb57a
--- /dev/null
+++ b/docs/assets/github-logo.svg
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/homepage-modules.svg b/docs/assets/homepage-modules.svg
new file mode 100644
index 000000000..bd9521bae
--- /dev/null
+++ b/docs/assets/homepage-modules.svg
@@ -0,0 +1,326 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/assets/icon-square-big.svg b/docs/assets/icon-square-big.svg
new file mode 100644
index 000000000..d4fed6ba7
--- /dev/null
+++ b/docs/assets/icon-square-big.svg
@@ -0,0 +1 @@
+icon-square-big
diff --git a/docs/assets/icon-square-small.svg b/docs/assets/icon-square-small.svg
new file mode 100644
index 000000000..d7b7e40b4
--- /dev/null
+++ b/docs/assets/icon-square-small.svg
@@ -0,0 +1 @@
+icon-square-small
diff --git a/docs/assets/logged-in.jpg b/docs/assets/logged-in.jpg
new file mode 100644
index 000000000..1906b25f4
Binary files /dev/null and b/docs/assets/logged-in.jpg differ
diff --git a/docs/assets/logo-on-dark-bg.svg b/docs/assets/logo-on-dark-bg.svg
new file mode 100644
index 000000000..952c47ede
--- /dev/null
+++ b/docs/assets/logo-on-dark-bg.svg
@@ -0,0 +1 @@
+logo-on-dark-bg
diff --git a/docs/assets/logo-on-white-bg.svg b/docs/assets/logo-on-white-bg.svg
new file mode 100644
index 000000000..a17b8b5d2
--- /dev/null
+++ b/docs/assets/logo-on-white-bg.svg
@@ -0,0 +1 @@
+logo-on-white-bg
diff --git a/docs/assets/logo-small.svg b/docs/assets/logo-small.svg
new file mode 100644
index 000000000..9564fb1b9
--- /dev/null
+++ b/docs/assets/logo-small.svg
@@ -0,0 +1,57 @@
+
+
+ Layer 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/site-logo.svg b/docs/assets/site-logo.svg
new file mode 100644
index 000000000..5b3b22a45
--- /dev/null
+++ b/docs/assets/site-logo.svg
@@ -0,0 +1 @@
+logo-on-dark-bg
\ No newline at end of file
diff --git a/docs/assets/socket.io-demo.gif b/docs/assets/socket.io-demo.gif
new file mode 100644
index 000000000..fdd3e0094
Binary files /dev/null and b/docs/assets/socket.io-demo.gif differ
diff --git a/docs/bootstrap.js b/docs/bootstrap.js
new file mode 100644
index 000000000..b4ac85e0f
--- /dev/null
+++ b/docs/bootstrap.js
@@ -0,0 +1,14 @@
+const antwar = require('antwar');
+
+const environment = process.env.npm_lifecycle_event || 'build';
+
+// Patch Babel env to make HMR switch work
+process.env.BABEL_ENV = environment;
+
+antwar[environment]({
+ environment,
+ antwar: require('./antwar.config'),
+ webpack: require('./webpack.config')
+}).catch(function (err) {
+ console.error(err);
+});
diff --git a/docs/components/cc/cc-style.scss b/docs/components/cc/cc-style.scss
new file mode 100644
index 000000000..1ca2c2b2d
--- /dev/null
+++ b/docs/components/cc/cc-style.scss
@@ -0,0 +1,11 @@
+.footer__license {
+ display:inline-block;
+ height:35px;
+ margin-left: 1em;
+ margin-right: 1em;
+
+ img {
+ width:auto;
+ height:100%;
+ }
+}
diff --git a/docs/components/cc/cc.jsx b/docs/components/cc/cc.jsx
new file mode 100644
index 000000000..32f8ba3e8
--- /dev/null
+++ b/docs/components/cc/cc.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import Link from '../link/link';
+import './cc-style.scss';
+
+const CC = () => {
+ return (
+
+
+
+ );
+};
+
+export default CC;
diff --git a/docs/components/container/container-style.scss b/docs/components/container/container-style.scss
new file mode 100644
index 000000000..474eeebf1
--- /dev/null
+++ b/docs/components/container/container-style.scss
@@ -0,0 +1,8 @@
+@import 'vars';
+@import 'mixins';
+
+.container {
+ width: 100%;
+ max-width: map-get($screens, large);
+ margin: 0 auto;
+}
diff --git a/docs/components/container/container.jsx b/docs/components/container/container.jsx
new file mode 100644
index 000000000..9f00bd0a8
--- /dev/null
+++ b/docs/components/container/container.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+export default (props = {}) => {
+ let { className = '' } = props;
+
+ return (
+
+ { props.children }
+
+ );
+};
diff --git a/docs/components/contributors/contributors-style.scss b/docs/components/contributors/contributors-style.scss
new file mode 100644
index 000000000..b8a441e02
--- /dev/null
+++ b/docs/components/contributors/contributors-style.scss
@@ -0,0 +1,37 @@
+@import 'functions';
+
+.contributors__list {
+ padding: 6px;
+}
+
+.contributor {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ margin-right: 1em;
+ font-size: 14px;
+
+ img {
+ height: 45px;
+ width: 45px;
+ border-radius: 50%;
+ box-shadow: 0 0 2px rgba(0,0,0,0.3);
+ }
+
+ .contributor__name {
+ color: getColor(fiord);
+ margin-top: -6px;
+ box-shadow: 0 0 2px rgba(0,0,0,0.3);
+ line-height: 1.4;
+ transition: color 0.1s;
+ padding: 0 6px;
+ border-radius: 2px;
+ background: transparentize(getColor(white), 0.05);
+ }
+
+ &:hover {
+ .contributor__name {
+ color: lighten(getColor(denim), 5%);
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/components/contributors/contributors.jsx b/docs/components/contributors/contributors.jsx
new file mode 100644
index 000000000..e114aac18
--- /dev/null
+++ b/docs/components/contributors/contributors.jsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import './contributors-style';
+
+export default ({contributors}) => {
+ if (!contributors.length) {
+ return ;
+ }
+
+ return (
+
+ );
+};
diff --git a/docs/components/cube/cube-style.scss b/docs/components/cube/cube-style.scss
new file mode 100644
index 000000000..dd52b31a3
--- /dev/null
+++ b/docs/components/cube/cube-style.scss
@@ -0,0 +1,39 @@
+@import 'functions';
+
+.cube {
+ position:relative;
+ display:block;
+ transform-style:preserve-3d;
+ transform:rotateX(-33.5deg) rotateY(45deg);
+
+ &__inner,
+ &__outer {
+ display:inline-block;
+ transform-style:preserve-3d;
+ transition:transform 1000ms;
+ }
+
+ &__inner {
+ position:absolute;
+ top:-2px;
+ left:0;
+ }
+
+ &__face {
+ position:absolute;
+ width:100%;
+ height:100%;
+ border: 1px solid getColor(white);
+ }
+
+ .cube__outer .cube__face {
+ background:transparentize(getColor(malibu), 0.5);
+ transition: border-width 0.2s;
+ transition-delay: 0.2s;
+ }
+
+ .cube__inner .cube__face {
+ background:getColor(denim);
+ border: 2px solid getColor(white);
+ }
+}
diff --git a/docs/components/cube/cube.jsx b/docs/components/cube/cube.jsx
new file mode 100644
index 000000000..06f4f01a9
--- /dev/null
+++ b/docs/components/cube/cube.jsx
@@ -0,0 +1,231 @@
+import React, { PropTypes } from 'react';
+
+export default class Cube extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.listeners = {
+ spin: this._spin.bind(this),
+ reset: this._reset.bind(this)
+ };
+
+ this.state = {
+ x: 0,
+ y: 0,
+ z: 0,
+ iteration: 0,
+ };
+ }
+
+ render() {
+ let { x, y, z } = this.state;
+ let { theme, depth, className = '' } = this.props;
+
+ return (
+
+ this.container = ref }
+ className={ `cube cube--${theme}` }
+ style={{
+ width: `${depth}px`,
+ paddingBottom: `${depth * 0.5}px`
+ }}>
+
+ { this._getFaces('outer') }
+
+
+ { this._getFaces('inner') }
+
+
+
+ );
+ }
+
+ componentDidMount() {
+ let { hover, continuous, repeatDelay } = this.props;
+
+ if (hover) {
+ this.container.addEventListener('mouseenter', this.listeners.spin);
+ this.container.addEventListener('mouseleave', this.listeners.reset);
+
+ } else if (continuous) {
+ let degrees = 0;
+ let axis = 'y';
+
+ this._interval = setInterval(() => {
+ let obj = {};
+ obj[axis] = degrees += 90;
+
+ this.setState({ ...obj, iteration: (this.state.iteration + 1) % 4 });
+ }, repeatDelay);
+ }
+ }
+
+ componentWillUnmount() {
+ let { hover, continuous } = this.props;
+
+ if (hover) {
+ this.container.removeEventListener('mouseenter', this.listeners.spin);
+ this.container.removeEventListener('mouseleave', this.listeners.reset);
+
+ } else if (continuous) {
+ clearInterval(this._interval);
+ }
+ }
+
+ /**
+ * Get all faces for a cube
+ *
+ * @param {'inner' | 'outer' } type
+ * @return {array} - An array of nodes
+ */
+ _getFaces(type) {
+ let { iteration } = this.state;
+
+ // Keep the thicker border on
+ // the outside on each iteration
+ const borderWidthMap = {
+ 0: {
+ left: [1, 1, 1, 6],
+ right: [6, 1, 1, 1],
+ top: [1, 1, 1, 1],
+ bottom: [6, 1, 1, 6],
+ },
+ 1: {
+ left: [1, 1, 1, 1],
+ right: [1, 1, 1, 1],
+ top: [1, 1, 1, 1],
+ bottom: [1, 1, 1, 1],
+ },
+ 2: {
+ left: [1, 1, 6, 6],
+ right: [6, 6, 1, 1],
+ top: [6, 1, 1, 6],
+ bottom: [1, 6, 6, 1],
+ },
+ 3: {
+ left: [6, 1, 1, 1],
+ right: [1, 6, 1, 1],
+ top: [1, 1, 1, 1],
+ bottom: [6, 6, 1, 1],
+ },
+ 4: {
+ left: [1, 1, 6, 1],
+ right: [1, 1, 1, 6],
+ top: [1, 1, 1, 1],
+ bottom: [1, 1, 6, 6],
+ },
+ 5: {
+ left: [1, 6, 1, 1],
+ right: [1, 1, 6, 1],
+ top: [1, 1, 1, 1],
+ bottom: [1, 6, 6, 1],
+ }
+ };
+
+ return [
+ 'rotateX(0deg)',
+ 'rotateX(-90deg)',
+ 'rotateX(90deg)',
+ 'rotateY(-90deg)',
+ 'rotateY(90deg)',
+ 'rotateY(180deg)'
+ ].map((rotation, i) => {
+ const borderStyles = type === 'outer' ? {
+ borderTopWidth: borderWidthMap[i].top[iteration],
+ borderRightWidth: borderWidthMap[i].right[iteration],
+ borderBottomWidth: borderWidthMap[i].bottom[iteration],
+ borderLeftWidth: borderWidthMap[i].left[iteration],
+ } : {};
+
+ return (
+
+ );
+ });
+ }
+
+ /**
+ * Get a random axis
+ *
+ * @return {string} - A random axis (i.e. x, y, or z)
+ */
+ _getRandomAxis() {
+ let axes = Object.keys(this.state);
+
+ return axes[ Math.floor(Math.random() * axes.length) ];
+ }
+
+ /**
+ * Spin the cubes in opposite directions semi-randomly
+ *
+ * @param {object} e - Native event
+ */
+ _spin(e) {
+ let obj = {};
+ let axis = this._getRandomAxis();
+ let sign = Math.random() < 0.5 ? -1 : 1;
+
+ obj[axis] = sign * 90;
+
+ this.setState(obj);
+ }
+
+ /**
+ * Rotate the cubes back to their original position
+ *
+ * @param {object} e - Native event
+ */
+ _reset(e) {
+ this.setState({
+ x: 0,
+ y: 0,
+ z: 0
+ });
+ }
+}
+
+Cube.propTypes = {
+ hover: PropTypes.bool,
+ theme: PropTypes.string,
+ depth: PropTypes.number,
+ repeatDelay: PropTypes.number
+};
+
+Cube.defaultProps = {
+ hover: false,
+ theme: 'dark',
+ depth: 30,
+ repeatDelay: 1000,
+};
diff --git a/docs/components/footer/footer-style.scss b/docs/components/footer/footer-style.scss
new file mode 100644
index 000000000..989db31c8
--- /dev/null
+++ b/docs/components/footer/footer-style.scss
@@ -0,0 +1,79 @@
+@import 'mixins';
+@import 'functions';
+
+.footer {
+ width: 100%;
+ flex: 0 0 auto;
+}
+
+.footer__inner {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-content: center;
+ padding: 0.4em 0;
+ border-top:1px solid getColor(concrete);
+
+ @include break {
+ flex-direction: initial;
+ }
+}
+
+.footer__left,
+.footer__right {
+ flex: 0 0 auto;
+ display: flex;
+ margin: auto;
+ align-items: center;
+ padding: 0.25em 0;
+
+ @include break {
+ margin: 0;
+ padding: 0;
+ }
+}
+
+.footer__left {
+ @include break {
+ padding-left:1.5em;
+ }
+}
+
+.footer__right {
+ @include break {
+ padding-right:1.5em;
+ }
+}
+
+.footer__middle {
+ display: none;
+
+ @include break {
+ flex: 0 0 auto;
+ display: block;
+ }
+}
+
+.footer__icon {
+ display:inline-block;
+ height:35px;
+
+ img {
+ width:auto;
+ height:100%;
+ }
+}
+
+.footer__link {
+ font-size: getFontSize(-2);
+ text-transform: uppercase;
+ color: getColor(dusty-grey);
+
+ &:not(:last-child) {
+ margin-right: 1.5em;
+ }
+
+ &:hover {
+ color: getColor(mine-shaft);
+ }
+}
diff --git a/docs/components/footer/footer.jsx b/docs/components/footer/footer.jsx
new file mode 100644
index 000000000..2a922e6c0
--- /dev/null
+++ b/docs/components/footer/footer.jsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import CC from '../cc/cc';
+import Link from '../link/link';
+import Container from '../container/container';
+import Icon from '../../assets/logo-small.svg';
+import './footer-style';
+
+export default (props) => {
+ return (
+
+
+
+ Get Started
+ Organization
+ Contribute
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/docs/components/link/link.jsx b/docs/components/link/link.jsx
new file mode 100644
index 000000000..71e2c3d33
--- /dev/null
+++ b/docs/components/link/link.jsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import startsWith from 'lodash/startsWith';
+
+let RRouter;
+if (__DEV__) {
+ RRouter = require('react-router');
+}
+
+const Link = ({ to, ...props }) => {
+ if (startsWith(to, 'http') || startsWith(to, '//')) {
+ return ;
+ }
+
+ if (__DEV__) {
+ return ;
+ }
+
+ return ;
+};
+
+export default Link;
diff --git a/docs/components/logo/logo-style.scss b/docs/components/logo/logo-style.scss
new file mode 100644
index 000000000..bf119e149
--- /dev/null
+++ b/docs/components/logo/logo-style.scss
@@ -0,0 +1,11 @@
+.logo {
+ float:left;
+ width:auto;
+ height:35px;
+ opacity:0.9;
+ transition:opacity 250ms;
+
+ &:hover {
+ opacity:1;
+ }
+}
diff --git a/docs/components/logo/logo.jsx b/docs/components/logo/logo.jsx
new file mode 100644
index 000000000..03f239337
--- /dev/null
+++ b/docs/components/logo/logo.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import Logo from '../../assets/logo-small.svg';
+
+export default () => {
+ return (
+
+ );
+};
diff --git a/docs/components/navigation/navigation-style.scss b/docs/components/navigation/navigation-style.scss
new file mode 100644
index 000000000..8841d8210
--- /dev/null
+++ b/docs/components/navigation/navigation-style.scss
@@ -0,0 +1,203 @@
+@import 'mixins';
+@import 'functions';
+
+.navigation {
+ flex: 0 0 auto;
+ transition: background 250ms;
+ background: getColor(elephant);
+}
+
+.navigation__inner {
+ display: flex;
+ align-items: center;
+ position: relative;
+ padding: 0.6em 1em;
+
+ @include break {
+ padding: 0 1.5em;
+ }
+}
+
+.navigation__mobile {
+ display: flex;
+ font-size: 1.5em;
+ position: absolute;
+ top: .64em;
+ align-items: center;
+ color: getColor(white);
+ cursor: pointer;
+ transition: color 250ms;
+
+ &:active {
+ color: getColor(alto);
+ }
+
+ @include break {
+ display: none;
+ }
+
+ .icon-menu {
+ display: inline-flex;
+ }
+}
+
+.navigation__logo {
+ margin:auto;
+
+ .logo-text {
+ color: white;
+ padding: .5em;
+ vertical-align: middle;
+ line-height: 35px;
+ font-weight: bold;
+ letter-spacing: .03em;
+ font-size: 1.2rem;
+ }
+}
+
+.navigation__links {
+ display: none;
+
+ @include break {
+ flex: 1 1 auto;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ }
+}
+
+.navigation__link {
+ @include break {
+ display:inline-block;
+ font-size: getFontSize(-1);
+ text-transform: uppercase;
+ color: getColor(white);
+ transition:all 250ms;
+ padding:1.5em 0.75em;
+
+ &:last-child {
+ margin-right:0.75em;
+ }
+ }
+
+ @include break('large') {
+ padding:1.5em;
+ }
+
+ &:hover,
+ &--active {
+ color: getColor(white);
+ }
+
+ &--active {
+ background: lighten(getColor(elephant), 5%);
+ }
+}
+
+.navigation__search {
+ display:none;
+}
+
+@include break {
+ .navigation__search {
+ flex:0 0 auto;
+ display:flex;
+ justify-content:flex-end;
+ padding:0.8em 0;
+
+ &-input {
+ padding:0;
+ border:none;
+ background:transparent;
+
+ width:0;
+ font-size:14px;
+ text-indent:0.5em;
+ border-bottom:1px solid getColor(fiord);
+ margin-right:0;
+ color: getColor(white);
+ text-shadow: 0 0 0 getColor(concrete);
+ transition:all 250ms;
+
+ &::-webkit-input-placeholder{
+ color: lighten(getColor(fiord), 10%);
+ text-shadow: none;
+ -webkit-text-fill-color: initial;
+ }
+ }
+
+ &-icon {
+ font-size:1em;
+ padding:0;
+ border:none;
+ cursor:pointer;
+ color:getColor(alto);
+ background:transparent;
+ transition:color 250ms;
+
+ &.icon-cross {
+ display:none;
+ }
+
+ &:hover {
+ color:getColor(white);
+ }
+ }
+
+ &-input:focus,
+ &-icon:focus {
+ outline:none;
+ }
+ }
+}
+
+@include break {
+ .navigation--search-mode {
+ .navigation__link {
+ pointer-events:none;
+ overflow:hidden;
+ white-space:nowrap;
+ padding:1.5em 0;
+ margin-right:-35px;
+ opacity:0;
+ }
+
+ .navigation__search-input {
+ margin-right:0.5em;
+ width:400px;
+ }
+
+ .navigation__search-icon.icon-cross {
+ display:block;
+ }
+
+ .navigation__search-icon.icon-magnifying-glass {
+ display:none;
+ }
+ }
+}
+
+.navigation__bottom {
+ display:none;
+ background: lighten(getColor(elephant), 5%);
+
+ @include break {
+ display:block;
+ }
+}
+
+.navigation__child {
+ font-size: getFontSize(-1);
+ margin:0.5em 1em 0.6em;
+ color:getColor(alto);
+ text-transform: uppercase;
+
+ &:first-of-type {
+ margin-left: 0;
+ }
+
+ &:hover,
+ &--active {
+ color:getColor(white);
+ }
+}
diff --git a/docs/components/navigation/navigation.jsx b/docs/components/navigation/navigation.jsx
new file mode 100644
index 000000000..4b72769be
--- /dev/null
+++ b/docs/components/navigation/navigation.jsx
@@ -0,0 +1,193 @@
+import React from 'react';
+import Link from '../link/link';
+import Container from '../container/container';
+import Logo from '../logo/logo';
+
+// TODO: Maybe by updating the routing scheme later on we can avoid hardcoding this?
+// let Sections = [
+// {
+// title: 'Concepts',
+// url: 'concepts'
+// },
+// {
+// title: 'Guides',
+// url: 'guides'
+// },
+// {
+// title: 'Documentation',
+// url: 'configuration',
+// children: [
+// { title: 'API', url: 'api' },
+// { title: 'Configuration', url: 'configuration' },
+// { title: 'Loaders', url: 'loaders' },
+// { title: 'Plugins', url: 'plugins' }
+// ]
+// },
+// {
+// title: 'Donate',
+// url: 'https://opencollective.com/angular-fullstack'
+// }
+// ];
+let Sections = [{
+ title: 'Getting Started',
+ url: 'get-started',
+}, {
+ title: 'Guides',
+ url: 'guides',
+ children: [
+ { title: 'Getting Started', url: 'get-started' },
+ { title: 'Developing', url: 'developing' },
+ { title: 'Deployment', url: 'deployment' },
+ { title: 'Examples', url: 'examples' },
+ ]
+}, {
+ title: 'Generators',
+ url: 'generators',
+}, {
+ title: 'Donate',
+ url: 'https://opencollective.com/angular-fullstack',
+}];
+
+// TODO: Move back to using state once we can handle algolia on our own
+export default class Navigation extends React.Component {
+ render() {
+ let { pageUrl = '' } = this.props;
+
+ return (
+
+ );
+ }
+
+ componentDidMount() {
+ if (typeof window !== 'undefined') {
+ window.docsearch({
+ apiKey: 'fac401d1a5f68bc41f01fb6261661490',
+ indexName: 'webpack-js-org',
+ inputSelector: '.navigation__search-input'
+ });
+
+ window.addEventListener('keyup', e => {
+ if (e.which === 9 && e.target.classList.contains('navigation__search-input')) {
+ this._openSearch();
+ }
+ });
+ }
+ }
+
+ /**
+ * Check if section is active
+ *
+ * @param {object} section - An object describing the section
+ * @return {bool} - Whether or not the given section is active
+ */
+ _isActive(section) {
+ let { pageUrl = '' } = this.props;
+
+ if (section.children) {
+ return section.children.some(child => pageUrl.includes(`${child.url}/`));
+
+ } else return pageUrl.includes(`${section.url}/`);
+ }
+
+ /**
+ * Toggle the SidebarMobile component
+ *
+ * @param {object} e - Native click event
+ */
+ _toggleSidebar(e) {
+ let sidebar = document.querySelector('.sidebar-mobile');
+ let modifier = 'sidebar-mobile--visible';
+
+ sidebar.classList.toggle(modifier);
+ }
+
+ /**
+ * Toggle the search input
+ *
+ */
+ _toggleSearch() {
+ let container = document.querySelector('.navigation');
+ let input = document.querySelector('.navigation__search-input');
+ let state = container.classList.toggle('navigation--search-mode');
+
+ if ( state === true ) input.focus();
+ }
+
+ /**
+ * Expand the search input
+ *
+ */
+ _openSearch() {
+ let container = document.querySelector('.navigation');
+
+ container.classList.add('navigation--search-mode');
+ }
+}
diff --git a/docs/components/navigation/search-style.scss b/docs/components/navigation/search-style.scss
new file mode 100644
index 000000000..9c9a59972
--- /dev/null
+++ b/docs/components/navigation/search-style.scss
@@ -0,0 +1,102 @@
+@import 'functions';
+
+.algolia-autocomplete {
+ display: flex !important;
+ position: relative;
+
+ .ds-dropdown-menu {
+ box-shadow: none;
+
+ &:before {
+ content: none;
+ }
+
+ [class^=ds-dataset-] {
+ border-radius: 0;
+ border-color: getColor(malibu);
+ border-width: 2px 0;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.25);
+ padding: 0;
+ }
+
+ .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content {
+ background: transparentize(getColor(malibu), 0.9) !important;
+ }
+ }
+
+ .algolia-docsearch-suggestion {
+ padding: 0;
+ }
+
+ .algolia-docsearch-suggestion--wrapper {
+ display: flex;
+ padding: 0;
+ }
+
+ .algolia-docsearch-suggestion--text {
+ color: getColor(dusty-grey);
+
+ .algolia-docsearch-suggestion--highlight {
+ color: $text-color-highlight;
+ box-shadow: none;
+ font-weight: bold;
+ }
+ }
+
+ .algolia-docsearch-suggestion--highlight {
+ color: $text-color-highlight;
+ background: rgba(getColor(malibu), 0.15);
+ }
+
+ .algolia-docsearch-suggestion--category-header {
+ text-transform: uppercase;
+ font-weight: bold;
+ font-size: 0.9em;
+ border-color: getColor(alto);
+ color: getColor(mine-shaft);
+ margin: 0;
+ padding: 6px 16px;
+ }
+
+ .algolia-docsearch-suggestion--subcategory-column {
+ padding: 8px 16px 8px 12px;
+ background: transparentize(getColor(dusty-grey), 0.92);
+ display: block;
+ color: transparent;
+ }
+
+ .algolia-docsearch-suggestion__secondary .algolia-docsearch-suggestion--subcategory-column {
+ color: getColor(dove-grey);
+ }
+
+ .algolia-docsearch-suggestion--content {
+ padding: 8px 16px 8px 12px;
+ }
+
+ .ds-suggestion:nth-child(n+2) {
+ .algolia-docsearch-suggestion--category-header {
+ border-top: 1px solid #dedede;
+ }
+ }
+
+ .algolia-docsearch-suggestion--title {
+ color: getColor(mine-shaft);
+ }
+
+ .ds-suggestion:last-child {
+ .algolia-docsearch-suggestion--subcategory-column,
+ .algolia-docsearch-suggestion--content {
+ padding-bottom: 26px;
+ }
+ }
+
+ .algolia-docsearch-footer {
+ position: absolute;
+ bottom: 4px;
+ right: 16px;
+ }
+
+ .aa-suggestion-title-separator {
+ color: getColor(dusty-grey);
+ }
+}
diff --git a/docs/components/organization/organization-style.scss b/docs/components/organization/organization-style.scss
new file mode 100644
index 000000000..b10f13f48
--- /dev/null
+++ b/docs/components/organization/organization-style.scss
@@ -0,0 +1,34 @@
+@import 'functions';
+@import 'mixins';
+
+.organization {
+ padding: 1.5em;
+}
+
+.organization__projects {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+
+ @include break {
+ justify-content: flex-start;
+ }
+}
+
+.organization__project {
+ width: 280px;
+ height: 420px;
+ margin-right: 1em;
+ margin-bottom: 1em;
+ padding: 1em;
+ border-radius: 2px;
+ box-shadow: 0 0 1px getColor(dove-grey);
+ overflow: hidden;
+ background: getColor(concrete);
+ transition: all 250ms;
+
+ &:hover {
+ box-shadow: 0 0 5px getColor(emperor);
+ background: white;
+ }
+}
diff --git a/docs/components/organization/organization.jsx b/docs/components/organization/organization.jsx
new file mode 100644
index 000000000..7a7fe9e72
--- /dev/null
+++ b/docs/components/organization/organization.jsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import Container from '../container/container';
+import Contributors from '../contributors/contributors';
+import Link from '../link/link';
+import Items from './projects.json';
+import './organization-style';
+
+let Shield = props => (
+
+);
+
+export default props => {
+ return (
+
+ The Organization
+
+ The list below provides a brief overview of all commonly used projects in the webpack ecosystem.
+
+
+ {
+ Items.map(org => (
+
+
+ { org.repo }
+
+
+
{ org.description }
+
+
Downloads and Stars
+
+
+
+
+
Activity
+
+
+
+
+
+
+
+
+
Issues and PRs
+
+
+
+
+
Maintainers
+ {
+ (() => {
+ if (org.maintainer) {
+ return
;
+
+ } else return
Maintainer needed...;
+ })()
+ }
+
+
+ ))
+ }
+
+
+ );
+};
diff --git a/docs/components/organization/projects.json b/docs/components/organization/projects.json
new file mode 100644
index 000000000..a9c6e72c3
--- /dev/null
+++ b/docs/components/organization/projects.json
@@ -0,0 +1,292 @@
+
+[
+ {
+ "repo": "webpack/memory-fs",
+ "npm": "memory-fs",
+ "description": "A simple in-memory filesystem that holds data in a javascript object.",
+ "maintainer": "sokra"
+ },
+ {
+ "repo": "webpack/webpack",
+ "npm": "webpack",
+ "description": "A bundler for javascript and friends.",
+ "maintainer": "sokra"
+ },
+ {
+ "repo": "webpack/loader-utils",
+ "npm": "loader-utils",
+ "description": "Webpack loader utilities.",
+ "maintainer": "sokra"
+ },
+ {
+ "repo": "webpack/source-list-map",
+ "npm": "source-list-map",
+ "description": "Fast line to line SourceMap generator.",
+ "maintainer": "sokra"
+ },
+ {
+ "repo": "webpack/enhanced-resolve",
+ "npm": "enhanced-resolve",
+ "description": "A highly configurable asynchronous require.resolve function.",
+ "maintainer": "sokra"
+ },
+ {
+ "repo": "webpack/tapable",
+ "npm": "tapable",
+ "description": "Just a little module for plugins.",
+ "maintainer": "sokra"
+ },
+ {
+ "repo": "webpack/watchpack",
+ "npm": "watchpack",
+ "description": "Wrapper library for directory and file watching.",
+ "maintainer": "sokra"
+ },
+ {
+ "repo": "webpack/node-libs-browser",
+ "npm": "node-libs-browser",
+ "description": "The node core libs for in browser usage.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/webpack-dev-server",
+ "npm": "webpack-dev-server",
+ "description": "A development server that updates the browser on file changes.",
+ "maintainer": "Spacek33z"
+ },
+ {
+ "repo": "webpack/file-loader",
+ "npm": "file-loader",
+ "description": "A simple loader for copying and renaming files.",
+ "maintainer": "sokra"
+ },
+ {
+ "repo": "webpack/style-loader",
+ "npm": "style-loader",
+ "description": "Load and inject stylesheets into the DOM.",
+ "maintainer": "sokra"
+ },
+ {
+ "repo": "webpack/css-loader",
+ "npm": "css-loader",
+ "description": "Load CSS modules and resolve any dependencies.",
+ "maintainer": "sokra"
+ },
+ {
+ "repo": "webpack/webpack-dev-middleware",
+ "npm": "webpack-dev-middleware",
+ "description": "Middleware which arguments a live bundle to a directory.",
+ "maintainer": "Spacek33z"
+ },
+ {
+ "repo": "webpack/fastparse",
+ "npm": "fastparse",
+ "description": "A simple parser based on a statemachine and regular expressions.",
+ "maintainer": "sokra"
+ },
+ {
+ "repo": "webpack/json-loader",
+ "npm": "json-loader",
+ "description": "Load JSON into a pre-parsed variable.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/url-loader",
+ "npm": "url-loader",
+ "description": "Load files into data urls based on byte limit.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/webpack-sources",
+ "npm": "webpack-sources",
+ "description": "Source code handling classes for webpack.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/extract-text-webpack-plugin",
+ "npm": "extract-text-webpack-plugin",
+ "description": "Extract text from a bundle into a file.",
+ "maintainer": ""
+ },
+ {
+ "repo": "jtangelder/sass-loader",
+ "npm": "sass-loader",
+ "description": "Load and parse SASS modules into CSS.",
+ "maintainer": "jhnns"
+ },
+ {
+ "repo": "webpack/karma-webpack",
+ "npm": "karma-webpack",
+ "description": "Use webpack to pre-process files in karma.",
+ "maintainer": "MikaAK"
+ },
+ {
+ "repo": "webpack/imports-loader",
+ "npm": "imports-loader",
+ "description": "Detect and import libraries based on the use of certain variables.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/raw-loader",
+ "npm": "raw-loader",
+ "description": "Load a module's contents as a string.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/exports-loader",
+ "npm": "exports-loader",
+ "description": "Load a module's contents directly into export statements.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/less-loader",
+ "npm": "less-loader",
+ "description": "Load and parse LESS modules into CSS.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/expose-loader",
+ "npm": "expose-loader",
+ "description": "Expose modules as global variables.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/html-loader",
+ "npm": "html-loader",
+ "description": "Load HTML modules/templates as strings.",
+ "maintainer": "hemanth"
+ },
+ {
+ "repo": "webpack/loader-runner",
+ "npm": "loader-runner",
+ "description": "Run webpack loaders.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/script-loader",
+ "npm": "script-loader",
+ "description": "Load and execute scripts once in the global context.",
+ "maintainer": ""
+ },
+ {
+ "repo": "shama/webpack-stream",
+ "npm": "webpack-stream",
+ "description": "Run webpack through a stream interface.",
+ "maintainer": "shama"
+ },
+ {
+ "repo": "webpack/source-map-loader",
+ "npm": "source-map-loader",
+ "description": "Extract source-map comments from modules to pass to webpack.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/null-loader",
+ "npm": "null-loader",
+ "description": "Load an empty module.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/compression-webpack-plugin",
+ "npm": "compression-webpack-plugin",
+ "description": "Compress assets to serve with Content-Encoding.",
+ "maintainer": "palmerj3"
+ },
+ {
+ "repo": "webpack/transform-loader",
+ "npm": "transform-loader",
+ "description": "Load modules with browserify transforms.",
+ "maintainer": "minwe"
+ },
+ {
+ "repo": "webpack/grunt-webpack",
+ "npm": "grunt-webpack",
+ "description": "Integrate webpack into a grunt build process.",
+ "maintainer": "danez"
+ },
+ {
+ "repo": "webpack/jshint-loader",
+ "npm": "jshint-loader",
+ "description": "Load and lint modules with JSHint.",
+ "maintainer": "kostasmanionis"
+ },
+ {
+ "repo": "webpack/bundle-loader",
+ "npm": "bundle-loader",
+ "description": "Load a module and it's children into a separate bundle.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/coffee-loader",
+ "npm": "coffee-loader",
+ "description": "Load and parse coffee script modules into JS.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/worker-loader",
+ "npm": "worker-loader",
+ "description": "Load modules as workers.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/mocha-loader",
+ "npm": "mocha-loader",
+ "description": "Load mocha modules for testing.",
+ "maintainer": "tricoder42"
+ },
+ {
+ "repo": "webpack/react-proxy-loader",
+ "npm": "react-proxy-loader",
+ "description": "Wrap a react component in a proxy component to enable Code Splitting.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/multi-loader",
+ "npm": "multi-loader",
+ "description": "Split a module and import each piece with different loaders.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/val-loader",
+ "npm": "val-loader",
+ "description": "Load and execute a module while compiling, returning the result.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/i18n-webpack-plugin",
+ "npm": "i18n-webpack-plugin",
+ "description": "Embed localization into your bundle.",
+ "maintainer": "EcutDavid"
+ },
+ {
+ "repo": "webpack/json5-loader",
+ "npm": "json5-loader",
+ "description": "Load JSON5 into a pre-parsed variable.",
+ "maintainer": "gdi2290"
+ },
+ {
+ "repo": "webpack/node-loader",
+ "npm": "node-loader",
+ "description": "Load native node modules.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/coverjs-loader",
+ "npm": "coverjs-loader",
+ "description": "Test modules' code coverage using CoverJS.",
+ "maintainer": ""
+ },
+ {
+ "repo": "webpack/coffee-redux-loader",
+ "npm": "coffee-redux-loader",
+ "description": "Load coffee script modules.",
+ "maintainer": ""
+ },
+ {
+ "repo": "thelarkinn/angular2-template-loader",
+ "npm": "angular2-template-loader",
+ "description": "Load angular2 components, inlining all html and styling.",
+ "maintainer": "thelarkinn"
+ }
+]
+
\ No newline at end of file
diff --git a/docs/components/page/page-style.scss b/docs/components/page/page-style.scss
new file mode 100644
index 000000000..6c04d34b6
--- /dev/null
+++ b/docs/components/page/page-style.scss
@@ -0,0 +1,49 @@
+@import 'vars';
+@import 'mixins';
+@import 'functions';
+
+.page {
+ position: relative;
+ flex: 1 0 auto;
+ display: flex;
+}
+
+// XXX: Temporary hack to fix sidebar width
+.page div:first-of-type {
+ flex: 0 0 auto;
+ overflow: auto;
+
+ @include break {
+ flex:0 0 30%;
+ }
+
+ @include break('large') {
+ flex:0 0 25%;
+ }
+}
+
+.page__content {
+ overflow-x: hidden;
+ width: 100%;
+ padding: 1.5em 1em;
+
+ @media break {
+ flex: 3;
+ padding: 1.5em;
+ }
+}
+
+.page__edit {
+ display: none;
+
+ @include break {
+ position: absolute;
+ display: block;
+ top: 1.5em;
+ right: 1.5em;
+ font-size: getFontSize(-1);
+ text-transform: uppercase;
+
+ i { font-size:0.8em; }
+ }
+}
diff --git a/docs/components/page/page.jsx b/docs/components/page/page.jsx
new file mode 100644
index 000000000..4ec8aa169
--- /dev/null
+++ b/docs/components/page/page.jsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import Interactive from 'antwar-interactive';
+import Container from '../container/container';
+import Sidebar from '../sidebar/sidebar';
+import Sidecar from '../sidecar/sidecar';
+import Contributors from '../contributors/contributors';
+import './page-style';
+import '../sidebar/sidebar-style';
+import { trimEnd } from 'lodash';
+
+export default ({ section, page }) => {
+ let edit = page.edit || `https://github.com/angular-fullstack/generator-angular-fullstack/edit/master/docs/content/${trimEnd(page.url, '/')}${page.type === 'index' ? '/index' : ''}.md`;
+
+ return (
+
+
+
+ );
+};
diff --git a/docs/components/sidebar-item/sidebar-item-style.scss b/docs/components/sidebar-item/sidebar-item-style.scss
new file mode 100644
index 000000000..e1e72a5ae
--- /dev/null
+++ b/docs/components/sidebar-item/sidebar-item-style.scss
@@ -0,0 +1,79 @@
+@import 'mixins';
+@import 'functions';
+
+.sidebar-item {
+ font-size: getFontSize(-1);
+ margin-bottom: 0.75em;
+
+ &__title {
+ font-weight: 400;
+ text-decoration: none;
+ color: inherit;
+
+ &:hover {
+ color: $text-color-highlight;
+ }
+ }
+
+ &__toggle {
+ float: right;
+ line-height: 1.5;
+ cursor: pointer;
+ color: getColor(dusty-grey);
+ transition: color 250ms;
+
+ &:hover {
+ color: getColor(mine-shaft);
+ }
+ }
+
+ &__anchors {
+ display:none;
+ list-style: none;
+ padding: 0;
+ margin: 0.5em 0 1em;
+ }
+
+ &__version {
+ margin-bottom: 10px;
+ }
+
+ &__anchor {
+ margin:0.25em 0;
+
+ a {
+ display: inline-block;
+ max-width: 100%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ color: getColor(dusty-grey);
+
+ &:hover {
+ color: getColor(mine-shaft);
+ }
+ }
+ }
+
+ &--open {
+ .sidebar-item__anchors {
+ display:block;
+ }
+
+ .sidebar-item__title {
+ color: $text-color-highlight;
+ }
+
+ .sidebar-item__toggle {
+ margin-left:-2px;
+ transform:rotate(180deg) translateX(1px);
+ }
+ }
+
+ &--empty {
+ .sidebar-item__toggle,
+ .sidebar-item__anchors {
+ display: none;
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/components/sidebar-item/sidebar-item.jsx b/docs/components/sidebar-item/sidebar-item.jsx
new file mode 100644
index 000000000..f59881b46
--- /dev/null
+++ b/docs/components/sidebar-item/sidebar-item.jsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import Link from '../link/link';
+
+export default class SidebarItem extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ open: false
+ };
+ }
+
+ render() {
+ let { index, url, title, anchors = [], currentPage } = this.props;
+
+ let emptyMod = !anchors.length ? 'sidebar-item--empty' : '';
+ let active = `/${currentPage}` === url;
+ let openMod = (active || this.state.open) ? 'sidebar-item--open' : '';
+ let anchorUrl = (active) ? '#' : url + '#';
+
+ return (
+
+ );
+ }
+
+ toggle(e) {
+ this.setState({
+ open: !this.state.open
+ });
+ }
+}
\ No newline at end of file
diff --git a/docs/components/sidebar-mobile/sidebar-mobile-style.scss b/docs/components/sidebar-mobile/sidebar-mobile-style.scss
new file mode 100644
index 000000000..6074a2e74
--- /dev/null
+++ b/docs/components/sidebar-mobile/sidebar-mobile-style.scss
@@ -0,0 +1,112 @@
+@import 'functions';
+@import 'mixins';
+
+.sidebar-mobile {
+ position: fixed;
+ width: 300px;
+ height: 100vh;
+ z-index: 100;
+ top: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+ -webkit-overflow-scrolling: touch;
+ transform: translate3D(-100%, 0, 0);
+ transform: translate3D(calc(-100% + 5px), 0, 0);
+ transition: all 500ms cubic-bezier(0.23, 1, 0.32, 1);
+
+ @include break {
+ display: none;
+ }
+
+ &--visible {
+ transform: translate3D(0, 0, 0);
+ }
+
+ &.no-delay{
+ transition-duration: 0ms;
+ }
+}
+
+.sidebar-mobile__toggle {
+ position: absolute;
+ top: 45px;
+ bottom: 0;
+ width: 32px;
+ left: 285px;
+}
+
+.sidebar-mobile__content {
+ position: relative;
+ width: 285px;
+ height: 100vh;
+ overflow-x: hidden;
+ padding: 12px 0;
+ background: getColor(white);
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
+}
+
+.sidebar-mobile__close {
+ position:absolute;
+ cursor:pointer;
+ right: 22px;
+ top: 22px;
+ font-size: 1.3em;
+ background: getColor(denim);
+ color: getColor(white);
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ transition: background 150ms;
+ -webkit-tap-highlight-color: transparent;
+
+ &:hover {
+ background: darken(getColor(denim), 20%);
+ }
+}
+
+.sidebar-mobile__section {
+ border-left: 2px solid transparent;
+ padding-bottom:0.5em;
+
+ &--active {
+ border-left: 2px solid getColor(malibu);
+
+ .sidebar-mobile__section-header {
+ color: lighten(getColor(fiord), 15%)
+ }
+ }
+}
+
+.sidebar-mobile__section-header {
+ text-transform: uppercase;
+ color: getColor(elephant);
+ padding: 0.75em 16px 0.25em;
+ font-weight: 600;
+ display: block;
+
+ .sidebar-mobile__content div:not(:first-of-type) & {
+ border-top: 1px solid getColor(alto);
+ }
+}
+
+.sidebar-mobile__page {
+ display: block;
+ padding: 0.5em 17px;
+ text-transform: capitalize;
+ color: getColor(dove-grey);
+ -webkit-tap-highlight-color: rgba(0,0,0,0);
+
+ &:active,
+ &--active {
+ color: getColor(mine-shaft);
+ font-weight: 600;
+ background: #F1F4F4;
+ }
+
+ &:hover {
+ color: inherit;
+ }
+}
diff --git a/docs/components/sidebar-mobile/sidebar-mobile.jsx b/docs/components/sidebar-mobile/sidebar-mobile.jsx
new file mode 100644
index 000000000..55b8b3f5d
--- /dev/null
+++ b/docs/components/sidebar-mobile/sidebar-mobile.jsx
@@ -0,0 +1,193 @@
+import React from 'react';
+import Link from '../link/link';
+
+let initialTouchPosition = {};
+let lastTouchPosition = {};
+
+export default class SidebarMobile extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this._handleBodyClick = this._handleBodyClick.bind(this);
+ }
+
+ render() {
+ return (
+ this.container = ref }
+ onTouchStart={this._handleTouchStart.bind(this)}
+ onTouchMove={this._handleTouchMove.bind(this)}
+ onTouchEnd={this._handleTouchEnd.bind(this)}>
+
+
+
+
+
+
+ { this._getSections() }
+
+
+ );
+ }
+
+ componentDidMount() {
+ if (typeof window !== 'undefined') {
+ window.addEventListener('click', this._handleBodyClick);
+ window.addEventListener('touchstart', this._handleBodyClick);
+ }
+ }
+
+ componentWillUnmount() {
+ if (typeof window !== 'undefined') {
+ window.removeEventListener('click', this._handleBodyClick);
+ window.removeEventListener('touchstart', this._handleBodyClick);
+ }
+ }
+
+ /**
+ * Get markup for each section
+ *
+ * @return {array} - Markup containing sections and links
+ */
+ _getSections() {
+ let pathname = '';
+
+ if (typeof window !== 'undefined') {
+ pathname = window.location.pathname;
+ }
+
+ return this.props.sections.map(section => {
+ let active = pathname === section.url || pathname.includes(`/${section.url}`),
+ absoluteUrl = `/${section.url}`;
+ return (
+
+
+
{ section.title }
+
+
+ { this._getPages(section.pages) }
+
+ );
+ });
+ }
+
+ /**
+ * Retrieve markup for page links
+ *
+ * @param {array} pages - A list of page objects
+ * @return {array} - Markup containing the page links
+ */
+ _getPages(pages) {
+ let pathname = '';
+
+ if (typeof window !== 'undefined') {
+ pathname = window.location.pathname;
+ }
+
+ return pages.map(page => {
+ let url = `/${page.url}`,
+ active = pathname === url || pathname.includes(`${url}/`);
+
+ return (
+
+ { page.title }
+
+ );
+ });
+ }
+
+ /**
+ * Handle clicks on content
+ *
+ * @param {object} e - Native click event
+ */
+ _handleBodyClick(e) {
+ if (
+ !e.target.classList.contains('icon-menu') &&
+ !this.container.contains(e.target)
+ ) {
+ this._close();
+ }
+ }
+
+ /**
+ * Hide the sidebar
+ *
+ */
+ _close() {
+ this.container.classList.remove(
+ 'sidebar-mobile--visible'
+ );
+ }
+
+ _open() {
+ this.container.classList.add(
+ 'sidebar-mobile--visible'
+ );
+ }
+
+ _handleTouchStart(e){
+ initialTouchPosition.x = e.touches[0].pageX;
+ initialTouchPosition.y = e.touches[0].pageY;
+
+ // For instant transform along with the touch
+ this.container.classList.add('no-delay');
+ }
+
+ _handleTouchMove(e){
+ let xDiff = initialTouchPosition.x - e.touches[0].pageX;
+ let yDiff = initialTouchPosition.y - e.touches[0].pageY;
+ let factor = Math.abs(yDiff / xDiff);
+
+ // Factor makes sure horizontal and vertical scroll dont take place together
+ if (xDiff>0 && factor < 0.8) {
+ e.preventDefault();
+ this.container.style.transform = `translateX(-${xDiff}px)`;
+ lastTouchPosition.x = e.touches[0].pageX;
+ lastTouchPosition.y = e.touches[0].pageY;
+ }
+ }
+
+ _handleOpenerTouchMove(e){
+ let xDiff = e.touches[0].pageX - initialTouchPosition.x;
+ let yDiff = initialTouchPosition.y - e.touches[0].pageY;
+ let factor = Math.abs(yDiff / xDiff);
+
+ // Factor makes sure horizontal and vertical scroll dont take place together
+ if (xDiff > 0 && xDiff < 295 && factor < 0.8) {
+ e.preventDefault();
+ this.container.style.transform = `translateX(calc(-100% + ${xDiff}px))`;
+ lastTouchPosition.x = e.touches[0].pageX;
+ lastTouchPosition.y = e.touches[0].pageY;
+ }
+ }
+
+ _handleTouchEnd(e){
+ // Free up all the inline styling
+ this.container.classList.remove('no-delay');
+ this.container.style.transform = '';
+
+ if (initialTouchPosition.x - lastTouchPosition.x > 100) {
+ this._close();
+ } else if (lastTouchPosition.x - initialTouchPosition.x > 100) {
+ this._open();
+ }
+ }
+}
diff --git a/docs/components/sidebar/sidebar-style.scss b/docs/components/sidebar/sidebar-style.scss
new file mode 100644
index 000000000..6d3bd9b7a
--- /dev/null
+++ b/docs/components/sidebar/sidebar-style.scss
@@ -0,0 +1,19 @@
+@import 'vars';
+@import 'mixins';
+
+.sidebar {
+ position: relative;
+ display: none;
+ flex: 1;
+ min-height: 100%;
+
+ @include break {
+ display:block;
+ }
+}
+
+.sidebar__inner {
+ padding: 1.5em;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
diff --git a/docs/components/sidebar/sidebar.jsx b/docs/components/sidebar/sidebar.jsx
new file mode 100644
index 000000000..d91145f4f
--- /dev/null
+++ b/docs/components/sidebar/sidebar.jsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import SidebarItem from '../sidebar-item/sidebar-item';
+
+export default props => {
+ let { sectionName, pages, currentPage } = props;
+
+ return (
+
+
+
Version 4.1.x
+
+ {
+ pages.map(({ url, title, anchors }, i) =>
+
+ )
+ }
+
+
+ );
+};
diff --git a/docs/components/sidecar/sidecar-style.scss b/docs/components/sidecar/sidecar-style.scss
new file mode 100644
index 000000000..2a55bb7d6
--- /dev/null
+++ b/docs/components/sidecar/sidecar-style.scss
@@ -0,0 +1,103 @@
+@import 'functions';
+@import 'mixins';
+
+.sidecar {
+ display: none;
+
+ @media (min-width: 1085px) {
+ display: block;
+ position: fixed;
+ top: 100px;
+ right: 0;
+ z-index: 100;
+ }
+}
+
+.sidecar__link {
+ display: inline-block;
+ padding: 0.65em;
+ float: right;
+ clear: both;
+ color: getColor(concrete);
+ transition: all 250ms;
+
+ &:first-of-type {
+ border-top-left-radius: 3px;
+ }
+
+ &:last-of-type {
+ border-bottom-left-radius: 3px;
+ }
+
+ &:hover {
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ color: getColor(white);
+ }
+}
+
+.sidecar__label {
+ display: block;
+ float: left;
+ width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ transition: width 250ms;
+}
+
+.sidecar__icon {
+ display: inline-block;
+ width: 18px;
+ vertical-align: middle;
+ font-size: 18px;
+}
+
+// TODO: Fix hardcoded widths using flex perhaps, can't animate to width auto
+.sidecar__link--github {
+ background: #444;
+
+ &:hover {
+ background: #333;
+
+ .sidecar__label {
+ width: 115px;
+ }
+ }
+}
+
+.sidecar__link--gitter {
+ background: desaturate(#ed1965, 15%);
+
+ &:hover {
+ background: #ed1965;
+
+ .sidecar__label {
+ width: 85px;
+ }
+ }
+}
+
+.sidecar__link--medium {
+ background: desaturate(#02b875, 15%);
+
+ &:hover {
+ background: #02b875;
+
+ .sidecar__label {
+ width: 115px;
+ }
+ }
+}
+
+.sidecar__link--so {
+ background: desaturate(#fe7a15, 15%);
+
+ &:hover {
+ background: #fe7a15;
+
+ .sidecar__label {
+ width: 125px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/components/sidecar/sidecar.jsx b/docs/components/sidecar/sidecar.jsx
new file mode 100644
index 000000000..244cba8a5
--- /dev/null
+++ b/docs/components/sidecar/sidecar.jsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import Link from '../link/link';
+import './sidecar-style';
+
+export default React.createClass({
+ getInitialState() {
+ return {open: false};
+ },
+
+ initChat() {
+ this.chat = new window.gitter.Chat({
+ room: 'angular-fullstack/generator-angular-fullstack',
+ activationElement: '.js-gitter-toggle-chat-button',
+ preload: true
+ });
+ // console.log(this.chat);
+
+ document.addEventListener('gitter-sidecar-instance-started', chat => {
+ // console.log('loaded', chat);
+ this.chat = chat;
+ });
+
+ document.querySelector('.gitter-chat-embed').addEventListener('gitter-chat-toggle', e => {
+ this.state.open = e.detail.state;
+ // console.log(e.detail.state ? 'Chat Opened' : 'Chat Closed');
+ });
+ },
+
+ componentDidMount() {
+ // console.log('did mount');
+ if(window.gitter && typeof window.gitter.Chat === 'function') {
+ // console.log('already loaded');
+
+ this.initChat();
+ } else {
+ // console.log('waiting');
+
+ document.addEventListener('gitter-sidecar-ready', () => {
+ // console.log('ready');
+
+ this.initChat();
+ });
+ }
+ },
+
+ openChat() {
+ this.state.open = true;
+ this.chat.toggleChat(true);
+ },
+
+ render() {
+ return (
+
+ );
+ }
+});
diff --git a/docs/components/site/site-style.scss b/docs/components/site/site-style.scss
new file mode 100644
index 000000000..3eeff2c7a
--- /dev/null
+++ b/docs/components/site/site-style.scss
@@ -0,0 +1,10 @@
+.site {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ overflow: hidden;
+
+ &.nav-displayed {
+ height: 100vh;
+ }
+}
\ No newline at end of file
diff --git a/docs/components/site/site.jsx b/docs/components/site/site.jsx
new file mode 100644
index 000000000..e6f24ef2f
--- /dev/null
+++ b/docs/components/site/site.jsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import Interactive from 'antwar-interactive';
+import { GoogleAnalytics } from 'antwar-helpers';
+import Navigation from '../navigation/navigation';
+import Footer from '../footer/footer';
+import SidebarMobile from '../sidebar-mobile/sidebar-mobile';
+import './site-style';
+
+// Load base styling
+import '../../styles';
+import '../../styles/icon.font.js';
+import '../container/container-style.scss';
+import '../navigation/navigation-style';
+import '../navigation/search-style';
+import '../sidebar-mobile/sidebar-mobile-style';
+import '../sidebar-item/sidebar-item-style';
+import '../logo/logo-style';
+
+export default props => {
+ // Retrieve section data
+ let sections = props.children.props.section.all()
+ .map(({ title, url, pages }) => ({
+ title,
+ url,
+ pages: pages.map(({ title, url }) => ({
+ title: title || url, // XXX: Title shouldn't be coming in as undefined
+ url
+ }))
+ }));
+
+ // Rename the root section ("Webpack" => "Other") and push it to the end
+ let rootIndex = sections.findIndex(section => section.title === 'Webpack');
+ let rootSection = sections.splice(rootIndex, 1)[0];
+ rootSection.title = 'Other';
+ sections.push(rootSection);
+
+ return (
+
+
+
+
+
+ { props.children }
+
+
+
+
+ );
+};
diff --git a/docs/components/splash-viz/splash-viz-style.scss b/docs/components/splash-viz/splash-viz-style.scss
new file mode 100644
index 000000000..35e4509cd
--- /dev/null
+++ b/docs/components/splash-viz/splash-viz-style.scss
@@ -0,0 +1,59 @@
+@import 'functions';
+@import 'mixins';
+
+.splash-viz {
+ position:relative;
+ display:flex;
+ height:calc(100vh - 55px);
+ min-height:320px;
+ max-height:720px;
+ background:getColor(elephant);
+ flex-direction: column;
+
+ &__heading {
+ color: getColor(white);
+ font-size: getFontSize(4);
+ text-align: center;
+ font-weight: 200;
+ margin-top: 24px;
+
+ @include break {
+ font-size: getFontSize(5);
+ }
+ }
+
+ &__modules {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ width: 65vw;
+ min-width: 550px;
+ margin: auto;
+ display: none;
+
+ @include break {
+ display: block;
+ }
+
+ img {
+ // Padding is needed to center align
+ // webpack icon with the module image
+ padding-top: 3vw;
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ &__cube {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ margin: auto;
+ z-index: 1;
+
+ }
+}
diff --git a/docs/components/splash-viz/splash-viz.jsx b/docs/components/splash-viz/splash-viz.jsx
new file mode 100644
index 000000000..114642e2d
--- /dev/null
+++ b/docs/components/splash-viz/splash-viz.jsx
@@ -0,0 +1,28 @@
+import React from 'react';
+
+import Cube from '../cube/cube';
+import TextRotator from '../text-rotater/text-rotater';
+import Modules from '../../assets/homepage-modules.svg';
+
+export default class SplashViz extends React.Component {
+
+ render() {
+ return (
+
+
+ bundle your
+
+ assets
+ scripts
+ images
+ styles
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/docs/components/splash/splash-style.scss b/docs/components/splash/splash-style.scss
new file mode 100644
index 000000000..52d898f94
--- /dev/null
+++ b/docs/components/splash/splash-style.scss
@@ -0,0 +1,35 @@
+@import 'functions';
+@import 'mixins';
+
+.splash {
+ &-logo {
+ background:getColor(elephant);
+ max-height: 500px;
+
+ img {
+ max-height: inherit;
+ }
+ }
+
+ &__section {
+ position:relative;
+ padding:5em 1em;
+ text-align:center;
+
+ &:last-child {
+ padding-top: 0;
+ }
+
+ @include break {
+ padding:8em 1.5em;
+ }
+
+ pre {
+ text-align:left;
+ }
+
+ .icon-link {
+ display:none;
+ }
+ }
+}
diff --git a/docs/components/splash/splash.jsx b/docs/components/splash/splash.jsx
new file mode 100644
index 000000000..1112decc8
--- /dev/null
+++ b/docs/components/splash/splash.jsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import Interactive from 'antwar-interactive';
+import Container from '../container/container';
+import SplashViz from '../splash-viz/splash-viz';
+import Support from '../support/support';
+import './splash-style';
+import '../splash-viz/splash-viz-style';
+import '../cube/cube-style';
+import '../text-rotater/text-rotater-style.scss';
+import BigLogo from '../../assets/angular-fullstack-logo.svg';
+
+export default props => {
+ let { page } = props;
+
+ return (
+
+
+
+
+
+
+ { page.title }
+
+
+
+
+ Support the Team
+
+ Through contributions, donations, and sponsorship, you allow this project to thrive.
+
+ Sponsors
+
+
+ Backers
+
+
+
+ );
+};
diff --git a/docs/components/support/support-style.scss b/docs/components/support/support-style.scss
new file mode 100644
index 000000000..1542f34dd
--- /dev/null
+++ b/docs/components/support/support-style.scss
@@ -0,0 +1,31 @@
+@import 'functions';
+
+.support {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ padding:10px 5px;
+
+ &__item {
+ margin:0 5px;
+ }
+
+ &__bottom {
+ flex:0 0 100%;
+ margin-top:10px;
+ }
+
+ &__button {
+ display:inline-block;
+ padding:0.4em 1em;
+ border:2px solid getColor(denim);
+ color:getColor(denim);
+ border-radius:1.25em;
+ transition:all 250ms;
+
+ &:hover {
+ background:getColor(denim);
+ color:getColor(white);
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/components/support/support.jsx b/docs/components/support/support.jsx
new file mode 100644
index 000000000..754478e01
--- /dev/null
+++ b/docs/components/support/support.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import './support-style';
+
+export default ({number, type}) => {
+ return (
+
+ {[...Array(number)].map((x, i) =>
+
+
+
+ )}
+
+ );
+};
diff --git a/docs/components/text-rotater/text-rotater-style.scss b/docs/components/text-rotater/text-rotater-style.scss
new file mode 100644
index 000000000..108e09158
--- /dev/null
+++ b/docs/components/text-rotater/text-rotater-style.scss
@@ -0,0 +1,46 @@
+.text-rotater {
+ overflow: hidden;
+ position: relative;
+ display: inline-block;
+ padding: 0 0.3em;
+ vertical-align: bottom;
+
+ &::after,
+ &::before {
+ content: '';
+ position: absolute;
+ height: 3px;
+ left: 0;
+ }
+
+ &::after {
+ top: 0;
+ background-image: linear-gradient(#{getColor(elephant)}, transparent);
+ }
+
+ &::before {
+ bottom: 0;
+ z-index: 1;
+ background-image: linear-gradient(transparent, #{getColor(elephant)});
+ }
+
+ > * {
+ display: inline-block;
+ }
+}
+
+.text-rotater--slide-up {
+ transition: transform 1s;
+ transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
+ transform: translateY(-100%);
+}
+
+.text-rotater__wrap {
+ display: inline-flex;
+ flex-direction: column;
+ text-align: left;
+
+ > * {
+ flex-shrink: 0;
+ }
+}
diff --git a/docs/components/text-rotater/text-rotater.jsx b/docs/components/text-rotater/text-rotater.jsx
new file mode 100644
index 000000000..5c1352dee
--- /dev/null
+++ b/docs/components/text-rotater/text-rotater.jsx
@@ -0,0 +1,91 @@
+import React, { PropTypes } from 'react';
+
+export default class TextRotater extends React.PureComponent {
+
+ constructor(props) {
+ super(props);
+ this.handleTransitionEnd = this.handleTransitionEnd.bind(this);
+ this.calculateContentHeight = this.calculateContentHeight.bind(this);
+
+ this.state = {
+ currentIndex: 0,
+ contentHeight: 0,
+ };
+ }
+
+ componentDidMount() {
+ const { delay } = this.props;
+
+ setTimeout(() => {
+ this.calculateContentHeight();
+ }, 50);
+
+ setTimeout(() => {
+ this.textRotatorWrap.classList.add('text-rotater--slide-up');
+ }, delay);
+
+ window.addEventListener('resize', this.calculateContentHeight);
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('resize', this.calculateContentHeight);
+ }
+
+ calculateContentHeight() {
+ this.setState({
+ contentHeight: this.content.clientHeight,
+ });
+ }
+
+ handleTransitionEnd() {
+ const { children, repeatDelay } = this.props;
+ this.textRotatorWrap.classList.remove('text-rotater--slide-up');
+ this.setState({
+ currentIndex: (this.state.currentIndex + 1) % React.Children.count(children),
+ }, () => {
+ setTimeout(() => {
+ this.textRotatorWrap.classList.add('text-rotater--slide-up');
+ }, repeatDelay);
+ });
+ }
+
+ render() {
+ const { children, maxWidth } = this.props;
+ const { currentIndex, contentHeight } = this.state;
+ const childrenCount = React.Children.count(children);
+
+ const currentChild = React.cloneElement(children[currentIndex], { ref: c => (this.content = c)});
+
+ const nextChild = React.cloneElement(children[(currentIndex + 1) % childrenCount]);
+
+ return (
+
+
(this.textRotatorWrap = trw) }
+ onTransitionEnd={ this.handleTransitionEnd }
+ style={ { height: contentHeight, width: maxWidth } }
+ >
+ { currentChild }
+ { nextChild }
+
+
+ );
+ }
+}
+
+TextRotater.defaultProps = {
+ delay: 0,
+ repeatDelay: 3000,
+};
+
+
+TextRotater.propTypes = {
+ children: PropTypes.arrayOf(PropTypes.node),
+ delay: PropTypes.number,
+ repeatDelay: PropTypes.number,
+ // Needed to prevent jump when
+ // rotating between texts of different widths
+ maxWidth: PropTypes.number,
+};
+
diff --git a/docs/components/vote/api.dev.js b/docs/components/vote/api.dev.js
new file mode 100644
index 000000000..1689b13da
--- /dev/null
+++ b/docs/components/vote/api.dev.js
@@ -0,0 +1,153 @@
+let usedCurrencies = {
+ influence: 100,
+ goldenInfluence: 100
+};
+let totalCurrencies = {
+ influence: 1000,
+ goldenInfluence: 300
+};
+let lists = {
+ todo: {
+ possibleVotes: [
+ {
+ name: "influence",
+ currency: "influence",
+ score: 1,
+ color: "blue"
+ },
+ {
+ name: "golden",
+ currency: "goldenInfluence",
+ score: 1,
+ color: "#bfa203"
+ }
+ ],
+ items: [
+ { id: "1234", list: "todo", title: "Finish up MVP documentation", description: "Take care for the remaining issues in the webpack.js.org repo which are relevant for the MVP.", influence: 15 },
+ { id: "2345", list: "todo", title: "Review whole documentation", description: "Read over **all** of the documentation to find errors.", golden: 20 },
+ ]
+ }
+};
+let allItems = {
+ "1234": lists.todo.items[0],
+ "2345": lists.todo.items[1],
+};
+
+function delay(time) {
+ return new Promise(function (fulfill) {
+ setTimeout(fulfill, time);
+ });
+}
+
+function clone(json) {
+ return JSON.parse(JSON.stringify(json));
+}
+
+export function isLoginActive() {
+ return /^\?login=/.test(window.location.search);
+}
+
+export function startLogin(callbackUrl) {
+ window.location.search = "?login=" + encodeURIComponent(callbackUrl);
+ return Promise.resolve();
+}
+
+export function continueLogin() {
+ if(/^\?login=/.test(window.location.search)) {
+ return delay(2000).then(() => {
+ setTimeout(() => window.location = decodeURIComponent(window.location.search.substr(7), 100));
+ return "developer";
+ });
+ }
+ return Promise.resolve();
+}
+
+export function getSelf(token) {
+ if(token !== "developer")
+ return Promise.reject(new Error("Not logged in as developer"));
+ return delay(500).then(() => ({
+ login: "dev",
+ name: "Developer",
+ avatar: "https://github.com/webpack.png",
+ currencies: [
+ { name: "influence", displayName: "Influence", description: "Some **description**", value: totalCurrencies.influence, used: usedCurrencies.influence, remaining: totalCurrencies.influence - usedCurrencies.influence },
+ { name: "goldenInfluence", displayName: "Golden Influence", description: "Some **description**", value: totalCurrencies.goldenInfluence, used: usedCurrencies.goldenInfluence, remaining: totalCurrencies.goldenInfluence - usedCurrencies.goldenInfluence }
+ ]
+ }));
+}
+
+export function getList(token, name) {
+ const loggedIn = token === "developer";
+ const listData = lists[name];
+ return delay(500).then(() => ({
+ name: name,
+ displayName: "DEV: " + name,
+ description: "Some **description**",
+ lockable: true,
+ deletable: true,
+ archivable: true,
+ isAdmin: true,
+ possibleVotes: listData.possibleVotes,
+ items: lists[name].items.map(item => {
+ const votes = listData.possibleVotes.map(pv => ({
+ name: pv.name,
+ votes: (item[pv.name] || 0) + Math.floor(Math.random() * 100)
+ }));
+ const score = listData.possibleVotes.map((pv, i) => {
+ return pv.score * votes[i].votes;
+ }).reduce((a, b) => a + b, 0);
+ return {
+ id: item.id,
+ list: item.list,
+ title: item.title,
+ description: item.description,
+ votes,
+ userVotes: loggedIn ? listData.possibleVotes.map(pv => ({
+ name: pv.name,
+ votes: item[pv.name] || 0
+ })) : undefined,
+ score
+ };
+ }).sort((a, b) => b.score - a.score)
+ }));
+}
+
+export function createItem(token, list, title, description) {
+ if(token !== "developer")
+ return Promise.reject(new Error("Not logged in as developer"));
+ let newItem = {
+ id: Math.random() + "",
+ list,
+ title,
+ description
+ };
+ allItems[newItem.id] = newItem;
+ lists[list].items.push(newItem);
+ return delay(500).then(() => ({
+ ...newItem,
+ votes: lists[list].possibleVotes.map(pv => ({
+ name: pv.name,
+ votes: 0
+ })),
+ userVotes: lists[list].possibleVotes.map(pv => ({
+ name: pv.name,
+ votes: 0
+ })),
+ score: 0
+ }));
+}
+
+export function vote(token, itemId, voteName, value) {
+ if(token !== "developer")
+ return Promise.reject(new Error("Not logged in as developer"));
+ var listId = allItems[itemId].list;
+ let listData = lists[listId];
+ let pv = listData.possibleVotes.filter(pv => pv.name === voteName)[0];
+ if(pv.currency) {
+ usedCurrencies[pv.currency] += value;
+ }
+ allItems[itemId][voteName] = (allItems[itemId][voteName] || 0) + value;
+ return delay(500).then(() => ({
+ ok: true
+ }));
+}
diff --git a/docs/components/vote/api.js b/docs/components/vote/api.js
new file mode 100644
index 000000000..27e6cc3f9
--- /dev/null
+++ b/docs/components/vote/api.js
@@ -0,0 +1,112 @@
+import {
+ isLoginActive as devIsLoginActive,
+ startLogin as devStartLogin,
+ continueLogin as devContinueLogin,
+ getSelf as devGetSelf,
+ getList as devGetList,
+ createItem as devCreateItem,
+ vote as devVote
+} from "./api.dev";
+
+const API_URL = "https://oswils44oj.execute-api.us-east-1.amazonaws.com/production/";
+const GITHUB_CLIENT_ID = "4d355e2799cb8926c665";
+const PRODUCTION_HOST = "webpack.js.org";
+
+// You can test the production mode with a host entry,
+// or by setting PRODUCTION_HOST to "localhost:3000" and stealing localStorage.voteAppToken from the production side.
+
+export function isLoginActive() {
+ if(window.location.host !== PRODUCTION_HOST)
+ return devIsLoginActive();
+ return /^\?code=([^&]*)&state=([^&]*)/.test(window.location.search);
+}
+
+export function startLogin(callbackUrl) {
+ if(window.location.host !== PRODUCTION_HOST)
+ return devStartLogin(callbackUrl);
+ let state = "" + Math.random();
+ window.localStorage.githubState = state;
+ window.location = "https://github.com/login/oauth/authorize?client_id=" + GITHUB_CLIENT_ID + "&scope=user:email&state=" + state + "&allow_signup=false&redirect_uri=" + encodeURIComponent(callbackUrl);
+ return Promise.resolve();
+}
+
+export function continueLogin() {
+ if(window.location.host !== PRODUCTION_HOST)
+ return devContinueLogin();
+ const match = /^\?code=([^&]*)&state=([^&]*)/.exec(window.location.search);
+ if(match) {
+ return login(match[1], match[2]).then(result => {
+ setTimeout(() => {
+ let href = window.location.href;
+ window.location = href.substr(0, href.length - window.location.search.length);
+ }, 100);
+ return result;
+ });
+ }
+ return Promise.resolve();
+}
+
+function login(code, state) {
+ if(state !== window.localStorage.githubState)
+ return Promise.reject(new Error("Request state doesn't match (Login was triggered by 3rd party)"));
+ delete window.localStorage.githubState;
+ return fetch(API_URL + "/login", {
+ headers: {
+ "Content-Type": "application/json"
+ },
+ method: "POST",
+ body: JSON.stringify({
+ code,
+ state
+ })
+ }).then((res) => res.json()).then(result => {
+ return result.token;
+ });
+}
+
+export function getSelf(token) {
+ if(window.location.host !== PRODUCTION_HOST)
+ return devGetSelf(token);
+ return fetch(API_URL + "/self?token=" + token, {
+ mode: "cors"
+ }).then((res) => res.json());
+}
+
+export function getList(token, name) {
+ if(window.location.host !== PRODUCTION_HOST)
+ return devGetList(token, name);
+ return fetch(API_URL + "/list/" + name + (token ? "?token=" + token : ""), {
+ mode: "cors"
+ }).then((res) => res.json());
+}
+
+export function createItem(token, list, title, description) {
+ if(window.location.host !== PRODUCTION_HOST)
+ return devCreateItem(token, list, title, description);
+ return fetch(API_URL + "/list/" + list + "?token=" + token, {
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ title,
+ description
+ }),
+ method: "POST"
+ }).then((res) => res.json());
+}
+
+export function vote(token, itemId, voteName, value) {
+ if(window.location.host !== PRODUCTION_HOST)
+ return devVote(token, itemId, voteName, value);
+ return fetch(API_URL + "/vote/" + itemId + "/" + voteName + "?token=" + token, {
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ count: value
+ }),
+ method: "POST"
+ }).then((res) => res.json()).then(result => {
+ return true;
+ });
+}
diff --git a/docs/components/vote/app-style.scss b/docs/components/vote/app-style.scss
new file mode 100644
index 000000000..5bf99c94d
--- /dev/null
+++ b/docs/components/vote/app-style.scss
@@ -0,0 +1,218 @@
+@import 'vars';
+@import 'mixins';
+@import 'functions';
+
+.vote-app {
+ margin: 1.5em;
+
+ &__top {
+ display: flex;
+ flex-direction: column;
+
+ @include break(medium) {
+ flex-direction: row;
+ }
+ }
+
+ &__influence {
+ flex: 0 0 75%;
+ }
+
+ &__influence-description {
+ display: flex;
+ flex-direction: column;
+
+ @include break(medium) {
+ flex-direction: row;
+ }
+ }
+
+ &__user-section {
+ padding: 0 0 30px;
+
+ @include break(medium) {
+ flex: 0 0 25%;
+ padding: 0 0 0 20px;
+ }
+
+ @include break(large) {
+ border-left: 2px solid getColor(elephant);
+ }
+ }
+
+ &__influence-section {
+ flex: 0 0 50%;
+ }
+
+ &__influence-disclaimer {
+ padding: 1em 0;
+ font-size: smaller;
+ color: red;
+
+ @include break(medium) {
+ text-align: center;
+ }
+ }
+
+ &__login-button {
+ margin: 20px 0;
+
+ button {
+ border: none;
+ outline: none;
+ color: getColor(white);
+ background: getColor(elephant);
+ padding: 5px 10px 5px 10px;
+ border-radius: 2px;
+ font-size: 13px;
+ cursor: pointer;
+
+ &:hover {
+ background: black;
+ }
+
+ &:active {
+ background: getColor(elephant)
+ }
+ }
+
+ img {
+ height: 25px;
+ vertical-align: middle;
+ padding-left: 5px;
+ }
+ }
+
+ &__userinfo {
+ outline: none;
+ border: none;
+ background: getColor(elephant);
+ color: white;
+ border-radius: 3px;
+ font-size: 13px;
+ display: inline-block;
+ padding: 3px 10px;
+
+ img {
+ height: 25px;
+ vertical-align: middle;
+ margin-right: 10px;
+ }
+ }
+
+ &__self-info {
+ margin-top: 10px;
+ }
+
+ &__button-area {
+ margin: 5px 0;
+ }
+
+ &__update-button {
+
+ }
+
+ h1 {
+ font-size: 16pt;
+ font-weight: bold;
+ margin-bottom: 10px;
+ }
+
+ &__item-title {
+ font-size: 16pt;
+ font-weight: bold;
+ margin-right: 10px;
+ }
+
+ &__currency-list {
+ display: block;
+
+ & > li {
+ display: inline-block;
+ padding: 5px;
+ }
+ }
+
+ &__item-card {
+ display: flex;
+ flex-direction: column;
+
+ @include break(medium){
+ flex-direction: row;
+ }
+ }
+
+ &__score-section {
+ display: flex;
+ border: 1px solid lightgray;
+ user-select: none;
+ flex-wrap: wrap;
+
+ @include break(medium){
+ flex: 0 0 40%;
+ margin-right: 30px;
+ padding: 0 0 0 20px;
+ }
+ }
+
+ &__item-score {
+ align-self: center;
+ font-size: 20pt;
+ flex: 0 0 100%;
+ text-align: center;
+
+ @include break(medium){
+ flex: 0 0 20%;
+ text-align: left;
+ }
+ }
+
+ &__item-button {
+ align-self: center;
+ flex: 0 0 50%;
+
+ @include break(medium){
+ flex: 0 0 40%;
+ }
+ }
+
+ &__items-list {
+ display: block;
+
+ & > li {
+ display: block;
+ padding: 5px 0;
+ }
+ }
+
+ &__currency-influence, &__vote-influence {
+ color: blue;
+ &:before {
+ content: "♦\2009";
+ }
+ }
+
+ &__currency-goldenInfluence, &__vote-golden {
+ color: #bfa203;
+ &:before {
+ content: "♦\2009";
+ }
+ }
+
+ &__currency-support, &__vote-support {
+ color: green;
+ &:before {
+ content: "$\2009";
+ }
+ }
+
+ &__vote-thumb {
+
+ }
+
+ &__admin {
+ input, textarea, button {
+ width: 100%;
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/components/vote/app.jsx b/docs/components/vote/app.jsx
new file mode 100644
index 000000000..49a760776
--- /dev/null
+++ b/docs/components/vote/app.jsx
@@ -0,0 +1,332 @@
+import React from 'react';
+import 'whatwg-fetch';
+import * as api from "./api";
+import VoteButton from './button/button';
+import Influence from './influence.jsx';
+import GithubMark from '../../assets/github-logo.svg';
+
+function updateByProperty(array, property, propertyValue, update) {
+ return array.map(item => {
+ if(item[property] === propertyValue) {
+ return update(item);
+ } else {
+ return item;
+ }
+ });
+}
+
+export default class VoteApp extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ selfInfo: undefined,
+ listInfo: undefined,
+ isFetchingSelf: false,
+ isVoting: 0
+ };
+ }
+
+ isBrowserSupported() {
+ return typeof localStorage === 'object';
+ }
+
+ componentDidMount() {
+ if(!this.isBrowserSupported())
+ return;
+
+ let { selfInfo, listInfo } = this.state;
+
+ if(api.isLoginActive()) {
+ this.setState({
+ isLoginActive: true
+ });
+ api.continueLogin().then(token => {
+ window.localStorage.voteAppToken = token;
+ });
+ } else {
+ if(!selfInfo) {
+ this.updateSelf();
+ }
+ if(!listInfo) {
+ this.updateList();
+ }
+ }
+ }
+
+ componentWillReceiveProps(props) {
+ if(!this.isBrowserSupported())
+ return;
+
+ this.updateList(props);
+ }
+
+ updateSelf() {
+ let { voteAppToken } = localStorage;
+ if(voteAppToken) {
+ this.setState({
+ isFetchingSelf: true
+ });
+ api.getSelf(voteAppToken).catch(e => {
+ this.setState({
+ selfInfo: null,
+ isFetchingSelf: false
+ });
+ }).then(result => {
+ this.setState({
+ selfInfo: result,
+ isFetchingSelf: false
+ });
+ });
+ }
+ }
+
+ updateList(props = this.props) {
+ let { name } = props;
+ let { voteAppToken } = localStorage;
+ this.setState({
+ isFetchingList: true
+ });
+ api.getList(voteAppToken, name).catch(e => {
+ this.setState({
+ listInfo: null,
+ isFetchingList: false
+ });
+ }).then(result => {
+ this.setState({
+ listInfo: result,
+ isFetchingList: false
+ });
+ });
+ }
+
+ localVote(itemId, voteName, diffValue, currencyName, score) {
+ let { selfInfo, listInfo } = this.state;
+ this.setState({
+ isVoting: this.state.isVoting + 1,
+ listInfo: listInfo && {
+ ...listInfo,
+ items: updateByProperty(listInfo.items, "id", itemId, item => ({
+ ...item,
+ votes: updateByProperty(item.votes, "name", voteName, vote => ({
+ ...vote,
+ votes: vote.votes + diffValue
+ })),
+ userVotes: updateByProperty(item.userVotes, "name", voteName, vote => ({
+ ...vote,
+ votes: vote.votes + diffValue
+ })),
+ score: item.score + score * diffValue
+ }))
+ },
+ selfInfo: selfInfo && {
+ ...selfInfo,
+ currencies: updateByProperty(selfInfo.currencies, "name", currencyName, currency => ({
+ ...currency,
+ used: currency.used + diffValue,
+ remaining: currency.remaining - diffValue
+ }))
+ }
+ });
+ }
+
+ vote(itemId, voteName, diffValue, currencyName, score) {
+ if(!diffValue) return;
+ this.localVote(itemId, voteName, diffValue, currencyName, score);
+ let { voteAppToken } = localStorage;
+ api.vote(voteAppToken, itemId, voteName, diffValue).catch(e => {
+ console.error(e);
+ // revert local vote
+ this.localVote(itemId, voteName, -diffValue, currencyName, score);
+ this.setState({
+ isVoting: this.state.isVoting - 1
+ });
+ }).then(() => {
+ this.setState({
+ isVoting: this.state.isVoting - 1
+ });
+ });
+ }
+
+ render() {
+ let { name } = this.props;
+
+ if(!this.isBrowserSupported())
+ return Your browser is not supported.
;
+
+ let { selfInfo, listInfo, isVoting, isFetchingList, isFetchingSelf, isCreating, isLoginActive } = this.state;
+
+ let { voteAppToken } = localStorage;
+
+ if(isLoginActive) {
+ return Logging in...
;
+ }
+
+ const inProgress = isFetchingList || isFetchingSelf || isCreating || isVoting;
+
+ let maxVoteInfo = listInfo && listInfo.possibleVotes.map(() => 0);
+
+ if(listInfo) listInfo.items.forEach(item => {
+ if(item.userVotes) {
+ maxVoteInfo.forEach((max, idx) => {
+ let votes = item.userVotes[idx].votes;
+ if(votes > max)
+ maxVoteInfo[idx] = votes;
+ });
+ }
+ });
+ listInfo && console.log(listInfo);
+ return (
+
+
+
+
+
+
+
+
+
+ DISCLAIMER: Since this feature is its Alpha stages, the formula for calculating influence may change.
+
+
+
+ {this.renderSelf(inProgress)}
+
+
+
+ { listInfo &&
+
{listInfo.displayName}
+
{listInfo.description}
+
+ { listInfo.items.map(item =>
+
+
+
{item.score}
+ {listInfo.possibleVotes.map((voteSettings, idx) => {
+ let vote = item.votes[idx];
+ let userVote = item.userVotes && item.userVotes[idx];
+ let currencyInfo = selfInfo && voteSettings.currency && this.findByName(selfInfo.currencies, voteSettings.currency);
+ let maximum = voteSettings.maximum || 1000; // infinity
+ let minimum = voteSettings.minimum || 0;
+ let value = (userVote && userVote.votes) ? userVote.votes: 0;
+ if(currencyInfo && currencyInfo.remaining + value < maximum) maximum = currencyInfo.remaining + value;
+ return
+ {
+ this.vote(item.id, voteSettings.name, diffValue, voteSettings.currency, voteSettings.score);
+ }}
+ />
+
;
+ })}
+
+
+ {item.title}
+ {item.description}
+
+
+ )}
+ { listInfo.isAdmin &&
+ this.setState({newTitle: e.target.value})} />
+
+ {
+ const { newTitle, newDescription } = this.state;
+ if(newTitle && newDescription) {
+ this.setState({
+ isCreating: true
+ });
+ api.createItem(voteAppToken, name, newTitle, newDescription).then(item => {
+ this.setState({
+ newTitle: "",
+ newDescription: "",
+ isCreating: false,
+ listInfo: listInfo && {
+ ...listInfo,
+ items: [
+ ...listInfo.items,
+ item
+ ]
+ }
+ });
+ });
+ }
+ }}>Create Item
+ }
+
+
}
+
+ );
+ }
+
+ renderSelf (inProgress) {
+ let { listInfo, selfInfo, isFetchingSelf } = this.state;
+ if(!selfInfo) {
+ if(isFetchingSelf) {
+ return Loading user info...
;
+ }
+ return {
+ api.startLogin(window.location + "");
+ }}>Login with Github ;
+ } else {
+ return
+
+
+ {selfInfo.login}
+
+ { listInfo &&
+ { selfInfo.currencies
+ .filter(currency => listInfo.possibleVotes.some(voteSettings => voteSettings.currency === currency.name))
+ .map(currency =>
+ {currency.remaining} {currency.displayName}
+ ) }
+ }
+
+ {
+ delete window.localStorage.voteAppToken;
+ window.location.reload();
+ }}>Logout
+ {
+ this.updateSelf();
+ this.updateList();
+ }}>Update
+
+
;
+ }
+ }
+
+ findByName(array, name) {
+ for(var i = 0; i < array.length; i++)
+ if(array[i].name === name)
+ return array[i];
+ return null;
+ }
+
+ getNiceVoteValues(maximum) {
+ var arr = [];
+ var b = true;
+ for(var x = 1; x < maximum; x *= b ? 5 : 2, b = !b) {
+ arr.push(x);
+ }
+ if(maximum)
+ arr.push(maximum);
+ return arr;
+ }
+
+ getStep(maximum) {
+ return Math.floor(maximum / 20) * 2 || 1;
+ }
+
+ getColor(name) {
+ switch(name) {
+ case "influence": return "blue";
+ case "golden": return "#bfa203";
+ case "thumb": return "#535353";
+ }
+ return undefined;
+ }
+}
diff --git a/docs/components/vote/button/button-style.scss b/docs/components/vote/button/button-style.scss
new file mode 100644
index 000000000..7a3419a7c
--- /dev/null
+++ b/docs/components/vote/button/button-style.scss
@@ -0,0 +1,57 @@
+@import 'vars';
+@import 'mixins';
+@import 'functions';
+
+.vote-button {
+ text-align: center;
+ &__value {
+ font-size: 150%;
+ }
+
+ &__value, &__my-value, &__upDown {
+ display: block;
+ width: 90px;
+ }
+
+ &__upDown {
+ border: 0;
+ padding: 0;
+ margin: 0;
+ }
+}
+
+.vote-new-button {
+ display: flex;
+ justify-content: center;
+
+ @include break(medium) {
+ justify-content: inherit;
+ }
+
+ &__arrows {
+ flex: 0 0 10%;
+ }
+
+ &__value {
+ font-size: 15pt;
+ }
+
+ &__value, &__my-value{
+ display: inline;
+ margin-left: 5px;
+ align-self: center;
+ }
+
+ &__upDown {
+ border: 0;
+ padding: 0;
+ margin: 0;
+ cursor: pointer;
+ }
+
+ &__logout-value {
+ align-self: center;
+ font-size: 20pt;
+ margin: auto;
+ }
+}
\ No newline at end of file
diff --git a/docs/components/vote/button/button.jsx b/docs/components/vote/button/button.jsx
new file mode 100644
index 000000000..bb49e903b
--- /dev/null
+++ b/docs/components/vote/button/button.jsx
@@ -0,0 +1,132 @@
+import React, {Component} from 'react';
+
+export default class NewButton extends Component {
+ handleClick (n) {
+ const {maxUp, maxDown, onVote} = this.props;
+ onVote(Math.min(maxUp, Math.max(n, -maxDown)));
+ return false;
+ }
+
+ titleText (n, maxUp, maxDown) {
+ n = Math.min(maxUp, Math.max(n, -maxDown));
+ if(n === 0)
+ return "";
+ return n > 0 ? "+" + n : "" + n;
+ }
+
+ makeTriangle (n, fn, size, minForEnabled, increase) {
+ const {maxUp, maxDown, color} = this.props;
+ const enabled = n !== 0 && (n > 0 ? (maxUp >= minForEnabled) : (maxDown >= minForEnabled));
+ const className = "vote-new-button__upDown";
+
+ if(enabled) {
+ return this.handleClick(n)}
+ onMouseDown={() => this.startCounter(increase)}
+ onMouseUp={() => this.stopCounter()}
+ onMouseOut={() => this.stopCounter()}
+ onTouchStart={() => this.startCounter(increase)}
+ onTouchEnd={() => this.stopCounter()}
+ onTouchCancel={() => this.stopCounter()}
+ className={className}
+ >
+ {fn({size: size, color: color})}
+ ;
+ } else {
+ return
+ {fn({size: size, color: "#eee"})}
+ ;
+ }
+ }
+
+ startCounter(increase) {
+ let current = 0;
+ let add = 0;
+ const that = this;
+
+ if (this.interval) {
+ clearInterval(this.interval);
+ }
+
+ this.interval = setInterval(function() {
+ // increase for 1 between 0 and 5
+ if(current <= 5) {
+ current++;
+ add = 1;
+ }
+ // increase for 2 between 6 and 10
+ else if(current <= 10) {
+ current+=2;
+ add = 2;
+ }
+ // increase for 5 between 11 and 40
+ else if(current <= 40) {
+ current+=5;
+ add = 5;
+ }
+ // increase for 10 between 41 and 70
+ else if(current <= 70) {
+ current+=10;
+ add = 10;
+ }
+ // increase for 15 after 71
+ else {
+ current+=15;
+ add = 15;
+ }
+
+ if(!increase) {
+ add *= -1;
+ }
+
+ that.handleClick(add);
+ }, 200);
+ }
+
+ stopCounter() {
+ if (this.interval) {
+ clearInterval(this.interval);
+ }
+ }
+
+ render() {
+ const {color, className, value, myValue, isLoggedIn} = this.props;
+ return isLoggedIn ? (
+
+ {this.makeTriangle(1, triangleUp, 10, 1, true)}
+ {this.makeTriangle(-1, triangleDown, 10, 1, false)}
+
+
+ {value}
+
+
+ ({myValue} )
+
+
): ();
+ }
+}
+
+function triangleUp({color, size}) {
+ let path = `m ${size},0 -${size},${size / 3 * 2} ${size*2},0 z`;
+ return
+
+ ;
+}
+
+function triangleDown({color, size}) {
+ let path = `m ${size},${size / 3 * 2} ${size},-${size / 3 * 2} -${size*2},0 z`;
+ return
+
+ ;
+}
\ No newline at end of file
diff --git a/docs/components/vote/influence-style.scss b/docs/components/vote/influence-style.scss
new file mode 100644
index 000000000..7e9eba35b
--- /dev/null
+++ b/docs/components/vote/influence-style.scss
@@ -0,0 +1,19 @@
+@import 'mixins';
+@import 'functions';
+
+.influence-info {
+ em {
+ font-weight: bolder;
+ }
+
+ i {
+ font-style: italic;
+ }
+
+ &__section {
+ padding: 0.5em 0;
+ @include break(medium) {
+ padding: 0 .5em;
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/components/vote/influence.jsx b/docs/components/vote/influence.jsx
new file mode 100644
index 000000000..e2ada69dd
--- /dev/null
+++ b/docs/components/vote/influence.jsx
@@ -0,0 +1,21 @@
+import React from 'react';
+
+export default class InfluenceComponent extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ return (this.props.type === "normal" ? (
+
+ Influence
+ Influence is a unit of measure based on time you have been a member on GitHub. However, in 2017 and on you will recieve one influence per day.
+
+ ) : (
+
+ Golden Influence
+ Golden Influence is equal to 100 normal influence . Golden Influence is obtained by being a backer or sponsor on our Open Collective page .
+
+ ));
+ }
+}
diff --git a/docs/components/vote/list-style.scss b/docs/components/vote/list-style.scss
new file mode 100644
index 000000000..8aa896a54
--- /dev/null
+++ b/docs/components/vote/list-style.scss
@@ -0,0 +1,4 @@
+@import 'vars';
+@import 'mixins';
+@import 'functions';
+
diff --git a/docs/components/vote/list.jsx b/docs/components/vote/list.jsx
new file mode 100644
index 000000000..edc66e91e
--- /dev/null
+++ b/docs/components/vote/list.jsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import Interactive from 'antwar-interactive';
+import Container from '../container/container';
+import VoteApp from './app';
+import '../../styles';
+import './list-style';
+import './app-style';
+import './influence-style';
+import './button/button-style';
+
+export default ({ section, page }) => {
+ let arr = page.url.split('/');
+ let name = arr[arr.length - 1];
+
+ return (
+
+
+
+
+ );
+};
diff --git a/docs/Contributing/00_title.md b/docs/content/Contributing/00_title.md
similarity index 100%
rename from docs/Contributing/00_title.md
rename to docs/content/Contributing/00_title.md
diff --git a/docs/Contributing/01_Commit_Style.md b/docs/content/Contributing/01_Commit_Style.md
similarity index 100%
rename from docs/Contributing/01_Commit_Style.md
rename to docs/content/Contributing/01_Commit_Style.md
diff --git a/docs/content/contribute.md b/docs/content/contribute.md
new file mode 100644
index 000000000..3b5bdabd7
--- /dev/null
+++ b/docs/content/contribute.md
@@ -0,0 +1,16 @@
+---
+title: Contribute
+---
+
+> How to contribute to the project? List ways/means here.
+
+## Documentation
+
+[Writer's guide](/writers-guide)
+
+## Technical Contribution
+
+
+
+## Donation
+
diff --git a/docs/03_Deployment/00_title.md b/docs/content/deployment/index.md
similarity index 73%
rename from docs/03_Deployment/00_title.md
rename to docs/content/deployment/index.md
index c5cd532e2..5db3b6880 100644
--- a/docs/03_Deployment/00_title.md
+++ b/docs/content/deployment/index.md
@@ -1,3 +1,8 @@
+---
+title: Deployment
+sort: 0
+---
+
[STUB]
# Deployment
@@ -10,8 +15,10 @@
3. On your server, unzip your built files somewhere on the disk. Ex: in a `myproj` folder.
-4. `$ cd myproj`
-
-5. `$ npm install --production`
+4. ```bash
+$ cd myproj
+```
-6. `$ NODE_ENV=production node ./server`
+5. ```
+$ NODE_ENV=production node ./server
+```
diff --git a/docs/02_Developing/01_Adding_a_Route.md b/docs/content/developing/01_Adding_a_Route.md
similarity index 100%
rename from docs/02_Developing/01_Adding_a_Route.md
rename to docs/content/developing/01_Adding_a_Route.md
diff --git a/docs/02_Developing/00_Starting_Up.md b/docs/content/developing/index.md
similarity index 99%
rename from docs/02_Developing/00_Starting_Up.md
rename to docs/content/developing/index.md
index 0ce7a5187..64ab077a1 100644
--- a/docs/02_Developing/00_Starting_Up.md
+++ b/docs/content/developing/index.md
@@ -1,3 +1,8 @@
+---
+title: Starting Up
+sort: 0
+---
+
# Starting your app
Now that you've gone through everything in the Getting Started section, lets get our app up and running. We do this by running the following:
diff --git a/docs/Examples/Heroku_Deployment.md b/docs/content/examples/Heroku_Deployment.md
similarity index 94%
rename from docs/Examples/Heroku_Deployment.md
rename to docs/content/examples/Heroku_Deployment.md
index 2d7cda3b7..f7f545a32 100644
--- a/docs/Examples/Heroku_Deployment.md
+++ b/docs/content/examples/Heroku_Deployment.md
@@ -1,3 +1,7 @@
+---
+title: Heroku Deploy
+---
+
After you have generated the app, go to heroku.com and create and application manually (ex. foo-bar-42424). Then, starting frome the root folder, run the following commands:
* `grunt build`
diff --git a/docs/Examples/IIS.md b/docs/content/examples/IIS.md
similarity index 99%
rename from docs/Examples/IIS.md
rename to docs/content/examples/IIS.md
index 245121b0f..d55a26afe 100644
--- a/docs/Examples/IIS.md
+++ b/docs/content/examples/IIS.md
@@ -1,3 +1,7 @@
+---
+title: IIS
+---
+
# Angular Full-Stack Generator deployment on Windows
This is a walk through to get generator-angular-fullstack up and running on a windows machine.
This walk through has been tested on:
diff --git a/docs/content/examples/index.md b/docs/content/examples/index.md
new file mode 100644
index 000000000..666a22fd0
--- /dev/null
+++ b/docs/content/examples/index.md
@@ -0,0 +1,6 @@
+---
+title: Examples
+sort: 0
+---
+
+[STUB]
diff --git a/docs/generators/app.md b/docs/content/generators/app.md
similarity index 97%
rename from docs/generators/app.md
rename to docs/content/generators/app.md
index f1e2f2159..883ae746b 100644
--- a/docs/generators/app.md
+++ b/docs/content/generators/app.md
@@ -1,3 +1,8 @@
+---
+title: App
+sort: 1
+---
+
### App
Sets up a new AngularJS + Express app, generating all the boilerplate you need to get started.
diff --git a/docs/generators/component.md b/docs/content/generators/component.md
similarity index 92%
rename from docs/generators/component.md
rename to docs/content/generators/component.md
index 3f432586c..d9c6b58e3 100644
--- a/docs/generators/component.md
+++ b/docs/content/generators/component.md
@@ -1,3 +1,7 @@
+---
+title: Component
+---
+
### Component
Generates an Angular 1.5 component.
diff --git a/docs/generators/controller.md b/docs/content/generators/controller.md
similarity index 92%
rename from docs/generators/controller.md
rename to docs/content/generators/controller.md
index 54d6ef9f4..2f0da81ea 100644
--- a/docs/generators/controller.md
+++ b/docs/content/generators/controller.md
@@ -1,3 +1,7 @@
+---
+title: Controller
+---
+
### Controller
Generates a controller.
diff --git a/docs/generators/decorator.md b/docs/content/generators/decorator.md
similarity index 92%
rename from docs/generators/decorator.md
rename to docs/content/generators/decorator.md
index 7410f2b55..8c6e5d90b 100644
--- a/docs/generators/decorator.md
+++ b/docs/content/generators/decorator.md
@@ -1,3 +1,7 @@
+---
+title: Decorator
+---
+
### Decorator
Generates an AngularJS service decorator.
diff --git a/docs/generators/directive.md b/docs/content/generators/directive.md
similarity index 97%
rename from docs/generators/directive.md
rename to docs/content/generators/directive.md
index 90ffe6331..e276d7ba5 100644
--- a/docs/generators/directive.md
+++ b/docs/content/generators/directive.md
@@ -1,3 +1,7 @@
+---
+title: Directive
+---
+
### Directive
Generates a directive.
diff --git a/docs/generators/endpoint.md b/docs/content/generators/endpoint.md
similarity index 97%
rename from docs/generators/endpoint.md
rename to docs/content/generators/endpoint.md
index ceff3bf07..eeb891dac 100644
--- a/docs/generators/endpoint.md
+++ b/docs/content/generators/endpoint.md
@@ -1,3 +1,7 @@
+---
+title: Endpoint
+---
+
### Endpoint
Generates a new API endpoint.
diff --git a/docs/generators/filter.md b/docs/content/generators/filter.md
similarity index 93%
rename from docs/generators/filter.md
rename to docs/content/generators/filter.md
index 078a152e9..3d4c7919c 100644
--- a/docs/generators/filter.md
+++ b/docs/content/generators/filter.md
@@ -1,3 +1,7 @@
+---
+title: Filter
+---
+
### Filter
Generates a filter.
diff --git a/docs/generators/heroku.md b/docs/content/generators/heroku.md
similarity index 98%
rename from docs/generators/heroku.md
rename to docs/content/generators/heroku.md
index 5ab5e92c5..0007b151e 100644
--- a/docs/generators/heroku.md
+++ b/docs/content/generators/heroku.md
@@ -1,3 +1,7 @@
+---
+title: Heroku
+---
+
### Heroku
#### Setup
diff --git a/docs/content/generators/index.md b/docs/content/generators/index.md
new file mode 100644
index 000000000..0204e08cc
--- /dev/null
+++ b/docs/content/generators/index.md
@@ -0,0 +1,6 @@
+---
+title: Generators
+sort: 0
+---
+
+List of generators and subgenerators.
diff --git a/docs/generators/openshift.md b/docs/content/generators/openshift.md
similarity index 96%
rename from docs/generators/openshift.md
rename to docs/content/generators/openshift.md
index 6577eab83..c30c8320d 100644
--- a/docs/generators/openshift.md
+++ b/docs/content/generators/openshift.md
@@ -1,4 +1,8 @@
-###Openshift
+---
+title: Openshift
+---
+
+### Openshift
> Note: Openshift uses a quite old version of Node by default. We strongly recommend updating your Node version. [Here's a helpful article](https://blog.openshift.com/any-version-of-nodejs-you-want-in-the-cloud-openshift-does-it-paas-style/).
diff --git a/docs/generators/route.md b/docs/content/generators/route.md
similarity index 96%
rename from docs/generators/route.md
rename to docs/content/generators/route.md
index 682542eef..eae2d118c 100644
--- a/docs/generators/route.md
+++ b/docs/content/generators/route.md
@@ -1,3 +1,8 @@
+---
+title: Route
+sort: 2
+---
+
### Route
Generates a new route.
diff --git a/docs/generators/service.md b/docs/content/generators/service.md
similarity index 93%
rename from docs/generators/service.md
rename to docs/content/generators/service.md
index 4d79f88c5..f295696f8 100644
--- a/docs/generators/service.md
+++ b/docs/content/generators/service.md
@@ -1,3 +1,8 @@
+---
+title: Service
+sort: 3
+---
+
### Service
Generates an AngularJS service.
diff --git a/docs/01_Getting_Started/01_Prerequisites.md b/docs/content/get-started/index.md
similarity index 98%
rename from docs/01_Getting_Started/01_Prerequisites.md
rename to docs/content/get-started/index.md
index b210ffa31..343662e67 100644
--- a/docs/01_Getting_Started/01_Prerequisites.md
+++ b/docs/content/get-started/index.md
@@ -1,3 +1,8 @@
+---
+title: Getting Started
+sort: 0
+---
+
## Prerequisites
### npm modules
diff --git a/docs/01_Getting_Started/02_Installation.md b/docs/content/get-started/installation.md
similarity index 97%
rename from docs/01_Getting_Started/02_Installation.md
rename to docs/content/get-started/installation.md
index 8edfb3acd..ece944257 100644
--- a/docs/01_Getting_Started/02_Installation.md
+++ b/docs/content/get-started/installation.md
@@ -1,3 +1,8 @@
+---
+title: Installation
+sort: 1
+---
+
## Installation
Run `yo angular-fullstack` (optionally passing an app name):
diff --git a/docs/01_Getting_Started/04_Project_Overview.md b/docs/content/get-started/overview.md
similarity index 99%
rename from docs/01_Getting_Started/04_Project_Overview.md
rename to docs/content/get-started/overview.md
index 6b8ac7a78..aca91c04c 100644
--- a/docs/01_Getting_Started/04_Project_Overview.md
+++ b/docs/content/get-started/overview.md
@@ -1,3 +1,8 @@
+---
+title: Project Overview
+sort: 3
+---
+
## Project Overview
What follows is an overfiew of the files/folders in your newly generated project.
diff --git a/docs/01_Getting_Started/03_Running_Your_New_App.md b/docs/content/get-started/running.md
similarity index 95%
rename from docs/01_Getting_Started/03_Running_Your_New_App.md
rename to docs/content/get-started/running.md
index 140d04528..ceaa4457e 100644
--- a/docs/01_Getting_Started/03_Running_Your_New_App.md
+++ b/docs/content/get-started/running.md
@@ -1,3 +1,8 @@
+---
+title: Running
+sort: 2
+---
+
## Running Your New App
You can start your new app by running `gulp serve`. This will do some preliminary things like clean out temporary
diff --git a/docs/content/guides/asdf.md b/docs/content/guides/asdf.md
new file mode 100644
index 000000000..794d27a29
--- /dev/null
+++ b/docs/content/guides/asdf.md
@@ -0,0 +1,3 @@
+---
+title: asdf
+---
diff --git a/docs/content/guides/index.md b/docs/content/guides/index.md
new file mode 100644
index 000000000..fe1bb6927
--- /dev/null
+++ b/docs/content/guides/index.md
@@ -0,0 +1,8 @@
+---
+title: Guides
+---
+
+* [Getting Started](/get-started)
+* [Developing](/developing)
+* [Deployment](/deployment)
+* [Examples](/examples)
diff --git a/docs/content/index.md b/docs/content/index.md
new file mode 100644
index 000000000..73cb731c2
--- /dev/null
+++ b/docs/content/index.md
@@ -0,0 +1,48 @@
+---
+title: The Angular Full-Stack Generator
+---
+## Install & run the generator.
+
+
+
+
+**Install Yeoman, generator-angular-fullstack, & the Gulp CLI**
+
+```bash
+$ npm install --global yo generator-angular-fullstack gulp-cli
+```
+
+
+
+**Make a new folder for your project, scope into it, and run the generator.**
+
+```bash
+$ mkdir example && cd $_
+$ yo angular-fullstack
+...
+```
+
+Answer the generator's questions to scaffold an app tailored to your preferences.
+
+
+
+
+## Get up and running
+
+
+
+
+**Start the development server**
+
+```bash
+$ gulp serve
+```
+
+Your web browser should open up to a page similar to the one shown to the right.
+
+
+
+
+
+
+
diff --git a/docs/package.json b/docs/package.json
new file mode 100644
index 000000000..85d741344
--- /dev/null
+++ b/docs/package.json
@@ -0,0 +1,107 @@
+{
+ "name": "webpack.js.org",
+ "version": "0.0.0",
+ "private": true,
+ "description": "The main site for all things Webpack.",
+ "homepage": "https://github.com/webpack/webpack.js.org",
+ "author": "Greg Venech",
+ "license": "CC BY",
+ "main": "n/a",
+ "keywords": [
+ "webpack",
+ "documentation",
+ "build",
+ "tool"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/angular-fullstack/generator-angular-fullstack.git"
+ },
+ "bugs": {
+ "url": "https://github.com/angular-fullstack/generator-angular-fullstack/issues"
+ },
+ "engines": {
+ "node": ">=6.9"
+ },
+ "scripts": {
+ "start": "npm run init:generated && node ./bootstrap.js",
+ "build": "npm run init:generated && rm -rf build/ && node ./bootstrap.js && npm run sitemap",
+ "build-test": "npm run build && http-server build/",
+ "deploy": "gh-pages -d build --repo git@github.com:angular-fullstack/angular-fullstack.github.io.git --branch master",
+ "fetch": "scripts/fetch.sh",
+ "init:generated": "mkdirp ./generated/loaders && mkdirp ./generated/plugins ",
+ "lint": "run-s lint:*",
+ "lint:links": "hyperlink build/index.html -r | tap-min",
+ "lint:js": "eslint . --ext .js --ext .jsx",
+ "lint:md": "eslint . --ext .md",
+ "lint:markdown": "markdownlint --config ./.markdownlintrc **/*.md *.md ./content/**/*.md",
+ "lint:social": "alex ./**/*.md",
+ "lint:prose": "cp .proselintrc ~/ && proselint content",
+ "test": "npm run lint ",
+ "sitemap": "cd build && sitemap-static --prefix=https://webpack.js.org/ > sitemap.xml"
+ },
+ "devDependencies": {
+ "alex": "^3.1.0",
+ "antwar": "0.8.1-alpha.078b5fbf",
+ "antwar-helpers": "0.8.1-alpha.078b5fbf",
+ "antwar-interactive": "0.8.1-alpha.078b5fbf",
+ "antwar-prevnext-plugin": "0.8.1-alpha.078b5fbf",
+ "async": "^2.1.2",
+ "autoprefixer": "^6.3.7",
+ "babel-core": "^6.10.4",
+ "babel-eslint": "^6.1.2",
+ "babel-loader": "^6.2.5",
+ "babel-plugin-transform-object-rest-spread": "^6.16.0",
+ "babel-preset-env": "^0.0.8",
+ "babel-preset-react": "^6.11.1",
+ "copy-webpack-plugin": "^4.0.1",
+ "css-loader": "^0.25.0",
+ "eslint": "3.6.0",
+ "eslint-loader": "^1.5.0",
+ "eslint-plugin-markdown": "^1.0.0-beta.2",
+ "extract-text-webpack-plugin": "^1.0.1",
+ "file-loader": "^0.9.0",
+ "fontgen-loader": "^0.2.1",
+ "gh-pages": "^0.12.0",
+ "github": "^5.2.3",
+ "html-webpack-plugin": "^2.22.0",
+ "http-server": "^0.9.0",
+ "hyperlink": "^2.5.0",
+ "json-loader": "^0.5.4",
+ "lodash": "^4.16.1",
+ "markdown-loader": "^0.1.7",
+ "markdownlint": "^0.2.0",
+ "markdownlint-cli": "^0.2.0",
+ "marked": "^0.3.6",
+ "mkdirp": "^0.5.1",
+ "modularscale-sass": "^2.1.1",
+ "moment": "^2.15.1",
+ "ncp": "^2.0.0",
+ "node-sass": "^3.9.3",
+ "npm-run-all": "^3.1.0",
+ "postcss-loader": "^0.13.0",
+ "prism-languages": "^0.3.1",
+ "prismjs": "^1.5.1",
+ "raw-loader": "^0.5.1",
+ "request": "^2.75.0",
+ "sass-loader": "^4.0.2",
+ "sitemap-static": "^0.3.1",
+ "style-loader": "^0.13.1",
+ "tap-min": "^1.1.0",
+ "url-loader": "^0.5.7",
+ "webpack": "^1.13.2",
+ "webpack-dev-server": "^1.16.1",
+ "webpack-merge": "^0.14.1",
+ "yaml-frontmatter-loader": "0.0.3"
+ },
+ "dependencies": {
+ "d3": "^4.2.7",
+ "filesize": "^3.3.0",
+ "preact": "^6.2.1",
+ "preact-compat": "^3.6.0",
+ "react": "^15.3.2",
+ "react-dom": "^15.3.2",
+ "react-router": "^2.8.1",
+ "whatwg-fetch": "^2.0.1"
+ }
+}
diff --git a/docs/scripts/deploy.sh b/docs/scripts/deploy.sh
new file mode 100644
index 000000000..c78e30449
--- /dev/null
+++ b/docs/scripts/deploy.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+# see https://gist.github.com/domenic/ec8b0fc8ab45f39403dd
+set -e # Exit with nonzero exit code if anything fails
+
+SOURCE_BRANCH="master"
+
+# Pull requests and commits to other branches shouldn't try to deploy, just build to verify
+if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "$SOURCE_BRANCH" ]; then
+ echo "Skipping deploy; just doing a build and linting links/prose/js."
+ npm run build
+ # npm run fetch - Relies on third party files, disabled for now
+ npm run lint:js
+ npm run lint:prose
+ npm run lint:links
+ exit 0
+fi
+
+# Save some useful information
+REPO=`cd .. && git config remote.origin.url`
+SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:}
+
+# Fetch loaders/plugins etc.
+npm run fetch
+
+# Run our build
+npm run build
+
+# Set some git options
+git config --global user.name "Travis CI"
+git config --global user.email "ci@travis-ci.org"
+git remote set-url origin "${SSH_REPO}"
+
+# Get the deploy key by using Travis's stored variables to decrypt deploy_key.enc
+ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key"
+ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv"
+ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR}
+ENCRYPTED_IV=${!ENCRYPTED_IV_VAR}
+openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in scripts/deploy_key.enc -out scripts/deploy_key -d
+chmod 600 scripts/deploy_key
+eval `ssh-agent -s`
+ssh-add scripts/deploy_key
+
+# Now that we're all set up, we can deploy
+npm run deploy
diff --git a/docs/scripts/deploy_key.enc b/docs/scripts/deploy_key.enc
new file mode 100644
index 000000000..3d5bf354c
Binary files /dev/null and b/docs/scripts/deploy_key.enc differ
diff --git a/docs/scripts/deploy_key.pub b/docs/scripts/deploy_key.pub
new file mode 100644
index 000000000..f1d931d3d
--- /dev/null
+++ b/docs/scripts/deploy_key.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDGpHVwGQCrfjaNmbFEfRRNZe7FUDvGH2yCJqaZTx6TmmizsimQfsmt7KyHDiuJs83/smJoFs+/4W73SRPL2JspcvRHlIVv2O2ixfo71jq0HPXnVVpmCBKN3brSMDFxTX3EqN0xX39N9cUanH86B1GY8vWwTKq2YHxuzRLoS6pD0bDmsXLV9Vl5wgMFskhXyrKNssCGoaAFRCoikKL79n6t1UxqVIJTSMQE+hTOCfdPVR/xvMfo8z7p5DUGeG/OCHkKOGyfXsDoyG5kg2/vLvEhCgLNIpS1OjuuQpnA4eRcppni4lHYAGuqH8IDRv+bLLS9tguacVuRIIiDEW4A81RKtipP103gUsrnEUSgRffAYmPUQBD2j6mD9NM8micaBbiUQ7FQD7iwZXf3Flwpt3w9lV+/QGOkUGNG1MZQDUZ6CCnVzHo/zo8pEiBNqMkHHNJn97T9ObZT/dgs6PgfbE0Ex4QSz1gxKEuVXcAM9Jb+5fteIs56NFPXgvd50CtHJJACpYtWZkgweluGQl1BSzO2bXfW2SOY4e75sGSJo5P+qH14cwpwAMila9W65lKXZ26Bfjlj+9lLpopP+FSW1VaoKv0TbNhk7eoaGbhCR8EYhhGV+1f2yt+ajPz22bbV44r6ojRGcKdAzgIVxeVTNZDb0usQ6ie+UWrW30e51EOKGw== travis
diff --git a/docs/scripts/fetch.sh b/docs/scripts/fetch.sh
new file mode 100644
index 000000000..0665de671
--- /dev/null
+++ b/docs/scripts/fetch.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -e # Exit with nonzero exit code if anything fails
+
+rm -rf ./generated
+mkdir -p ./generated/loaders
+cp -rf ./content/loaders/ ./generated/loaders
+mkdir -p ./generated/plugins
+cp -rf ./content/plugins/ ./generated/plugins
+
+# Fetches github.com/webpack/*-loader repositories
+./scripts/fetch_package_names.js "-loader" | ./scripts/fetch_package_files.js "README.md" "./generated/loaders"
+
+# Fetches github.com/webpack/*-webpack-plugin repositories
+./scripts/fetch_package_names.js "-webpack-plugin" | ./scripts/fetch_package_files.js "README.md" "./generated/plugins"
diff --git a/docs/scripts/fetch_package_files.js b/docs/scripts/fetch_package_files.js
new file mode 100644
index 000000000..96876a0ba
--- /dev/null
+++ b/docs/scripts/fetch_package_files.js
@@ -0,0 +1,115 @@
+#!/usr/bin/env node
+// ./fetch_package_files < input
+// ./fetch_package_files "README.md" "./output" < input
+const fs = require('fs');
+const path = require('path');
+const async = require('async');
+const mkdirp = require('mkdirp');
+const request = require('request');
+
+if (require.main === module) {
+ main();
+} else {
+ module.exports = fetchPackageFiles;
+}
+
+function main() {
+ const file = process.argv[2];
+ const output = process.argv[3];
+
+ if(!file) {
+ return console.error('Missing file!');
+ }
+
+ if(!output) {
+ return console.error('Missing output!');
+ }
+
+ mkdirp.sync(output);
+
+ const stdin = process.openStdin();
+ var input = '';
+
+ stdin.setEncoding('utf8');
+ stdin.on('data', function(d) {
+ input += d;
+ });
+ stdin.on('end', function() {
+ fetchPackageFiles({
+ input: JSON.parse(input),
+ file: file,
+ output: path.resolve(process.cwd(), output),
+ limit: 4
+ }, function(err, d) {
+ if (err) {
+ return console.error(err);
+ }
+
+ console.log('Fetched ' + d.length + ' files');
+ });
+ });
+}
+
+function fetchPackageFiles(options, finalCb) {
+ const file = options.file;
+
+ async.mapLimit(
+ options.input,
+ options.limit,
+ function(pkg, cb) {
+ const branch = 'master';
+ const url = ['https://raw.githubusercontent.com', pkg.full_name, branch, file].join('/');
+
+ request(url, function(err, response, body) {
+ if (err) {
+ return cb(err);
+ }
+
+ if (body && file === 'README.md') {
+ body = body
+ .replace(/^[^]*?]*>/m, '## ') // drop everything up to first
+ .replace(/]*>/g, '## ') // replace any with ##
+ .replace(/<\/h2>/g, ''); // drop
+ }
+
+ // TODO: push this type of to a script of its own to keep this generic
+ let headmatter = yamlHeadmatter({
+ title: pkg.name,
+ source: url,
+ edit: [pkg.html_url, 'edit', branch, file].join('/'),
+ });
+ return async.parallel(
+ [
+ fs.writeFile.bind(null,
+ path.resolve(options.output, pkg.name + path.extname(file)),
+ headmatter + body
+ ),
+ fs.writeFile.bind(null,
+ path.resolve(options.output, pkg.name + '.json'),
+ JSON.stringify(pkg, null, 2)
+ )
+ ],
+ function(err) {
+ if (err) {
+ return cb(err);
+ }
+
+ return cb(null, pkg);
+ }
+ );
+ });
+ },
+ finalCb
+ );
+}
+
+// TODO: push this type of to a script of its own to keep this generic
+function yamlHeadmatter(fields) {
+ var ret = '---\n';
+
+ Object.keys(fields).forEach(function(field) {
+ ret += field + ': ' + fields[field] + '\n';
+ });
+
+ return ret + '---\n';
+}
diff --git a/docs/scripts/fetch_package_names.js b/docs/scripts/fetch_package_names.js
new file mode 100644
index 000000000..77aa506bc
--- /dev/null
+++ b/docs/scripts/fetch_package_names.js
@@ -0,0 +1,47 @@
+#!/usr/bin/env node
+// ./fetch_package_names > output
+// ./fetch_package_names "-loader" > output.json
+const GitHubApi = require("github");
+
+if (require.main === module) {
+ main();
+} else {
+ module.exports = fetchPackageNames;
+}
+
+function main() {
+ const suffix = process.argv[2];
+
+ if(!suffix) {
+ return console.error('Missing suffix!');
+ }
+
+ fetchPackageNames({
+ organization: 'webpack',
+ suffix: suffix
+ }, function(err, d) {
+ if (err) {
+ return console.error(err);
+ }
+
+ console.log(JSON.stringify(d, null, 4));
+ });
+}
+
+function fetchPackageNames(options, cb) {
+ const github = new GitHubApi();
+
+ // XXX: weak since this handles only one page
+ github.repos.getForOrg({
+ org: options.organization,
+ per_page: 100
+ }, function (err, d) {
+ if (err) {
+ return cb(err);
+ }
+
+ return cb(null, d.filter(function(o) {
+ return o.name.endsWith(options.suffix);
+ }));
+ });
+}
diff --git a/docs/styles/fonts.scss b/docs/styles/fonts.scss
new file mode 100644
index 000000000..d00cca87e
--- /dev/null
+++ b/docs/styles/fonts.scss
@@ -0,0 +1,7 @@
+@font-face {
+ font-family: 'Geomanist';
+ src: url('../assets/geomanist-medium.woff2') format('woff2'),
+ url('../assets/geomanist-medium.woff') format('woff');
+ font-weight: 600;
+ font-style: normal;
+}
\ No newline at end of file
diff --git a/docs/styles/homepage.scss b/docs/styles/homepage.scss
new file mode 100644
index 000000000..1b4cd15f6
--- /dev/null
+++ b/docs/styles/homepage.scss
@@ -0,0 +1,30 @@
+.homepage {
+ &__left,
+ &__right {
+ width: 100%;
+ float: left;
+
+ @include break {
+ width: 50%;
+ padding: 0 15px;
+ }
+ }
+
+ &__wrap {
+ display: block;
+
+ @include break {
+ margin: 0 -30px;
+ }
+
+ &:before,
+ &:after {
+ content: " ";
+ display: table;
+ }
+
+ &:after {
+ clear: both;
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/styles/icon.font.js b/docs/styles/icon.font.js
new file mode 100644
index 000000000..e614e58aa
--- /dev/null
+++ b/docs/styles/icon.font.js
@@ -0,0 +1,9 @@
+module.exports = {
+ files: [
+ './icons/*.svg'
+ ],
+ fontName: 'icons',
+ cssTemplate: './icon.template.hbs',
+ fixedWidth: true,
+ types: [ 'woff' ]
+};
diff --git a/docs/styles/icon.template.hbs b/docs/styles/icon.template.hbs
new file mode 100644
index 000000000..6def3a9ad
--- /dev/null
+++ b/docs/styles/icon.template.hbs
@@ -0,0 +1,20 @@
+@font-face {
+ font-family: "{{fontName}}";
+ src: {{{src}}};
+}
+
+[class*="icon-"] {
+ line-height: 1;
+}
+
+[class*="icon-"]:before {
+ font-family: {{fontName}} !important;
+ font-style: normal;
+ font-weight: normal !important;
+}
+
+{{#each codepoints}}
+.icon-{{@key}}:before {
+ content: "\\{{this}}";
+}
+{{/each}}
\ No newline at end of file
diff --git a/docs/styles/icons/chevron-down.svg b/docs/styles/icons/chevron-down.svg
new file mode 100644
index 000000000..1dae6bbfc
--- /dev/null
+++ b/docs/styles/icons/chevron-down.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/docs/styles/icons/chevron-left.svg b/docs/styles/icons/chevron-left.svg
new file mode 100644
index 000000000..b177827d7
--- /dev/null
+++ b/docs/styles/icons/chevron-left.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/docs/styles/icons/chevron-right.svg b/docs/styles/icons/chevron-right.svg
new file mode 100644
index 000000000..41cabd49c
--- /dev/null
+++ b/docs/styles/icons/chevron-right.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/docs/styles/icons/chevron-up.svg b/docs/styles/icons/chevron-up.svg
new file mode 100644
index 000000000..53e24a7ca
--- /dev/null
+++ b/docs/styles/icons/chevron-up.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/docs/styles/icons/cross.svg b/docs/styles/icons/cross.svg
new file mode 100644
index 000000000..fbe4911ff
--- /dev/null
+++ b/docs/styles/icons/cross.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/docs/styles/icons/edit.svg b/docs/styles/icons/edit.svg
new file mode 100644
index 000000000..e448d0ef7
--- /dev/null
+++ b/docs/styles/icons/edit.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/docs/styles/icons/github.svg b/docs/styles/icons/github.svg
new file mode 100644
index 000000000..0a0c0cb9d
--- /dev/null
+++ b/docs/styles/icons/github.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/styles/icons/gitter.svg b/docs/styles/icons/gitter.svg
new file mode 100644
index 000000000..f71e928ec
--- /dev/null
+++ b/docs/styles/icons/gitter.svg
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/styles/icons/link.svg b/docs/styles/icons/link.svg
new file mode 100644
index 000000000..6999b4722
--- /dev/null
+++ b/docs/styles/icons/link.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/styles/icons/magnifying-glass.svg b/docs/styles/icons/magnifying-glass.svg
new file mode 100644
index 000000000..518626e11
--- /dev/null
+++ b/docs/styles/icons/magnifying-glass.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/docs/styles/icons/medium.svg b/docs/styles/icons/medium.svg
new file mode 100644
index 000000000..a3cecb544
--- /dev/null
+++ b/docs/styles/icons/medium.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/styles/icons/menu.svg b/docs/styles/icons/menu.svg
new file mode 100644
index 000000000..dd93f1129
--- /dev/null
+++ b/docs/styles/icons/menu.svg
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/docs/styles/icons/stack-overflow.svg b/docs/styles/icons/stack-overflow.svg
new file mode 100644
index 000000000..4a7266920
--- /dev/null
+++ b/docs/styles/icons/stack-overflow.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/styles/index.scss b/docs/styles/index.scss
new file mode 100644
index 000000000..8e12733df
--- /dev/null
+++ b/docs/styles/index.scss
@@ -0,0 +1,89 @@
+/**
+ * Styling
+ *
+ * This file contains the base styling for the site.
+ *
+ */
+
+@import 'vars';
+@import 'fonts';
+@import 'functions';
+@import 'mixins';
+
+@import './reset';
+
+* {
+ box-sizing: inherit;
+}
+
+html {
+ box-sizing: border-box;
+}
+
+body {
+ font: 400 getFontSize(0) $font-stack-body;
+ color: getColor(elephant);
+}
+
+a {
+ color: $text-color-highlight;
+ text-decoration: none;
+ transition: color 250ms;
+
+ &.icon-link {
+ display: inline-block;
+ font-size: 0.7em;
+ margin-left: 16px;
+ transform: rotate(-45deg);
+ color:lighten(getColor(dusty-grey), 10%);
+
+ &:hover {
+ color: $text-color-highlight;
+ }
+ }
+
+ &:hover {
+ color: darken(getColor(denim), 5%);
+ }
+}
+
+details:focus, summary:focus{
+ outline: none;
+ background: rgba(255,255,255,0.03);
+ border-radius: 2px;
+}
+
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+ /* Style details arrow if on webkit */
+
+ details summary::-webkit-details-marker {
+ color: getColor(malibu);
+ }
+
+ summary::-webkit-details-marker {
+ display: none
+ }
+ summary:after {
+ content: "\F103";
+ float: left;
+ position: relative;
+ left: -2px;
+ text-align: center;
+ font-family: icons;
+ color: lighten(getColor(denim), 10%)
+ }
+
+ details[open] summary:after {
+ content: "\F101";
+ font-family: icons;
+ }
+}
+
+
+
+::selection {
+ background: transparentize(getColor(malibu), 0.65);
+}
+
+@import './markdown';
+@import './homepage';
\ No newline at end of file
diff --git a/docs/styles/markdown.scss b/docs/styles/markdown.scss
new file mode 100644
index 000000000..2d4e6ce4e
--- /dev/null
+++ b/docs/styles/markdown.scss
@@ -0,0 +1,234 @@
+// Markdown styling is based on https://gist.github.com/tuzz/3331384.
+@import 'vars';
+@import 'functions';
+@import 'prism-theme';
+
+.page__content,
+.splash__section {
+ line-height:1.5em;
+
+ h1 { font-size: getFontSize(4); }
+ h2 { font-size: getFontSize(3); }
+ h3 { font-size: getFontSize(2); }
+ h4 { font-size: getFontSize(1); }
+ h5 { font-size: getFontSize(0); }
+ h6 { font-size: getFontSize(-1); }
+
+ h1, h2, h3, h4, h5, h6 {
+ font-family: $font-stack-heading;
+ font-weight:600;
+ line-height:1.4;
+ margin:1.5em 0 0.25em;
+ color:getColor(fiord);
+
+ &:first-child { margin-top:0; }
+ tt, code { font-size: 90%; color: inherit }
+ }
+
+ p, blockquote, table, pre {
+ margin:1em 0;
+ }
+
+ ul, ol, dl {
+ margin:0.5em 0 1em;
+ }
+
+ li {
+ margin:0.5em 0;
+ }
+
+ hr {
+ border:none;
+ background-color:getColor(alto);
+ height:3px;
+ margin:2em 0;
+ }
+
+ ul, ol {
+ padding-left: 30px;
+
+ &:first-child { margin-top:0; }
+ &:last-child { margin-bottom:0; }
+ }
+
+ dl {
+ dt {
+ font-size: getFontSize(0);
+ font-weight: bold;
+ font-style: italic;
+ margin: 15px 0 5px;
+
+ &:first-child { padding: 0; }
+ }
+
+ dd {
+ margin: 0 0 15px;
+ padding: 0 15px;
+ }
+
+ dt, dd {
+ > :first-child { margin-top: 0; }
+ > :last-child { margin-bottom: 0; }
+ }
+ }
+
+ blockquote {
+ border-left: 4px solid #dddddd;
+ padding:0.75em 1em;
+ color:getColor(dove-grey);
+
+ > :first-child { margin-top: 0; }
+ > :last-child { margin-bottom: 0; }
+
+ &.tip,
+ &.warning,
+ &.todo {
+ border-left:none;
+ border-radius: 3px;
+
+ .tip-content {
+ font-style: italic;
+ }
+
+ code {
+ color: inherit;
+ }
+ }
+
+ &.tip {
+ background-color: #DCF2FD;
+ color: #618ca0;
+ }
+ &.warning {
+ background-color: #fbedb7;
+ color: #8c8466;
+ }
+ &.todo {
+ background-color: #fbddcd;
+ color: #907a6e;
+ }
+ }
+
+ table {
+ display:block;
+ width:100%;
+ overflow:auto;
+
+ tr {
+ border-top: 1px solid #cccccc;
+ background-color: white;
+ margin: 0;
+ padding: 0;
+
+ &:nth-child(2n) { background-color: #f8f8f8; }
+
+ th {
+ font-weight: bold;
+ border: 1px solid #cccccc;
+ text-align: left;
+ margin: 0;
+ padding: 6px 13px;
+ }
+
+ td {
+ border: 1px solid #cccccc;
+ text-align: left;
+ margin: 0;
+ padding: 6px 13px;
+
+ img {
+ max-width:none;
+ }
+ }
+
+ th, td {
+ white-space: nowrap;
+
+ > :first-child { margin-top: 0; }
+ > :last-child { margin-bottom: 0; }
+ }
+ }
+ }
+
+ img {
+ max-width: 100%;
+ }
+
+ b, strong {
+ font-weight:600;
+ }
+
+ code, tt {
+ font-family: $font-stack-code;
+ font-size: 90%;
+ margin: 0 2px;
+ padding: 2px 6px;
+ white-space: nowrap;
+ background-color: transparentize(getColor(fiord), 0.94);
+ border-radius: 3px;
+ text-shadow: 0 1px 0 transparentize(getColor(white), 0.4);
+ }
+
+ a code {
+ color: $text-color-highlight;
+ }
+
+ pre {
+ background-color: rgba(238, 238, 238, 0.35);
+ background-color: getColor(elephant);
+ font-size: 13px;
+ line-height: 19px;
+ overflow: auto;
+ padding: 8px 16px;
+ border-radius: 3px;
+
+ code {
+ margin: 0;
+ padding: 0;
+ white-space: pre;
+ border: none;
+ background: transparent;
+ text-shadow: 0 1px 0 transparentize(darken(getColor(elephant), 10%), 0.5);
+ color: desaturate(getColor(malibu), 40%);
+
+ .code-details-summary-span {
+ margin-left: -15px;
+ cursor: pointer;
+ }
+
+ a {
+ border-bottom: 1px dotted getColor(denim);
+ }
+
+ .code-link {
+ position: relative;
+
+ &:hover {
+ color: lighten(getColor(denim), 15%);
+ }
+ }
+ }
+
+ code, tt {
+ background-color: transparent;
+ border: none;
+ }
+ }
+
+ p {
+ code, tt {
+ display: inline-block;
+ max-width: 100%;
+ line-height: initial;
+ overflow: auto;
+ margin: 0;
+ vertical-align: middle;
+ }
+ }
+
+ span {
+ code, tt {
+ white-space: pre-line;
+ }
+ }
+}
diff --git a/docs/styles/partials/_functions.scss b/docs/styles/partials/_functions.scss
new file mode 100644
index 000000000..0cb9fa312
--- /dev/null
+++ b/docs/styles/partials/_functions.scss
@@ -0,0 +1,12 @@
+// Custom functions
+
+@import 'vars';
+@import '~modularscale-sass/stylesheets/modular-scale';
+
+@function getFontSize($step) {
+ @return ms($step, 16px, $minor-third)
+}
+
+@function getColor($name) {
+ @return map-get($colors, $name);
+}
diff --git a/docs/styles/partials/_mixins.scss b/docs/styles/partials/_mixins.scss
new file mode 100644
index 000000000..3028bc739
--- /dev/null
+++ b/docs/styles/partials/_mixins.scss
@@ -0,0 +1,5 @@
+@mixin break ($size: medium) {
+ @media (min-width: map-get($screens, $size)) {
+ @content;
+ }
+}
diff --git a/docs/styles/partials/_vars.scss b/docs/styles/partials/_vars.scss
new file mode 100644
index 000000000..bad3d97ab
--- /dev/null
+++ b/docs/styles/partials/_vars.scss
@@ -0,0 +1,25 @@
+$colors: (
+ malibu: #8DD6F9,
+ denim: #1D78C1,
+ fiord: #465E69,
+ elephant: #2B3A42,
+ white: #ffffff,
+ concrete: #f2f2f2,
+ alto: #dedede,
+ dusty-grey: #999999,
+ dove-grey: #666666,
+ emperor: #535353,
+ mine-shaft: #333333
+);
+
+$screens: (
+ large: 1024px,
+ medium: 768px
+);
+
+$font-stack-body: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
+$font-stack-heading: Geomanist, sans-serif;
+$font-stack-code: 'Source Code Pro', Consolas, "Liberation Mono", Menlo, Courier, monospace;
+
+$text-color-highlight: lighten(map-get($colors, denim), 5%);
+
diff --git a/docs/styles/prism-theme.scss b/docs/styles/prism-theme.scss
new file mode 100644
index 000000000..ce382db38
--- /dev/null
+++ b/docs/styles/prism-theme.scss
@@ -0,0 +1,109 @@
+@import 'functions';
+
+code[class*="lang-"],
+pre[class*="lang-"] {
+ text-align: left;
+ white-space: pre;
+ word-spacing: normal;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 1.5;
+ tab-size: 4;
+ hyphens: none;
+ //color: getColor(fiord);
+ color: desaturate(getColor(malibu), 40%);
+
+ a {
+ color: inherit;
+ }
+}
+
+/* Code blocks */
+pre[class*="lang-"] {
+ padding: .4em .8em;
+ margin: .5em 0;
+ overflow: auto;
+ //background-color: rgba(238,238,238,0.35);
+ background-color: getColor(elephant);
+}
+
+/* Inline code */
+:not(pre) > code[class*="lang-"] {
+ padding: .2em;
+ border-radius: .3em;
+ box-shadow: none;
+ white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+ color: #77858c;
+}
+
+.token.punctuation {
+ color: #e1e6e9;
+}
+
+.namespace {
+ opacity: .7;
+}
+
+.token.function{
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol {
+ color: desaturate(darken(getColor(malibu), 15%), 15%);
+}
+
+.token.selector,
+.token.string,
+.token.char,
+.token.builtin,
+.token.regex,
+.token.attr-value,
+.token.important {
+ color: desaturate(#2dd271, 25%)
+}
+
+.token.inserted {
+ color: #9df29d;
+}
+
+.token.deleted {
+ color: #f79494;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.toke.variable {
+ color: #a9becc;
+}
+
+.token.atrule,
+.token.attr-name,
+.token.keyword,
+.token.function {
+ color: darken(desaturate(getColor(malibu), 30%), 15%);
+}
+
+.token.important,
+.token.bold {
+ font-weight: bold;
+}
+
+.token.italic {
+ font-style: italic;
+}
+
+.token.entity {
+ cursor: help;
+}
diff --git a/docs/styles/reset.css b/docs/styles/reset.css
new file mode 100644
index 000000000..ab173a7e2
--- /dev/null
+++ b/docs/styles/reset.css
@@ -0,0 +1,4 @@
+/* Reset */
+
+html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0}
+*, *:before, *:after { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }
\ No newline at end of file
diff --git a/docs/template.ejs b/docs/template.ejs
new file mode 100644
index 000000000..44c991001
--- /dev/null
+++ b/docs/template.ejs
@@ -0,0 +1,35 @@
+
+
+
+
+ <%= webpackConfig.template.title %>
+
+
+
+
+
+
+
+ <% for (var file in webpackConfig.template.cssFiles) { %>
+
+ <% } %>
+
+
+
+
+ <%- webpackConfig.html %>
+
+ <% for (var script of webpackConfig.template.jsFiles) { %>
+
+ <% } %>
+
+
+
+
+
+
diff --git a/docs/utilities/highlight.js b/docs/utilities/highlight.js
new file mode 100644
index 000000000..fbf9be104
--- /dev/null
+++ b/docs/utilities/highlight.js
@@ -0,0 +1,32 @@
+'use strict';
+
+if(typeof document !== "undefined") {
+ // disable automatic highlight on content loaded
+ var script = document.currentScript || [].slice.call(document.getElementsByTagName("script")).pop();
+ script.setAttribute("data-manual", "");
+}
+
+var Prism = require('prismjs');
+var languages = require('prism-languages');
+
+var highlight = Prism.highlight;
+
+module.exports = function(code, language) {
+ language = language || 'bash';
+
+ if (language === 'sh' || language === 'text') {
+ language = 'bash';
+ }
+
+ try {
+ return highlight(code, languages[language]);
+
+ } catch (error) {
+ if (!languages[language]) {
+ console.warn('Prism does not support this language: ', language);
+
+ } else console.warn('Prism failed to highlight: ', error);
+ }
+
+ return code;
+};
diff --git a/docs/utilities/markdown.js b/docs/utilities/markdown.js
new file mode 100644
index 000000000..9936a8b1a
--- /dev/null
+++ b/docs/utilities/markdown.js
@@ -0,0 +1,228 @@
+'use strict';
+var marked = require('marked');
+
+module.exports = function(section) {
+ // alter marked renderer to add slashes to beginning so images point at root
+ // leanpub expects images without slash...
+ section = section ? '/' + section + '/' : '/';
+
+ var renderer = new marked.Renderer();
+
+ renderer.image = function(href, title, text) {
+ return ' ';
+ };
+
+ // patch ids (this.options.headerPrefix can be undefined!)
+ renderer.heading = function(text, level, raw) {
+ var id = raw.toLowerCase().replace(/`/g, '').replace(/[^\w]+/g, '-');
+
+ return `\n`;
+ };
+
+ var codeTemplate = renderer.code;
+
+ renderer.code = function(code, lang, escaped) {
+ var linksEnabled = false;
+ var detailsEnabled = false;
+ var links = [];
+
+ if (/-with-links/.test(lang)) {
+ linksEnabled = true;
+ lang = lang.replace(/-with-links/, "");
+ }
+
+ if (/-with-details/.test(lang)) {
+ detailsEnabled = true;
+ lang = lang.replace(/-with-details/, "");
+ }
+
+ if (linksEnabled) {
+ code = code.replace(/\[([^\[\]]+?)\]\((.+?)\)/g, match => {
+ match = /\[([^\[\]]+?)\]\((.+?)\)/.exec(match);
+ links.push('' + match[1] + ' ');
+ return "MARKDOWNLINK_" + (links.length - 1) + "_";
+ });
+ }
+
+ if (detailsEnabled) {
+ code = code.replace(//g, "MARKDOWNDETAILSSTART\n");
+ code = code.replace(/ *<\/details>(\n)?/g, "\nMARKDOWNDETAILSEND\n");
+ code = code.replace(//g, "\nMARKDOWNSUMMARYSTART\n");
+ code = code.replace(/ *<\/summary>/g, "\nMARKDOWNSUMMARYEND");
+ code = code.replace(/(?:)?( *)MARKDOWNDETAILSSTART([\s\S]*?)MARKDOWNSUMMARYSTART\n/g, "MARKDOWNDETAILSSTART$2MARKDOWNSUMMARYSTART\n$1");
+ }
+
+ var rendered = codeTemplate.call(this, code, lang, escaped);
+
+ if (linksEnabled) {
+ rendered = rendered.replace(/MARKDOWNLINK_(\d+)_/g, match => {
+ var idx = +(/MARKDOWNLINK_(\d+)_/.exec(match)[1]);
+ return links[idx];
+ });
+ }
+
+ if (detailsEnabled) {
+ rendered = rendered.replace(/MARKDOWNDETAILSSTART.*?\n/g, "");
+ rendered = rendered.replace(/\n.*?MARKDOWNDETAILSEND.*?\n/g, " ");
+ rendered = rendered.replace(/\n.*?MARKDOWNSUMMARYSTART.*?\n/g, "");
+ rendered = rendered.replace(/\n.*?MARKDOWNSUMMARYEND.*?\n/g, " ");
+ }
+
+ return rendered;
+ };
+
+ return {
+ process: function(content, highlight) {
+ var markedDefaults = {
+ gfm: true,
+ tables: true,
+ breaks: false,
+ pedantic: false,
+ sanitize: false,
+ sanitizer: null,
+ mangle: true,
+ smartLists: false,
+ silent: false,
+ highlight: highlight || false,
+ langPrefix: 'lang-',
+ smartypants: false,
+ headerPrefix: '',
+ renderer: renderer,
+ xhtml: false
+ };
+
+ var tokens = parseContent(content);
+ tokens.links = [];
+
+ return marked.parser(tokens, markedDefaults);
+ },
+
+ // Note that this should correspond with renderer.heading
+ getAnchors: function(content) {
+ return marked.lexer(content)
+ .filter(chunk => chunk.type === 'heading')
+ .map(chunk => ({
+ title: chunk.text.replace(/`/g, ''),
+ id: chunk.text.toLowerCase().replace(/`/g, '').replace(/[^\w]+/g, '-')
+ }));
+ }
+ };
+};
+
+function parseContent(data) {
+ var tokens = [];
+
+ marked.lexer(data).forEach(function(t) {
+ // add custom quotes
+ if (t.type === 'paragraph') {
+ var quote = parseCustomQuote(t, 'T>', 'tip') ||
+ parseCustomQuote(t, 'W>', 'warning') ||
+ parseCustomQuote(t, '?>', 'todo') ||
+ t;
+
+ tokens.push(quote);
+ }
+ // handle html
+ else if (t.type === 'html') {
+ tokens = tokens.concat(handleHTML(t));
+ }
+ // just add other types
+ else {
+ tokens.push(t);
+ }
+ });
+
+ return tokens;
+}
+
+function handleHTMLSplit(tokens, htmlArray, merging) {
+ const htmlItem = htmlArray[0];
+ htmlArray = htmlArray.slice(1);
+ const tickSplit = htmlItem.split('`');
+ const tickLength = tickSplit.length;
+
+ // detect start of the inline code
+ if(merging.length === 0 && tickLength%2 === 0) {
+ merging = htmlItem;
+ }
+ // append code inside the inline code
+ else if(merging.length > 0 && tickLength === 1) {
+ merging += htmlItem;
+ }
+ // finish inline code
+ else if(merging.length > 0 && tickLength > 1) {
+ htmlArray.unshift(tickSplit.slice(1, tickLength).join("`"));
+ merging += tickSplit[0]+"`";
+ tokens = tokens.concat(parseContent(merging));
+ merging = "";
+ } else if (merging.length === 0) {
+ tokens = tokens.concat(parseContent(htmlItem));
+ }
+
+ if(htmlArray.length === 0) {
+ return tokens;
+ }
+
+ return handleHTMLSplit(tokens, htmlArray, merging);
+}
+
+function handleHTML(t) {
+ let tokens = [];
+
+ // Split code in markdown, so that HTML inside code is not parsed
+ const codeArray = t.text.split(/(```(.|\n)*```)/g).filter(v => (v && v !== '' && v !== '\n'));
+
+ // if only one item in codeArray, then it's already parsed
+ if(codeArray.length == 1) {
+ return t;
+ }
+
+ codeArray.forEach(item => {
+ // if item is not code, then check for html tags and parse accordingly
+ if (item.indexOf('```') !== 0) {
+ // split all html tags
+ const htmlArray = item.split(/\s*(<[^>]*>)/g).filter(v => (v !== '' && v !== '\n'));
+ tokens = handleHTMLSplit(tokens, htmlArray, "");
+ }
+ // normally parse code block
+ else {
+ tokens = tokens.concat(parseContent(item));
+ }
+ });
+
+ return tokens;
+}
+
+function parseCustomQuote(token, match, className) {
+ if (token.type === 'paragraph') {
+ var text = token.text;
+
+ if (text.indexOf(match) === 0) {
+ // var icon;
+
+ // TODO: Update icons and styling
+ // switch(className) {
+ // case 'tip':
+ // icon = 'icon-info';
+ // break;
+ // case 'warning':
+ // icon = 'icon-warning';
+ // break;
+ // default:
+ // icon = 'icon-chevron-right';
+ // break;
+ // }
+
+ return {
+ type: 'html',
+ text: `` +
+ ` ${text.slice(2).trim()}
` +
+ ' '
+ };
+ }
+ }
+}
diff --git a/docs/webpack.config.js b/docs/webpack.config.js
new file mode 100644
index 000000000..b3606f46b
--- /dev/null
+++ b/docs/webpack.config.js
@@ -0,0 +1,162 @@
+var path = require('path');
+var ExtractTextPlugin = require('extract-text-webpack-plugin');
+var CopyWebpackPlugin = require('copy-webpack-plugin');
+var Autoprefixer = require('autoprefixer');
+var merge = require('webpack-merge');
+var webpack = require('webpack');
+
+var cwd = process.cwd();
+var stylePaths = [
+ path.join(cwd, 'styles'),
+ path.join(cwd, 'components')
+];
+
+const commonConfig = {
+ resolve: {
+ extensions: ['', '.js', '.jsx', '.scss']
+ },
+ module: {
+ loaders: [
+ {
+ test: /\.jsx?$/,
+ loaders: ['babel-loader', 'eslint-loader'],
+ include: [
+ path.join(__dirname, 'components')
+ ]
+ },
+ {
+ test: /\.woff2?$/,
+ loaders: ['url-loader?prefix=font/&limit=10000&mimetype=application/font-woff']
+ },
+ {
+ test: /\.jpg$/,
+ loaders: ['file-loader']
+ },
+ {
+ test: /\.png$/,
+ loaders: ['file-loader']
+ },
+ {
+ test: /\.svg$/,
+ loaders: ['file-loader']
+ },
+ {
+ test: /\.html$/,
+ loaders: ['raw-loader']
+ },
+ {
+ test: /\.json$/,
+ loaders: ['json-loader']
+ }
+ ]
+ },
+ eslint: {
+ fix: true,
+ configFile: require.resolve('./.eslintrc')
+ },
+ postcss: function() {
+ return [ Autoprefixer ];
+ },
+ sassLoader: {
+ includePaths: [ path.join('./styles/partials') ]
+ },
+ plugins: [
+ new CopyWebpackPlugin([{
+ from: './assets',
+ to: './assets'
+ }])
+ ]
+};
+
+const interactiveConfig = {
+ resolve: {
+ alias: {
+ react: 'preact-compat',
+ 'react-dom': 'preact-compat'
+ }
+ },
+ plugins: [
+ new webpack.optimize.UglifyJsPlugin({
+ compress: {
+ warnings: false
+ }
+ })
+ ]
+};
+
+const developmentConfig = {
+ module: {
+ loaders: [
+ {
+ test: /\.font.js$/,
+ loaders: ['style-loader', 'css-loader', 'fontgen-loader']
+ },
+ {
+ test: /\.css$/,
+ loaders: ['style-loader', 'css-loader'],
+ include: stylePaths
+ },
+ {
+ test: /\.scss$/,
+ loaders: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
+ include: stylePaths
+ }
+ ]
+ }
+};
+
+const buildConfig = {
+ plugins: [
+ new ExtractTextPlugin('[chunkhash].css', {
+ allChunks: true
+ })
+ ],
+ module: {
+ loaders: [
+ {
+ test: /\.font.js$/,
+ loader: ExtractTextPlugin.extract(
+ 'style-loader',
+ 'css-loader!fontgen-loader?embed'
+ )
+ },
+ {
+ test: /\.css$/,
+ loader: ExtractTextPlugin.extract(
+ 'style-loader',
+ 'css-loader'
+ ),
+ include: stylePaths
+ },
+ {
+ test: /\.scss$/,
+ loader: ExtractTextPlugin.extract(
+ 'style-loader',
+ 'css-loader!postcss-loader!sass-loader'
+ ),
+ include: stylePaths
+ }
+ ]
+ }
+};
+
+module.exports = function(env) {
+ switch(env) {
+ case 'start':
+ return merge(
+ commonConfig,
+ developmentConfig
+ );
+ case 'interactive':
+ return merge(
+ commonConfig,
+ interactiveConfig
+ );
+ case 'build':
+ case 'lint:links':
+ return merge(
+ commonConfig,
+ buildConfig
+ );
+ }
+};