diff --git a/cloud/routes.py b/cloud/routes.py
index 9128049..c05e7f1 100644
--- a/cloud/routes.py
+++ b/cloud/routes.py
@@ -17,6 +17,8 @@ from pillar.web.utils import attach_project_pictures
from pillar.web.settings import blueprint as blueprint_settings
from pillar.web.nodes.routes import url_for_node
from pillar.web.nodes.custom.comments import render_comments_for_node
+from pillar.web.projects.routes import render_project
+from pillar.web.projects.routes import find_project_or_404
blueprint = Blueprint('cloud', __name__)
@@ -434,6 +436,32 @@ def comments_for_node(node_id):
return render_comments_for_node(node_id, can_post_comments=can_post_comments)
+@blueprint.route('/p/hero')
+def project_hero():
+ api = system_util.pillar_api()
+ project = find_project_or_404('hero',
+ embedded={'header_node': 1},
+ api=api)
+ # Load the header video file, if there is any.
+ header_video_file = None
+ header_video_node = None
+ if project.header_node and project.header_node.node_type == 'asset' and \
+ project.header_node.properties.content_type == 'video':
+ header_video_node = project.header_node
+ header_video_file = get_file(project.header_node.properties.file)
+ header_video_node.picture = get_file(header_video_node.picture)
+
+ pages = Node.all({
+ 'where': {'project': project._id, 'node_type': 'page'},
+ 'projection': {'name': 1}}, api=api)
+
+ return render_project(project, api,
+ extra_context={'header_video_file': header_video_file,
+ 'header_video_node': header_video_node,
+ 'pages': pages._items,},
+ template_name='projects/landing.html')
+
+
def setup_app(app):
global _homepage_context
cached = app.cache.cached(timeout=300)
diff --git a/src/styles/project-landing.sass b/src/styles/project-landing.sass
new file mode 100644
index 0000000..170cfd4
--- /dev/null
+++ b/src/styles/project-landing.sass
@@ -0,0 +1,347 @@
+@import ../../../pillar/src/styles/_config
+@import ../../../pillar/src/styles/_utils
+
+$node-latest-thumbnail-size: 160px
+$node-latest-gallery-thumbnail-size: 200px
+body
+ background-color: white
+.page-body
+ background-color: white
+
+nav.navbar
+ background-color: white
+
+ .navbar-brand
+ color: $color-text
+
+ li a.navbar-item
+ color: $color-text
+ &:hover
+ color: black
+ &:focus
+ color: black
+ &.active
+ color: black
+ .dropdown.open
+ a
+ background-color: white
+ .dropdown.libraries
+ &:hover
+ background: none
+ ul.dropdown-menu
+ background-color: white
+ li
+ a
+ color: $color-text
+ &:hover
+ color: black
+ background-color: white
+ .navbar-container
+ +container-behavior
+
+ .navbar-toggle
+ border: 2px solid $color-text-dark-primary
+ color: $color-text
+
+ .navbar-nav
+ +media-xs
+ padding: 10px
+
+ .search-input
+ display: none
+
+.node-details-container
+ max-width: 620px
+ font-family: $font-body
+ font-size: 1.3em
+ line-height: 1.5em
+ margin: 0 auto 40px auto
+ padding-bottom: 40px
+ border-bottom: thin solid $color-background
+
+
+ p
+ margin-bottom: 1.3em
+
+header
+ display: flex
+ flex-direction: column /* stack flex items vertically */
+ position: relative
+ img.header
+ width: 100%
+ flex-direction: column /* stack flex items vertically */
+ position: relative
+ a.page-card-cta
+ position: absolute
+ left: 76%
+ top: 50%
+ transform: translate(-50%, -50%)
+ color: white
+ font-weight: bold
+ background: #ff4970
+ border-radius: 3px
+ border: none
+ box-shadow: 1px 1px 0 rgba(black, .2)
+ padding: 7px 20px
+ text-decoration: none
+ text-shadow: none
+
+ &:hover
+ background: lighten(#ff4970, 5%)
+
+
+h2
+ text-align: center
+ margin-bottom: 40px
+
+section
+ max-width: 1024px
+ padding-top: 20px
+ border-top: thin solid $color-background
+ margin: 0 auto
+
+a.btn
+ margin: 20px auto
+ font-size: 1.3em
+ padding: 9px 18px
+ border-radius: 8px
+ color: $color-text-dark
+
+.navbar-secondary
+ max-width: 620px
+ margin: 0 auto
+
+ .navbar-container
+ border-bottom: 1px solid #dddddd
+
+ .navbar-collapse
+ padding-left: 0
+
+ li
+ a
+ padding-left: 20px
+ padding-right: 20px
+ color: $color-text
+ &:hover
+ &.active
+ background: none
+ color: black
+ box-shadow: 0px 2px 0 rgba(red, .8)
+
+.node-extra
+ display: flex
+ flex-direction: column
+
+ //padding: 0 20px
+ width: 100%
+
+
+ .node-updates
+ flex: 1
+ font-size: 1.1em
+
+ ul
+ padding: 0
+ margin: 0 0 15px 0
+ display: flex
+ flex-direction: row
+ flex-wrap: wrap
+
+ li
+ display: flex
+ flex-direction: column
+ list-style: none
+ padding: 5px
+ cursor: pointer
+ width: 33.3333%
+
+ +media-xs
+ width: 100%
+
+ &.texture, &.group_texture
+ width: 25%
+
+ &:hover
+ img
+ opacity: .9
+ a.title
+ //color: $color-primary
+ text-decoration: underline
+
+ &.post
+ .info .title
+ //color: $node-type-post
+ font-size: 1.1em
+ a.image
+ border: none
+ //border-color: $node-type-post
+ background-color: hsl(hue($node-type-post), 20%, 55%)
+
+ &.asset.image a.image
+ border-color: $node-type-asset_image
+ background-color: hsl(hue($node-type-asset_image), 20%, 55%)
+ &.asset.file a.image
+ border-color: $node-type-asset_file
+ background-color: hsl(hue($node-type-asset_file), 20%, 55%)
+ &.asset.video a.image
+ border-color: $node-type-asset_video
+ background-color: hsl(hue($node-type-asset_video), 20%, 55%)
+
+ .image
+ width: 100%
+ height: $node-latest-thumbnail-size
+ min-height: $node-latest-thumbnail-size
+ max-height: $node-latest-thumbnail-size
+ background-color: $color-background
+ margin: 5px auto 10px auto
+ position: relative
+ overflow: hidden
+ border-radius: 0
+
+ img
+ max-height: $node-latest-thumbnail-size
+ +position-center-translate
+
+ i
+ color: rgba(white, .9)
+ font-size: 1.8em
+ position: absolute
+ bottom: 3px
+ left: 5px
+ text-shadow: 1px 1px 0 rgba(black, .2)
+
+ &.pi-file-archive
+ font-size: 1.5em
+ bottom: 5px
+ &.pi-newspaper
+ font-size: 1.6em
+ left: 7px
+
+ .ribbon
+ +ribbon
+
+ .info
+ width: 100%
+ height: 100%
+ display: flex
+ flex-direction: column
+ justify-content: space-between
+ word-break: break-word
+
+ .description
+ font-size: 1em
+ line-height: 1.8em
+ padding-top: 8px
+ color: $color-text-dark-primary
+
+ .title
+ display: block
+ font-size: 1.3em
+ color: $color-text-dark
+ font-weight: 600
+ +clearfix
+ +text-overflow-ellipsis
+
+ span.details
+ width: 100%
+ display: block
+ font-size: 1em
+ line-height: 1.2em
+ padding: 5px 0
+ color: $color-text-dark-secondary
+ +clearfix
+
+ .who
+ margin-left: 3px
+ .what
+ text-transform: capitalize
+
+
+$bg-color: #444
+$bg-color2: #666
+$yellow: rgb(249,229,89)
+$almost-white: rgb(255,255,255)
+$btn-transparent-color: rgba(249,229,89,1)
+$btn-transparent-bg: rgba(249,229,89,0)
+
+
+section.gallery
+ max-width: 1024px
+ margin: 60px auto 0 auto
+ text-align: center
+ padding-bottom: 40px
+
+ p
+ color: $almost-white
+ padding: 0 40px
+
+
+ .thumbnail
+ float: left
+ position: relative
+ width: 23%
+ padding-bottom: 23%
+ margin: 0.83%
+ overflow: hidden
+ &:hover
+ box-shadow: 2px 2px 50px 0 rgba(0,0,0,0.3)
+
+ .img-container
+ position: absolute
+ width: 100%
+ height: 100%
+
+ img
+ width: 300%
+ transform: translate(-20%,-10%)
+
+ &:hover .img-caption
+ top: 0
+ left: 0
+ .btn-trans
+ background: rgba(255,255,255,0.4)
+
+ .img-caption
+ position: absolute
+ width: 100%
+ height: 100%
+ background: rgba(0, 0, 0, 0.3)
+ text-align: center
+
+ .table
+ display: table
+ .table-cell
+ display: table-cell
+ vertical-align: bottom
+ border: none
+
+ @media screen and (max-width: 992px)
+ .thumbnail
+ width: 22%
+ padding-bottom: 22%
+ margin: 1.5%
+
+ .img-container:hover .img-caption
+ top: 0
+ left: 0
+
+ .img-caption
+ position: absolute
+ width: 100%
+ height: 100%
+ background: rgba(0, 0, 0, .7)
+ text-align: center
+ a
+ color: $yellow
+
+ @media screen and (max-width: 720px)
+ .thumbnail
+ width: 29%
+ padding-bottom: 29%
+ margin: 2.16%
+
+ @media screen and (max-width: 470px)
+ .thumbnail
+ width: 44%
+ padding-bottom: 44%
+ margin: 3%
diff --git a/src/templates/projects/landing.pug b/src/templates/projects/landing.pug
new file mode 100644
index 0000000..003ec3a
--- /dev/null
+++ b/src/templates/projects/landing.pug
@@ -0,0 +1,294 @@
+| {% extends 'layout.html' %}
+
+| {% block page_title %}{{ project.name }}{% endblock%}
+
+| {% block og %}
+meta(property="og:type", content="website")
+
+| {% if og_picture %}
+meta(property="og:image", content="{{ og_picture.thumbnail('l', api=api) }}")
+meta(name="twitter:image", content="{{ og_picture.thumbnail('l', api=api) }}")
+| {% elif node and node.picture %}
+meta(property="og:image", content="{{ node.picture.thumbnail('l', api=api) }}")
+meta(name="twitter:image", content="{{ node.picture.thumbnail('l', api=api) }}")
+| {% elif project.picture_header %}
+meta(property="og:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
+meta(name="twitter:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
+| {% endif %}
+
+| {% if show_project %}
+meta(property="og:title", content="{{ project.name }} - Blender Cloud")
+meta(name="twitter:title", content="{{ project.name }} - Blender Cloud")
+meta(property="og:description", content="{{ project.summary }}")
+meta(name="twitter:description", content="{{ project.summary }}")
+meta(property="og:url", content="{{ url_for('projects.view', project_url=project.url, _external=True) }}")
+| {% else %}
+
+| {% if node %}
+meta(property="og:title", content="{{ node.name }} - Blender Cloud")
+meta(name="twitter:title", content="{{ node.name }} on Blender Cloud")
+
+| {% if node.node_type == 'post' %}
+
+| {% if node.properties.content %}
+meta(property="og:description", content="{{ node.properties.content | truncate(180) }}")
+meta(name="twitter:description", content="{{ node.properties.content | truncate(180) }}")
+| {% else %}
+meta(property="og:description", content="Blender Cloud, your source for open content and training")
+meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
+| {% endif %}
+
+| {% else %}
+
+| {% if node.description %}
+meta(property="og:description", content="{{ node.description | truncate(180) }}")
+meta(name="twitter:description", content="{{ node.description | truncate(180) }}")
+| {% else %}
+meta(property="og:description", content="Blender Cloud, your source for open content and training")
+meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
+| {% endif %}
+
+| {% endif %}
+
+meta(property="og:url", content="{{url_for('projects.view_node', project_url=project.url, node_id=node._id)}}")
+| {% else %}
+meta(property="og:title", content="{{ project.name }} Blog on Blender Cloud")
+meta(name="twitter:title", content="{{ project.name }} Blog on Blender Cloud")
+meta(property="og:description", content="{{ project.summary }}")
+meta(name="twitter:description", content="{{ project.summary }}")
+
+meta(property="og:url", content="{{url_for('projects.view', project_url=project.url, _external=True)}}")
+| {% endif %}
+
+| {% endif %}
+| {% endblock og %}
+
+| {% block page_overlay %}
+#page-overlay.video
+ .video-embed
+#others
+| {% endblock %}
+
+| {% block head %}
+
+script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-6.2.8.min.js') }}")
+script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-ga-0.4.2.min.js') }}")
+script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-hotkeys-0.2.20.min.js') }}")
+| {% endblock %}
+
+| {% block css %}
+link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
+link(href="{{ url_for('static_pillar', filename='assets/css/base.css') }}", rel="stylesheet")
+link(href="{{ url_for('static_cloud', filename='assets/css/project-landing.css') }}", rel="stylesheet")
+| {% endblock %}
+
+| {% block body %}
+header
+ //a(href="{{ url_for( 'projects.view', project_url=project.url) }}")
+ img.header(src="{{ project.picture_header.thumbnail('l', api=api) }}")
+
+ a.page-card-cta.js-watch-video(
+ href="https://www.youtube.com/watch?v=NwVGvcIrNWA",
+ data-youtube-id="NwVGvcIrNWA")
+ i.pi-play
+ | Watch Film
+| {% block navbar_secondary %}
+nav.navbar-secondary
+ .navbar-container
+ nav.collapse.navbar-collapse
+ ul.nav.navbar-nav.navbar-right
+ li
+ a.navbar-item(
+ href="{{ url_for('projects.view', project_url=project.url) }}",
+ title="{{ project.name }} Homepage")
+ span
+ b {{ project.name }}
+ li
+ a.navbar-item(
+ href="{{ url_for('main.project_blog', project_url=project.url) }}",
+ title="Project Blog",
+ class="{% if category == 'blog' %}active{% endif %}")
+ span Blog
+ | {% for p in pages %}
+ li
+ a.navbar-item(
+ href="{{ url_for('projects.view_node', project_url=project.url, node_id=p._id) }}",
+ title="{{ p.name }}",
+ class="{% if category == 'page' %}active{% endif %}")
+ span {{ p.name }}
+ | {% endfor %}
+ li
+ a.navbar-item(
+ href="{{ url_for('projects.view_node', project_url=project.url, node_id='5ad24dd91effe4181aff5930') }}",
+ title="Blender Cloud Blog",
+ class="{% if category == 'blog' %}active{% endif %}")
+ span Explore
+| {% endblock navbar_secondary %}
+#container
+ section.node-details-container.project
+ .node-details-title
+ h1 {{ project.name }}
+
+ | {% if project.description %}
+ .node-details-description
+ | {{ project | markdowned('description') }}
+ | {% endif %}
+
+
+ section.gallery
+ h2 Gallery
+ | {% for n in activity_stream %}
+ | {% if n.node_type not in ['comment', 'post'] %}
+ .thumbnail.expand-image-links
+ .img-container
+ a(href="{{ n.picture.thumbnail('l', api=api) }}", data-node_id="{{ n._id }}")
+ img(src="{{ n.picture.thumbnail('l', api=api) }}", alt="{{ n.name }}")
+ .img-caption.table
+ | {# Not using for the moment
+ span.table-cell {{ n.name }}
+ | #}
+ | {% endif %}
+ | {% endfor %}
+ div(class="clearfix")
+ a.btn(href="{{ url_for('main.project_blog', project_url=project.url) }}") See more
+
+ section.node-extra
+ h2 Latest Updates
+
+ | {% if activity_stream %}
+ .node-updates
+ ul.node-updates-list
+ | {% for n in activity_stream %}
+ | {% if n.node_type == 'post' %}
+ li.node-updates-list-item(
+ data-node_id="{{ n._id }}",
+ class="{{ n.node_type }} {{ n.properties.content_type | hide_none }}")
+ a.image(href="{{ url_for_node(node=n) }}")
+ | {% if n.picture %}
+ img(src="{{ n.picture.thumbnail('l', api=api) }}")
+ | {% endif %}
+
+ .info
+ a.title(href="{{ url_for_node(node=n) }}") {{ n.name }}
+ p.description(href="{{ url_for_node(node=n) }}")
+ | {% if n.node_type == 'post' %}
+ | {{ n.properties | markdowned('content') | striptags | truncate(140, end="... read more") | safe | hide_none }}
+ | {% else %}
+ | {{ n | markdowned('description') | striptags | truncate(140, end="... read more") | safe | hide_none }}
+ | {% endif %}
+ //span.details
+ // span.what {% if n.properties.content_type %}{{ n.properties.content_type | undertitle }}{% else %}{{ n.node_type | undertitle }}{% endif %} ยท
+ // span.when {{ n._updated | pretty_date }} by
+ // span.who {{ n.user.full_name }}
+ | {% endif %}
+ | {% endfor %}
+ | {% endif %}
+ a.btn(href="{{ url_for('main.project_blog', project_url=project.url) }}") See all updates
+ section.social
+ h2 Contact
+ p.
+ Blender Animation Studio
+ ton@blender.org
+
+ Entrepotdok 57A
+ 1018 AD Amsterdam
+ the Netherlands
+ ul.footer-social
+ li
+ a(href="https://www.facebook.com/BlenderCloudOfficial/",
+ title="Follow us on Facebook")
+ i.pi-social-facebook
+ li
+ a(href="https://twitter.com/Blender_Cloud",
+ title="Follow us on Twitter")
+ i.pi-social-twitter
+
+| {% endblock body %}
+
+
+| {% block footer_scripts %}
+script.
+ // Click anywhere in the page to hide the overlay
+ function hideOverlay() {
+ $('#page-overlay.video').removeClass('active');
+ $('#page-overlay.video .video-embed').html('');
+ }
+
+ $(document).click(function () {
+ hideOverlay();
+ });
+
+ $(document).keyup(function (e) {
+ if (e.keyCode == 27) {
+ hideOverlay();
+ }
+ });
+
+ $('a.js-watch-video').click(function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ $('#page-overlay.video').addClass('active');
+
+ var videoId = $(this).attr('data-youtube-id');
+ $('#page-overlay .video-embed').html('')
+ });
+
+ function loadNodeContent(url, nodeId) {
+
+ $('#project-loading').addClass('active');
+ $.get(url, function (dataHtml) {
+ // Update the DOM injecting the generate HTML into the page
+ $('#page-overlay').addClass('active');
+ $('#others').html(dataHtml);
+ // $('#project_context').html(dataHtml);
+ })
+ .done(function () {
+ // updateUi(nodeId, 'view');
+ })
+ .fail(function (dataResponse) {
+ $('#project_context').html($(''));
+ $('#server_error').attr('src', url);
+ })
+ .always(function () {
+ $('#project-loading').removeAttr('class');
+ $('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
+ });
+ }
+
+ function displayNode(nodeId, pushState) {
+ // Remove the 'n_' suffix from the id
+ if (nodeId.substring(0, 2) == 'n_') {
+ nodeId = nodeId.substr(2);
+ }
+
+ var url = '/nodes/' + nodeId + '/view';
+ loadNodeContent(url, nodeId);
+
+ // Determine whether we should push the new state or not.
+ pushState = (typeof pushState !== 'undefined') ? pushState : true;
+ if (!pushState) return;
+
+ // Push the correct URL onto the history.
+ var push_state = {nodeId: nodeId, url: url};
+ var push_url = '{{url_for("projects.view", project_url=project.url)}}' + nodeId;
+ // console.log('Pushing state ', push_state, ' with URL ', push_url);
+ window.history.pushState(
+ push_state,
+ 'Node ' + nodeId, // TODO: use sensible title
+ push_url
+ );
+ }
+
+ $("a[data-node_id]").on( "click", function(e) {
+ // var nodeId = $(this).data('node_id');
+ // displayNode(nodeId);
+ e.preventDefault();
+ e.stopPropagation();
+ $('#page-overlay').addClass('active');
+ var url = $(this).attr('href');
+ $('#page-overlay').html('
')
+ });
+
+
+| {% endblock %}
\ No newline at end of file