diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee
index 32d2abf594..03e87125ac 100644
--- a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee
+++ b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee
@@ -10,6 +10,7 @@ GeoIpLookup = require("../../infrastructure/GeoIpLookup")
SubscriptionDomainHandler = require("./SubscriptionDomainHandler")
UserGetter = require "../User/UserGetter"
FeaturesUpdater = require './FeaturesUpdater'
+planFeatures = require './planFeatures'
module.exports = SubscriptionController =
@@ -20,6 +21,7 @@ module.exports = SubscriptionController =
viewName = "#{viewName}_#{req.query.v}"
logger.log viewName:viewName, "showing plans page"
currentUser = null
+
GeoIpLookup.getCurrencyCode req.query?.ip || req.ip, (err, recomendedCurrency)->
return next(err) if err?
render = () ->
@@ -29,6 +31,7 @@ module.exports = SubscriptionController =
gaExperiments: Settings.gaExperiments.plansPage
recomendedCurrency:recomendedCurrency
shouldABTestPlans: currentUser == null or (currentUser?.signUpDate? and currentUser.signUpDate >= (new Date('2016-10-27')))
+ planFeatures: planFeatures
user_id = AuthenticationController.getLoggedInUserId(req)
if user_id?
UserGetter.getUser user_id, {signUpDate: 1}, (err, user) ->
diff --git a/services/web/app/coffee/Features/Subscription/planFeatures.coffee b/services/web/app/coffee/Features/Subscription/planFeatures.coffee
new file mode 100644
index 0000000000..8c9c276955
--- /dev/null
+++ b/services/web/app/coffee/Features/Subscription/planFeatures.coffee
@@ -0,0 +1,133 @@
+module.exports =
+ [
+ {
+ feature: 'number_collab'
+ value: 'str'
+ plans: {
+ free: '1'
+ coll: '10'
+ prof: 'unlimited'
+ }
+ student: '6'
+ }
+ {
+ feature: 'unlimited_private'
+ value: 'bool'
+ info: 'unlimited_private_info'
+ plans: {
+ free: true
+ coll: true
+ prof: true
+ },
+ student: true
+ }
+ {
+ feature: 'realtime_collab'
+ value: 'bool'
+ info: 'realtime_collab_info'
+ plans: {
+ free: true
+ coll: true
+ prof: true
+ }
+ student: true
+ }
+ {
+ feature: 'hundreds_templates'
+ value: 'bool'
+ info: 'hundreds_templates_info'
+ plans: {
+ free: true
+ coll: true
+ prof: true
+ }
+ student: true
+ }
+ {
+ feature: 'powerful_latex_editor'
+ value: 'bool'
+ info: 'latex_editor_info'
+ plans: {
+ free: true
+ coll: true
+ prof: true
+ }
+ student: true
+ }
+ {
+ feature: 'realtime_track_changes'
+ value: 'bool'
+ info: 'realtime_track_changes_info'
+ plans: {
+ free: false
+ coll: true
+ prof: true
+ },
+ student: true
+ }
+ {
+ feature: 'reference_search'
+ value: 'bool'
+ info: 'reference_search_info'
+ plans: {
+ free: false
+ coll: true
+ prof: true
+ },
+ student: true
+ },
+ {
+ feature: 'reference_sync'
+ info: 'reference_sync_info'
+ value: 'bool'
+ plans: {
+ free: false
+ coll: true
+ prof: true
+ },
+ student: true
+ }
+ {
+ feature: 'full_doc_history'
+ value: 'bool'
+ info: 'full_doc_history_info'
+ plans: {
+ free: false,
+ coll: true,
+ prof: true
+ },
+ student: true
+ }
+ {
+ feature: 'dropbox_integration_lowercase'
+ value: 'bool'
+ info: 'dropbox_integration_info'
+ plans: {
+ free: false,
+ coll: true,
+ prof: true
+ },
+ student: true
+ },
+ {
+ feature: 'github_integration_lowercase'
+ value: 'bool'
+ info: 'github_integration_info'
+ plans: {
+ free: false,
+ coll: true,
+ prof: true
+ },
+ student: true
+ },
+ {
+ feature: 'priority_support',
+ value: 'bool',
+ plans: {
+ free: false,
+ coll: true,
+ prof: true
+ },
+ student: true
+ },
+ ]
\ No newline at end of file
diff --git a/services/web/app/views/subscriptions/_plans_page_details_less.pug b/services/web/app/views/subscriptions/_plans_page_details_less.pug
new file mode 100644
index 0000000000..2aa572e4e7
--- /dev/null
+++ b/services/web/app/views/subscriptions/_plans_page_details_less.pug
@@ -0,0 +1,118 @@
+.row
+ .col-md-12
+ .page-header.centered.plans-header.text-centered
+ h1 #{translate("start_x_day_trial", {len:'{{trial_len}}'})}
+.row
+ .col-md-8.col-md-offset-2
+ p.text-centered #{translate("sl_benefits_plans")}
+
+.row.top-switch
+ .col-md-6.col-md-offset-3
+ +plan_switch('card')
+ .col-md-2.text-right
+ +currency_dropdown
+
+div(ng-show="showPlans")
+ .row
+ .col-md-10.col-md-offset-1
+ .row
+ .card-group.text-centered(ng-if="ui.view == 'monthly' || ui.view == 'annual'")
+ .col-md-4
+ .card.card-first
+ .card-header
+ h2 #{translate("personal")}
+ .circle #{translate("free")}
+ +features_free
+ .col-md-4
+ .card.card-highlighted
+ .card-header
+ h2 #{translate("collaborator")}
+ .circle
+ +price_collaborator
+ +features_collaborator
+ .col-md-4
+ .card.card-last
+ .card-header
+ h2 #{translate("professional")}
+ .circle
+ +price_professional
+ +features_professional
+
+ .card-group.text-centered(ng-if="ui.view == 'student'")
+ .col-md-4
+ .card.card-first
+ .card-header
+ h2 #{translate("personal")}
+ .circle #{translate("free")}
+ +features_free
+
+ .col-md-4
+ .card.card-highlighted
+ +card_student_monthly
+
+ .col-md-4
+ .card.card-last
+ +card_student_annual
+
+.row.row-spaced
+ p.text-centered #{translate("choose_plan_works_for_you", {len:'{{trial_len}}'})}
+
+.row
+ .col-md-8.col-md-offset-2
+ .alert.alert-info.text-centered
+ | #{translate("interested_in_group_licence")}
+ br
+ a(href, ng-click="openGroupPlanModal()") #{translate("get_in_touch_for_details")}
+
+ script(type="text/ng-template", id="groupPlanModalTemplate")
+ .modal-header
+ h3 #{translate("group_plan_enquiry")}
+ .modal-body
+ form.text-left.form(ng-controller="UniverstiesContactController", ng-submit="contactUs()")
+ span(ng-show="sent == false && error == false")
+ .form-group
+ label#title9(for='Field9')
+ | Name
+ input#Field9.field.text.medium.span8.form-control(ng-model="form.name", maxlength='255', tabindex='1', onkeyup='')
+ label#title11.desc(for='Field11')
+ | Email
+ .form-group
+ input#Field11.field.text.medium.span8.form-control(ng-model="form.email", name='Field11', type='email', spellcheck='false', value='', maxlength='255', tabindex='2')
+ label#title12.desc(for='Field12')
+ | University / Company
+ .form-group
+ input#Field12.field.text.medium.span8.form-control(ng-model="form.university", name='Field12', type='text', value='', maxlength='255', tabindex='3', onkeyup='')
+ label#title13.desc(for='Field13')
+ | Position
+ .form-group
+ input#Field13.field.text.medium.span8.form-control(ng-model="form.position", name='Field13', type='text', value='', maxlength='255', tabindex='4', onkeyup='')
+ .form-group
+ input(ng-model="form.source", type="hidden", ng-init="form.source = '__ref__'; form.subject = 'General enquiry for larger ShareLaTeX use';")
+ .form-group.text-center
+ input#saveForm.btn-success.btn.btn-lg(name='saveForm', type='submit', ng-disabled="sending", value='Request a quote')
+ span(ng-show="sent == true && error == false")
+ p Request Sent, Thank you.
+ span(ng-show="error")
+ p Error sending request.
+
+.row
+ .col-md-12
+ .page-header.plans-header.plans-subheader.text-centered
+ h2 #{translate("enjoy_these_features")}
+ .col-md-4
+ .card.features.text-centered
+ i.fa.fa-file-text-o.fa-5x
+ h4 #{translate("unlimited_projects")}
+ p #{translate("create_unlimited_projects")}
+ .col-md-4
+ .card.features.text-centered
+ i.fa.fa-clock-o.fa-5x
+ h4 #{translate("full_doc_history")}
+ p #{translate("never_loose_work")}
+ .col-md-4
+ .card.features.text-centered
+ i.fa.fa-dropbox.fa-5x
+ |
+ i.fa.fa-github.fa-5x
+ h4 #{translate("sync_to_dropbox_and_github")}
+ p #{translate("access_projects_anywhere")}
\ No newline at end of file
diff --git a/services/web/app/views/subscriptions/_plans_page_details_more.pug b/services/web/app/views/subscriptions/_plans_page_details_more.pug
new file mode 100644
index 0000000000..17c683201e
--- /dev/null
+++ b/services/web/app/views/subscriptions/_plans_page_details_more.pug
@@ -0,0 +1,160 @@
+.row
+ .col-md-12
+ .page-header.centered.plans-header.text-centered
+ h1.text-capitalize #{translate('instant_access')}
+.row
+ .col-md-8.col-md-offset-2
+ p.text-centered #{translate("sl_benefits_plans")}
+
+.row.top-switch
+ .col-md-6.col-md-offset-3
+ +plan_switch('card')
+ .col-md-2.text-right
+ +currency_dropdown
+
+div(ng-show="showPlans")
+ .row
+ .col-md-10.col-md-offset-1
+ .row
+ .card-group.text-centered(ng-if="ui.view == 'monthly' || ui.view == 'annual'")
+ .col-md-4
+ .card.card-first
+ .card-header
+ h2 #{translate("personal")}
+ h5.tagline #{translate("tagline_personal")}
+ .circle #{translate("free")}
+ +features_free
+ .col-md-4
+ .card.card-highlighted
+ .best-value
+ strong #{translate('best_value')}
+ .card-header
+ h2 #{translate("collaborator")}
+ h5.tagline #{translate("tagline_collaborator")}
+ .circle
+ +price_collaborator
+ +features_collaborator
+ .col-md-4
+ .card.card-last
+ .card-header
+ h2 #{translate("professional")}
+ h5.tagline #{translate("tagline_professional")}
+ .circle
+ +price_professional
+ +features_professional
+
+ .card-group.text-centered(ng-if="ui.view == 'student'")
+ .col-md-4
+ .card.card-first
+ .card-header
+ h2 #{translate("personal")}
+ h5.tagline #{translate("tagline_personal")}
+ .circle #{translate("free")}
+ +features_free
+
+ .col-md-4
+ .card.card-highlighted
+ +card_student_annual
+
+ .col-md-4
+ .card.card-last
+ +card_student_monthly
+
+.row.row-spaced-large.text-centered
+ i.fa.fa-cc-mastercard.fa-2x
+ i.fa.fa-cc-visa.fa-2x
+ i.fa.fa-cc-amex.fa-2x
+ i.fa.fa-cc-paypal.fa-2x
+ div.text-centered #{translate('change_plans_any_time')}
#{translate('billed_after_x_days', {len:'{{trial_len}}'})}
+
+.row.row-spaced-large
+ .col-md-8.col-md-offset-2
+ .card.text-centered
+ .card-header
+ h2 #{translate('looking_multiple_licenses')}
+ span #{translate('reduce_costs_group_licenses')}
+ br
+ br
+ a.btn.btn-info(href="/i/university/groups") #{translate('find_out_more')}
+
+div
+ .row.row-spaced-large
+ .col-sm-12
+ .page-header.plans-header.plans-subheader.text-centered
+ h2 #{translate('compare_plan_features')}
+ .row
+ .col-md-6.col-md-offset-3
+ +plan_switch('table')
+ .col-md-3.text-right
+ +currency_dropdown
+ .row(event-tracking="features-table-viewed" event-tracking-ga="subscription-funnel" event-tracking-trigger="scroll" event-tracking-send-once="true")
+ .col-sm-12(ng-if="ui.view != 'student'")
+ +table_premium
+ .col-sm-12(ng-if="ui.view == 'student'")
+ +table_student
+
+ .row.row-spaced-large
+ .col-md-12
+ .page-header.plans-header.plans-subheader.text-centered
+ h2 #{translate('in_good_company')}
+ .row
+ .col-md-6
+ div
+ .row
+ .col-md-3
+ .circle-img
+ img(src=buildImgPath('advocates/erdogmus.jpg') alt="Professor Erdogmus")
+ .col-md-9
+ blockquote
+ p The ability to track changes and the real-time collaborative nature is what sets ShareLaTeX apart.
+ footer Professor Erdogmus, Northeastern University
+ .col-md-6
+ div
+ .row
+ .col-md-3
+ .circle-img
+ img(src=buildImgPath('advocates/henderson.jpg') alt="Rob Henderson")
+ .col-md-9
+ blockquote
+ p ShareLaTeX has proven to be a powerful and robust collaboration tool that is widely used in our School.
+ footer Rob Henderson, School Of Informatics And Computing - Indiana University
+
+ .faq
+ .row.row-spaced-large
+ .col-md-12
+ .page-header.plans-header.plans-subheader.text-centered
+ h2 FAQ
+ .row
+ .col-md-6
+ h3 #{translate("faq_how_free_trial_works_question")}
+ p #{translate('faq_how_free_trial_works_answer', { len:'{{trial_len}}' })}
+ .col-md-6
+ h3 #{translate('faq_change_plans_question')}
+ p #{translate('faq_change_plans_answer')}
+ .row
+ .col-md-6
+ h3 #{translate('faq_do_collab_need_premium_question')}
+ p #{translate('faq_do_collab_need_premium_answer')}
+ .col-md-6
+ h3 #{translate('faq_need_more_collab_question')}
+ p !{translate('faq_need_more_collab_answer', { referFriendsLink: '' + translate('referring_your_friends') + ''})}
+ .row
+ .col-md-6
+ h3 #{translate('faq_purchase_more_licenses_question')}
+ p !{translate('faq_purchase_more_licenses_answer', { groupLink: '' + translate('discounted_group_accounts') + '' })}
+ .col-md-6
+ h3 #{translate('faq_monthly_or_annual_question')}
+ p #{translate('faq_monthly_or_annual_answer')}
+ .row
+ .col-md-6
+ h3 #{translate('faq_how_to_pay_question')}
+ p #{translate('faq_how_to_pay_answer')}
+ .col-md-6
+ h3 #{translate('faq_pay_by_invoice_question')}
+ p !{translate('faq_pay_by_invoice_answer', { groupLink: '' + translate('discounted_group_accounts') + '' })}
+ .row.row-spaced-large.text-centery
+ .col-md-12
+ .plans-header.plans-subheader.text-centered
+ h2 #{translate('still_have_questions')}
+ button.btn.btn-info.btn-header.text-capitalize(ng-controller="ContactGeneralModal" ng-click="openModal()") #{translate('get_in_touch')}
+ != moduleIncludes("contactModalGeneral", locals)
diff --git a/services/web/app/views/subscriptions/_plans_page_mixins.pug b/services/web/app/views/subscriptions/_plans_page_mixins.pug
new file mode 100644
index 0000000000..c1f19bca00
--- /dev/null
+++ b/services/web/app/views/subscriptions/_plans_page_mixins.pug
@@ -0,0 +1,162 @@
+//- Buy Buttons
+mixin btn_buy_collaborator(location)
+ a.btn.btn-info(
+ ng-href="/user/subscription/new?planCode={{ getCollaboratorPlanCode() }}¤cy={{currencyCode}}",
+ ng-click="signUpNowClicked('collaborator','" + location + "')"
+ )
+ span(ng-show="ui.view != 'annual'") #{translate("start_free_trial")}
+ span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
+mixin btn_buy_free(location)
+ a.btn.btn-info(
+ href="/register"
+ style=(getLoggedInUserId() === null ? "" : "visibility: hidden")
+ ng-click="signUpNowClicked('free','" + location + "')"
+ )
+ span(ng-if="plansVariant !== 'more-details'") #{translate('sign_up_now')}
+ span.text-capitalize(ng-if="plansVariant === 'more-details'") #{translate('get_started_now')}
+mixin btn_buy_professional(location)
+ a.btn.btn-info(
+ ng-href="/user/subscription/new?planCode=professional{{ ui.view == 'annual' && '-annual' || planQueryString}}¤cy={{currencyCode}}"
+ ng-click="signUpNowClicked('professional','" + location + "')"
+ )
+ span(ng-show="ui.view != 'annual'") #{translate("start_free_trial")}
+ span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
+mixin btn_buy_student(location, plan)
+ if plan == 'annual'
+ a.btn.btn-info(
+ ng-href="/user/subscription/new?planCode=student-annual¤cy={{currencyCode}}",
+ ng-click="signUpNowClicked('student-annual','" + location + "')"
+ ) #{translate("buy_now")}
+ else
+ //- planQueryString will contain _free_trial_7_days
+ a.btn.btn-info(
+ ng-href="/user/subscription/new?planCode=student{{planQueryString}}¤cy={{currencyCode}}",
+ ng-click="signUpNowClicked('student-monthly','" + location + "')"
+ ) #{translate("start_free_trial")}
+
+//- Cards
+mixin card_student_annual
+ .best-value(ng-if="plansVariant == 'more-details'")
+ strong #{translate('best_value')}
+ .card-header
+ h2 #{translate("student")} (#{translate("annual")})
+ h5.tagline(ng-if="plansVariant == 'more-details'") #{translate('tagline_student_annual')}
+ .circle
+ span
+ +price_student_annual
+ +features_student('card', 'annual')
+mixin card_student_monthly
+ .card-header
+ h2 #{translate("student")}
+ h5.tagline(ng-if="plansVariant == 'more-details'") #{translate('tagline_student_monthly')}
+ .circle
+ span
+ +price_student_monthly
+ +features_student('card', 'monthly')
+
+//- Features Lists
+mixin features_collaborator
+ ul.list-unstyled
+ li
+ strong #{translate("collabs_per_proj", {collabcount:10})}
+ +features_premium
+ li
+ br
+ +btn_buy_collaborator('card')
+mixin features_free
+ ul.list-unstyled
+ li #{translate("one_collaborator")}
+ li(class="hidden-xs hidden-sm")
+ li(class="hidden-xs hidden-sm")
+ li(class="hidden-xs hidden-sm")
+ li(class="hidden-xs hidden-sm" ng-if="plansVariant === 'more-details'")
+ li(class="hidden-xs hidden-sm" ng-if="plansVariant === 'more-details'")
+ li(class="hidden-xs hidden-sm" ng-if="plansVariant === 'more-details'")
+ li
+ br
+ +btn_buy_free('card')
+mixin features_premium
+ li(ng-if="plansVariant != 'more-details'") #{translate("full_doc_history")}
+ li(ng-if="plansVariant != 'more-details'") #{translate("sync_to_dropbox")}
+ li(ng-if="plansVariant != 'more-details'") #{translate("sync_to_github")}
+ li(ng-if="plansVariant === 'more-details'")
+ li(ng-if="plansVariant === 'more-details'")
+ strong #{translate('all_premium_features')}
+ li(ng-if="plansVariant === 'more-details'") #{translate('sync_dropbox_github')}
+ li(ng-if="plansVariant === 'more-details'") #{translate('full_doc_history')}
+ li(ng-if="plansVariant === 'more-details'") #{translate('track_changes')}
+ li(ng-if="plansVariant === 'more-details'") + #{translate('more').toLowerCase()}
+mixin features_professional
+ ul.list-unstyled
+ li
+ strong #{translate("unlimited_collabs")}
+ +features_premium
+ li
+ br
+ +btn_buy_professional('card')
+mixin features_student(location, plan)
+ ul.list-unstyled
+ li
+ strong #{translate("collabs_per_proj", {collabcount:6})}
+ +features_premium
+ li
+ br
+ +btn_buy_student(location, plan)
+
+//- Prices
+mixin price_collaborator
+ span(ng-if="ui.view == 'monthly'")
+ | {{plans[currencyCode]['collaborator']['monthly']}}
+ span.small /mo
+ span(ng-if="ui.view == 'annual'")
+ | {{plans[currencyCode]['collaborator']['annual']}}
+ span.small /yr
+mixin price_professional
+ span(ng-if="ui.view == 'monthly'")
+ | {{plans[currencyCode]['professional']['monthly']}}
+ span.small /mo
+ span(ng-if="ui.view == 'annual'")
+ | {{plans[currencyCode]['professional']['annual']}}
+ span.small /yr
+mixin price_student_annual
+ | {{plans[currencyCode]['student']['annual']}}
+ span.small /yr
+mixin price_student_monthly
+ | {{plans[currencyCode]['student']['monthly']}}
+ span.small /mo
+
+//- UI Control
+mixin currency_dropdown
+ .dropdown.currency-dropdown(dropdown)
+ a.btn.btn-default.dropdown-toggle(
+ href="#",
+ data-toggle="dropdown",
+ dropdown-toggle
+ )
+ | {{currencyCode}} ({{plans[currencyCode]['symbol']}})
+ span.caret
+
+ ul.dropdown-menu.dropdown-menu-right.text-right(role="menu")
+ li(ng-repeat="(currency, value) in plans")
+ a(
+ href="#",
+ ng-click="changeCurreny($event, currency)"
+ ) {{currency}} ({{value['symbol']}})
+mixin plan_switch(location)
+ ul.nav.nav-pills
+ li(ng-class="{'active': ui.view == 'monthly'}")
+ a(
+ href="#"
+ ng-click="switchToMonthly($event,'" + location + "')"
+ ) #{translate("monthly")}
+ li(ng-class="{'active': ui.view == 'annual'}")
+ a(
+ href="#"
+ ng-click="switchToAnnual($event,'" + location + "')"
+ ) #{translate("annual")}
+ li(ng-class="{'active': ui.view == 'student'}")
+ a(
+ href="#"
+ ng-click="switchToStudent($event,'" + location + "')"
+ ) #{translate("half_price_student")}
+
diff --git a/services/web/app/views/subscriptions/_plans_page_tables.pug b/services/web/app/views/subscriptions/_plans_page_tables.pug
new file mode 100644
index 0000000000..63c4747603
--- /dev/null
+++ b/services/web/app/views/subscriptions/_plans_page_tables.pug
@@ -0,0 +1,107 @@
+
+//- Features Tables
+mixin table_premium
+ table.card.plans-table
+ tr
+ th
+ th #{translate("personal")}
+ th #{translate("collaborator")}
+ .outer.outer-top
+ .outer-content
+ .best-value
+ strong #{translate('best_value')}
+ th #{translate("professional")}
+
+ tr
+ td #{translate("price")}
+ td #{translate("free")}
+ td
+ +price_collaborator
+ td
+ +price_professional
+
+ for feature in planFeatures
+ tr
+ td(event-tracking="features-table" event-tracking-trigger="hover" event-tracking-ga="subscription-funnel" event-tracking-label=`${feature.feature}-exp-{{plansVariant}}`)
+ if feature.info
+ span(tooltip=translate(feature.info)) #{translate(feature.feature)}
+ else
+ | #{translate(feature.feature)}
+ for plan in feature.plans
+ td
+ if feature.value == 'str'
+ | #{plan}
+ else if plan
+ i.fa.fa-check
+ else
+ i.fa.fa-times
+
+ tr
+ td
+ td
+ +btn_buy_free('table')
+ td
+ +btn_buy_collaborator('table')
+ .outer.outer-btm
+ .outer-content
+ td
+ +btn_buy_professional('table')
+
+mixin table_cell_student(feature)
+ if feature.value == 'str'
+ | #{feature.student}
+ else if feature.student
+ i.fa.fa-check
+ else
+ i.fa.fa-times
+
+mixin table_student
+ table.card.plans-table
+ tr
+ th
+ th #{translate("personal")}
+ th #{translate("student")} (#{translate("annual")})
+ .outer.outer-top
+ .outer-content
+ .best-value
+ strong Best Value
+ th #{translate("student")}
+
+ tr
+ td #{translate("price")}
+ td #{translate("free")}
+ td
+ +price_student_annual
+ td
+ +price_student_monthly
+
+ for feature in planFeatures
+ tr
+ td(event-tracking="plans-page-table" event-tracking-trigger="hover" event-tracking-ga="subscription-funnel" event-tracking-label=`${feature.feature}-exp-{{plansVariant}}`)
+ if feature.info
+ span(tooltip=translate(feature.info)) #{translate(feature.feature)}
+ else
+ | #{translate(feature.feature)}
+ td
+ if feature.value == 'str'
+ | #{feature.plans.free}
+ else if feature.plans.free
+ i.fa.fa-check
+ else
+ i.fa.fa-times
+ td
+ +table_cell_student(feature)
+ td
+ +table_cell_student(feature)
+
+ tr
+ td
+ td
+ +btn_buy_free('table')
+ td
+ +btn_buy_student('table', 'annual')
+ .outer.outer-btm
+ .outer-content
+ td
+ +btn_buy_student('table', 'monthly')
+
diff --git a/services/web/app/views/subscriptions/new.pug b/services/web/app/views/subscriptions/new.pug
index d86bdb8166..4eae442f1e 100644
--- a/services/web/app/views/subscriptions/new.pug
+++ b/services/web/app/views/subscriptions/new.pug
@@ -31,7 +31,10 @@ block content
li(ng-repeat="(currency, value) in plans")
a(
ng-click="changeCurrency(currency)",
- ) {{currency}} ({{value['symbol']}})
+ ) {{currency}} ({{value['symbol']}})
+ .row(ng-if="plansVariant == 'more-details' && planCode == 'student-annual' || plansVariant == 'more-details' && planCode == 'student-monthly'")
+ .col-xs-12
+ p.student-disclaimer #{translate('student_disclaimer')}
hr.thin
.row
.col-md-12.text-center
diff --git a/services/web/app/views/subscriptions/plans.pug b/services/web/app/views/subscriptions/plans.pug
index 7c40c38a52..56a20f90f5 100644
--- a/services/web/app/views/subscriptions/plans.pug
+++ b/services/web/app/views/subscriptions/plans.pug
@@ -1,253 +1,18 @@
extends ../layout
+
+include _plans_page_mixins
+include _plans_page_tables
+
block scripts
script(type='text/javascript').
window.recomendedCurrency = '#{recomendedCurrency}'
window.abCurrencyFlag = '#{abCurrencyFlag}'
window.shouldABTestPlans = #{shouldABTestPlans || false}
- script(type='text/javascript').
- (function() {var s=document.createElement('script'); s.type='text/javascript';s.async=true;
- s.src=('https:'==document.location.protocol?'https':'http') + '://sharelatex-accounts.groovehq.com/widgets/f5ad3b09-7d99-431b-8af5-c5725e3760ce/ticket/api.js';
- var q = document.getElementsByTagName('script')[0];q.parentNode.insertBefore(s, q);})();
-
block content
.content.content-alt
.content.plans(ng-controller="PlansController")
- .container
- .row
- .col-md-12
- .page-header.centered.plans-header.text-centered
- h1(ng-cloak) #{translate("start_x_day_trial", {len:'{{trial_len}}'})}
- .row
- .col-md-8.col-md-offset-2
- p.text-centered #{translate("sl_benefits_plans")}
-
- .row(ng-cloak)
- .col-md-6.col-md-offset-3
- ul.nav.nav-pills
- li(ng-class="{'active': ui.view == 'monthly'}")
- a(
- href,
- ng-click="switchToMonthly()"
- ) #{translate("monthly")}
- li(ng-class="{'active': ui.view == 'annual'}")
- a(
- href
- ng-click="switchToAnnual()"
- ) #{translate("annual")}
- li(ng-class="{'active': ui.view == 'student'}")
- a(
- href,
- ng-click="switchToStudent()"
- ) #{translate("half_price_student")}
- .col-md-2.text-right
- .dropdown.currency-dropdown(dropdown)
- a.btn.btn-default.dropdown-toggle#currenyDropdown(
- href="#",
- data-toggle="dropdown",
- dropdown-toggle
- )
- | {{currencyCode}} ({{plans[currencyCode]['symbol']}})
- span.caret
-
- ul.dropdown-menu.dropdown-menu-right.text-right(role="menu")
- li(ng-repeat="(currency, value) in plans")
- a(
- href,
- ng-click="changeCurreny(currency)"
- ) {{currency}} ({{value['symbol']}})
-
- div(ng-show="showPlans")
- .row(ng-cloak)
- .col-md-10.col-md-offset-1
- .row
- .card-group.text-centered(ng-if="ui.view == 'monthly' || ui.view == 'annual'")
- .col-md-4
- .card.card-first
- .card-header
- h2 #{translate("personal")}
- .circle #{translate("free")}
- ul.list-unstyled
- li #{translate("one_collaborator")}
- li
- li
- li
- li
- br
- a.btn.btn-info(
- href="/register"
- style=(getLoggedInUserId() === null ? "" : "visibility: hidden")
- ) #{translate("sign_up_now")}
- .col-md-4
- .card.card-highlighted
- .card-header
- h2 #{translate("collaborator")}
- .circle
- span(ng-if="ui.view == 'monthly'")
- | {{plans[currencyCode]['collaborator']['monthly']}}
- span.small /mo
- span(ng-if="ui.view == 'annual'")
- | {{plans[currencyCode]['collaborator']['annual']}}
- span.small /yr
- ul.list-unstyled
- li
- strong #{translate("collabs_per_proj", {collabcount:10})}
- li #{translate("full_doc_history")}
- li #{translate("sync_to_dropbox")}
- li #{translate("sync_to_github")}
- li
- br
- a.btn.btn-info(
- ng-href="/user/subscription/new?planCode={{ getCollaboratorPlanCode() }}¤cy={{currencyCode}}", ng-click="signUpNowClicked('collaborator')"
- )
- span(ng-show="ui.view != 'annual'") #{translate("start_free_trial")}
- span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
- .col-md-4
- .card.card-last
- .card-header
- h2 #{translate("professional")}
- .circle
- span(ng-if="ui.view == 'monthly'")
- | {{plans[currencyCode]['professional']['monthly']}}
- span.small /mo
- span(ng-if="ui.view == 'annual'")
- | {{plans[currencyCode]['professional']['annual']}}
- span.small /yr
- ul.list-unstyled
- li
- strong #{translate("unlimited_collabs")}
- li #{translate("full_doc_history")}
- li #{translate("sync_to_dropbox")}
- li #{translate("sync_to_github")}
- li
- br
- a.btn.btn-info(
- ng-href="/user/subscription/new?planCode=professional{{ ui.view == 'annual' && '-annual' || planQueryString}}¤cy={{currencyCode}}", ng-click="signUpNowClicked('professional')"
- )
- span(ng-show="ui.view != 'annual'") #{translate("start_free_trial")}
- span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
-
- .card-group.text-centered(ng-if="ui.view == 'student'")
- .col-md-4
- .card.card-first
- .card-header
- h2 #{translate("personal")}
- .circle #{translate("free")}
- ul.list-unstyled
- li #{translate("one_collaborator")}
- li
- li
- li
- li
- br
- a.btn.btn-info(
- href="/register"
- style=(getLoggedInUserId() === null ? "" : "visibility: hidden")
- ) #{translate("sign_up_now")}
-
- .col-md-4
- .card.card-highlighted
- .card-header
- h2 #{translate("student")}
- .circle
- span
- | {{plans[currencyCode]['student']['monthly']}}
- span.small /mo
- ul.list-unstyled
- li
- strong #{translate("collabs_per_proj", {collabcount:6})}
- li #{translate("full_doc_history")}
- li #{translate("sync_to_dropbox")}
- li #{translate("sync_to_github")}
- li
- br
- a.btn.btn-info(
- ng-href="/user/subscription/new?planCode=student{{ plansVariant == 'default' ? planQueryString : '_'+plansVariant }}¤cy={{currencyCode}}",
- ng-click="signUpNowClicked('student-monthly')"
- ) #{translate("start_free_trial")}
-
- .col-md-4
- .card.card-last
- .card-header
- h2 #{translate("student")} (#{translate("annual")})
- .circle
- span
- | {{plans[currencyCode]['student']['annual']}}
- span.small /yr
- ul.list-unstyled
- li
- strong #{translate("collabs_per_proj", {collabcount:6})}
- li #{translate("full_doc_history")}
- li #{translate("sync_to_dropbox")}
- li #{translate("sync_to_github")}
- li
- br
- a.btn.btn-info(
- ng-href="/user/subscription/new?planCode=student-annual{{ plansVariant == 'default' ? '' : '_'+plansVariant }}¤cy={{currencyCode}}",
- ng-click="signUpNowClicked('student-annual')"
- ) #{translate("buy_now")}
-
-
-
- .row.row-spaced(ng-cloak)
- p.text-centered #{translate("choose_plan_works_for_you", {len:'{{trial_len}}'})}
-
- .row(ng-cloak)
- .col-md-8.col-md-offset-2
- .alert.alert-info.text-centered
- | #{translate("interested_in_group_licence")}
- br
- a(href, ng-click="openGroupPlanModal()") #{translate("get_in_touch_for_details")}
-
- script(type="text/ng-template", id="groupPlanModalTemplate")
- .modal-header
- h3 #{translate("group_plan_enquiry")}
- .modal-body
- form.text-left.form(ng-controller="UniverstiesContactController", ng-submit="contactUs()", ng-cloak)
- span(ng-show="sent == false && error == false")
- .form-group
- label#title9(for='Field9')
- | Name
- input#Field9.field.text.medium.span8.form-control(ng-model="form.name", maxlength='255', tabindex='1', onkeyup='')
- label#title11.desc(for='Field11')
- | Email
- .form-group
- input#Field11.field.text.medium.span8.form-control(ng-model="form.email", name='Field11', type='email', spellcheck='false', value='', maxlength='255', tabindex='2')
- label#title12.desc(for='Field12')
- | University / Company
- .form-group
- input#Field12.field.text.medium.span8.form-control(ng-model="form.university", name='Field12', type='text', value='', maxlength='255', tabindex='3', onkeyup='')
- label#title13.desc(for='Field13')
- | Position
- .form-group
- input#Field13.field.text.medium.span8.form-control(ng-model="form.position", name='Field13', type='text', value='', maxlength='255', tabindex='4', onkeyup='')
- .form-group
- input(ng-model="form.source", type="hidden", ng-init="form.source = '__ref__'; form.subject = 'General enquiry for larger ShareLaTeX use';")
- .form-group.text-center
- input#saveForm.btn-success.btn.btn-lg(name='saveForm', type='submit', ng-disabled="sending", value='Request a quote')
- span(ng-show="sent == true && error == false")
- p Request Sent, Thank you.
- span(ng-show="error")
- p Error sending request.
-
- .row
- .col-md-12
- .page-header.plans-header.plans-subheader.text-centered
- h2 #{translate("enjoy_these_features")}
- .col-md-4
- .card.features.text-centered
- i.fa.fa-file-text-o.fa-5x
- h4 #{translate("unlimited_projects")}
- p #{translate("create_unlimited_projects")}
- .col-md-4
- .card.features.text-centered
- i.fa.fa-clock-o.fa-5x
- h4 #{translate("full_doc_history")}
- p #{translate("never_loose_work")}
- .col-md-4
- .card.features.text-centered
- i.fa.fa-dropbox.fa-5x
- |
- i.fa.fa-github.fa-5x
- h4 #{translate("sync_to_dropbox_and_github")}
- p #{translate("access_projects_anywhere")}
+ .container(class="more-details" ng-cloak ng-if="plansVariant === 'more-details'")
+ include _plans_page_details_more
+ .container(ng-cloak ng-if="plansVariant != 'more-details'")
+ include _plans_page_details_less
diff --git a/services/web/public/coffee/main/event.coffee b/services/web/public/coffee/main/event.coffee
index 3ff8fc0aa4..22b7cbb4b6 100644
--- a/services/web/public/coffee/main/event.coffee
+++ b/services/web/public/coffee/main/event.coffee
@@ -93,4 +93,4 @@ define [
$('.navbar a').on "click", (e)->
href = $(e.target).attr("href")
if href?
- ga('send', 'event', 'navigation', 'top menu bar', href)
+ ga('send', 'event', 'navigation', 'top menu bar', href)
\ No newline at end of file
diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee
index 31d8e37f40..7851171524 100644
--- a/services/web/public/coffee/main/new-subscription.coffee
+++ b/services/web/public/coffee/main/new-subscription.coffee
@@ -9,6 +9,7 @@ define [
$scope.currencyCode = MultiCurrencyPricing.currencyCode
$scope.plans = MultiCurrencyPricing.plans
+ $scope.planCode = window.plan_code
$scope.switchToStudent = ()->
currentPlanCode = window.plan_code
@@ -234,3 +235,6 @@ define [
{code:'WK',name:'Wake Island'},{code:'WF',name:'Wallis and Futuna'},{code:'EH',name:'Western Sahara'},{code:'YE',name:'Yemen'},
{code:'ZM',name:'Zambia'},{code:'AX',name:'Åland Islandscode:'}
]
+
+ sixpack.participate 'plans', ['default', 'more-details'], (chosenVariation, rawResponse)->
+ $scope.plansVariant = chosenVariation
\ No newline at end of file
diff --git a/services/web/public/coffee/main/plans.coffee b/services/web/public/coffee/main/plans.coffee
index 9a62420d66..7eb63607c0 100644
--- a/services/web/public/coffee/main/plans.coffee
+++ b/services/web/public/coffee/main/plans.coffee
@@ -3,7 +3,6 @@ define [
"libs/recurly-4.8.5"
], (App, recurly) ->
-
App.factory "MultiCurrencyPricing", () ->
currencyCode = window.recomendedCurrency
@@ -146,17 +145,16 @@ define [
}
- App.controller "PlansController", ($scope, $modal, event_tracking, abTestManager, MultiCurrencyPricing, $http, sixpack) ->
+ App.controller "PlansController", ($scope, $modal, event_tracking, abTestManager, MultiCurrencyPricing, $http, sixpack, $filter) ->
$scope.showPlans = false
-
- $scope.plansVariant = 'default'
$scope.shouldABTestPlans = window.shouldABTestPlans
if $scope.shouldABTestPlans
- $scope.showPlans = true
- else
- $scope.showPlans = true
+ sixpack.participate 'plans-details', ['default', 'more-details'], (chosenVariation, rawResponse)->
+ $scope.plansVariant = chosenVariation
+
+ $scope.showPlans = true
$scope.plans = MultiCurrencyPricing.plans
@@ -169,44 +167,57 @@ define [
$scope.ui =
view: "monthly"
- $scope.changeCurreny = (newCurrency)->
+ $scope.changeCurreny = (e, newCurrency)->
+ e.preventDefault()
$scope.currencyCode = newCurrency
# because ternary logic in angular bindings is hard
$scope.getCollaboratorPlanCode = () ->
view = $scope.ui.view
- variant = $scope.plansVariant
if view == "annual"
- if variant == "default"
- return "collaborator-annual"
- else
- return "collaborator-annual_#{variant}"
+ return "collaborator-annual"
else
- if variant == "default"
- return "collaborator#{$scope.planQueryString}"
- else
- return "collaborator_#{variant}"
+ return "collaborator#{$scope.planQueryString}"
- $scope.signUpNowClicked = (plan, annual)->
- event_tracking.sendMB 'plans-page-start-trial', {plan}
+ $scope.signUpNowClicked = (plan, location)->
if $scope.ui.view == "annual"
plan = "#{plan}_annual"
- event_tracking.send 'subscription-funnel', 'sign_up_now_button', plan
+ plan = eventLabel(plan, location)
+ event_tracking.sendMB 'plans-page-start-trial', {plan}
+ event_tracking.send 'subscription-funnel', 'sign_up_now_button', plan
+ if $scope.shouldABTestPlans
+ sixpack.convert 'plans-details'
- $scope.switchToMonthly = ->
- $scope.ui.view = "monthly"
- event_tracking.send 'subscription-funnel', 'plans-page', 'monthly-prices'
+ $scope.switchToMonthly = (e, location) ->
+ uiView = 'monthly'
+ switchEvent(e, uiView + '-prices', location)
+ $scope.ui.view = uiView
- $scope.switchToStudent = ->
- $scope.ui.view = "student"
- event_tracking.send 'subscription-funnel', 'plans-page', 'student-prices'
+ $scope.switchToStudent = (e, location) ->
+ uiView = 'student'
+ switchEvent(e, uiView + '-prices', location)
+ $scope.ui.view = uiView
- $scope.switchToAnnual = ->
- $scope.ui.view = "annual"
- event_tracking.send 'subscription-funnel', 'plans-page', 'annual-prices'
+ $scope.switchToAnnual = (e, location) ->
+ uiView = 'annual'
+ switchEvent(e, uiView + '-prices', location)
+ $scope.ui.view = uiView
$scope.openGroupPlanModal = () ->
$modal.open {
templateUrl: "groupPlanModalTemplate"
}
event_tracking.send 'subscription-funnel', 'plans-page', 'group-inquiry-potential'
+
+ eventLabel = (label, location) ->
+ if location && $scope.plansVariant != 'default'
+ label = label + '-' + location
+ if $scope.plansVariant != 'default'
+ label += '-exp-' + $scope.plansVariant
+ label
+
+ switchEvent = (e, label, location) ->
+ e.preventDefault()
+ gaLabel = eventLabel(label, location)
+ event_tracking.send 'subscription-funnel', 'plans-page', gaLabel
+
diff --git a/services/web/public/img/advocates/erdogmus.jpg b/services/web/public/img/advocates/erdogmus.jpg
new file mode 100644
index 0000000000..e70a641fd0
Binary files /dev/null and b/services/web/public/img/advocates/erdogmus.jpg differ
diff --git a/services/web/public/img/advocates/henderson.jpg b/services/web/public/img/advocates/henderson.jpg
new file mode 100644
index 0000000000..549570955c
Binary files /dev/null and b/services/web/public/img/advocates/henderson.jpg differ
diff --git a/services/web/public/stylesheets/app/plans.less b/services/web/public/stylesheets/app/plans.less
index b830db9b23..41e2fc80f1 100644
--- a/services/web/public/stylesheets/app/plans.less
+++ b/services/web/public/stylesheets/app/plans.less
@@ -32,7 +32,7 @@
padding-bottom: @line-height-computed * 2;
}
}
-
+
.circle {
font-size: 1.5rem;
font-weight: 700;
@@ -63,6 +63,12 @@
}
.card .btn { white-space:normal; }
+
+ .top-switch {
+ .currency-dropdown {
+ margin-right: -15px;
+ }
+ }
}
#changePlanSection {
@@ -127,4 +133,261 @@ input.paymentTypeOption.ng-valid {
text-align: right;
}
+/**
+ Plans Test
+*/
+@best-val-height: 35px;
+@highlight-border: 3px;
+@highlight-color: #d3584b;
+@gray-med: #6d6d6d;
+@white-med: #fdfdfd;
+.more-details {
+ .best-value {
+ color: @red;
+ line-height: @line-height-computed;
+ }
+ blockquote {
+ footer{
+ /* accessibility fix */
+ color: @gray-med;
+ }
+ }
+ .btn-header {
+ font-family: @font-family-sans-serif;
+ margin-left: 10px;
+ margin-top: -10px;
+ text-shadow: 0 0 0;
+ }
+ .card-first, .card-last {
+ background: @white-med;
+ }
+ .card-highlighted {
+ border: @highlight-border solid @gray-lighter;
+ padding-top: 10px!important;
+ .best-value {
+ margin-bottom: 15px;
+ }
+ .card-header {
+ padding-bottom: 22px; /* align hr with other plans */
+ }
+ }
+ .card-header {
+ margin-bottom: 15px;
+ }
+ .circle {
+ /* accessibility fix */
+ span.small {
+ color: rgba(255, 255, 255, 0.85)
+ }
+ }
+ .circle-img {
+ border-radius: 50%;
+ float: right;
+ height: 100px;
+ overflow: hidden;
+ position: relative;
+ width: 100px;
+ img {
+ display: inline;
+ margin: 0 auto;
+ width: 100%;
+ }
+ }
+ .faq:last-child {
+ p {
+ margin-bottom: 0;
+ }
+ }
+ .questions-header {
+ color: @red;
+ line-height: 37px;
+ margin: 0;
+ text-align: right;
+ }
+ .tagline {
+ margin-bottom: 20px;
+ }
+ /* Media Queries */
+ @media (max-width: @screen-md-min) {
+ .card-highlighted {
+ /*override style in cards.less */
+ margin-top: @line-height-computed!important;
+ }
+ .circle-img {
+ float: left;
+ margin: 0 15px;
+ }
+ }
+ @media (min-width: @screen-md-min) {
+ blockquote {
+ margin-bottom: 0;
+ }
+ .faq {
+ .row:nth-child(2) {
+ h3 {
+ margin-top: 0;
+ }
+ }
+ }
+ }
+}
+.student-disclaimer {
+ font-size: 14px; /* match .paymentPageFeatures p */
+ color: @gray; /* match .paymentPageFeatures p */
+ margin: 12.5px 0 0 0;
+}
+
+/**
+ Plans Table
+*/
+.plans-table {
+ border: 1px solid @gray-lighter;
+ background-color: @white-med;
+ margin: @best-val-height 0 15px 0;
+ table-layout: fixed;
+ width: 100%;
+
+ th, td {
+ -moz-background-clip: padding;
+ -webkit-background-clip: padding;
+ background-clip: padding-box; /* needed for firefox when there is bg color */
+ border: 1px solid @gray-lighter;
+ padding: 6px;
+ text-align: center;
+ vertical-align: middle;
+ }
+
+ td {
+ font-weight: bold;
+ }
+
+ th {
+ border-top: 0;
+ font-family: @headings-font-family;
+ font-size: @font-size-h2;
+ font-weight: @headings-font-weight;
+ line-height: @headings-line-height;
+ padding: 18px;
+ }
+
+ th:first-child, td:first-child {
+ border-left: 0;
+ }
+
+ th:last-child, td:last-child {
+ border-right: 0;
+ }
+
+ td:first-child {
+ font-weight: bold;
+ padding-left: 18px;
+ text-align: left;
+ }
+
+ tr:first-child {
+ th {
+ position: relative;
+ /* keep here position here, otherwise messes up border on safari */
+ }
+ }
+
+ tr:last-child {
+ td {
+ border-bottom: 0;
+ padding: 18px;
+ }
+ /* highlighted column */
+ td:nth-child(3) {
+ position: relative;
+ /* keep here position here, otherwise messes up border on safari when there is a bg color */
+ &:before {
+ /* needed for safafi */
+ border-top: 1px solid @gray-lighter;
+ content: '';
+ left: 0;
+ position: absolute;
+ top: -1px;
+ width: 100%;
+ }
+ }
+ td:first-child {
+ border: 0;
+ }
+ }
+
+ .fa-check {
+ color: @green;
+ }
+
+ /* accessibility fixes */
+ .small {
+ color: @gray-med;
+ }
+
+ /* highlighted column */
+ td:nth-child(3), th:nth-child(3) {
+ background-color: white;
+ border-left: @highlight-border solid @gray-lighter;
+ border-right: @highlight-border solid @gray-lighter;
+ }
+ .outer {
+ left: -@highlight-border;
+ right: -@highlight-border;
+ position: absolute;
+
+ .outer-content {
+ background: white;
+ border: @highlight-border solid @gray-lighter;
+ border-radius: @border-radius-base;
+ font-size: @font-size-base;
+ font-family: @font-family-sans-serif;
+ font-weight: bold;
+ height: @best-val-height;
+ padding-top: 10px;
+ }
+ }
+ .outer.outer-top {
+ top: -@best-val-height;
+ .outer-content {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ border-bottom: 0;
+ }
+ }
+ .outer.outer-btm {
+ bottom: -@best-val-height/2;
+ .outer-content {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-top: 0;
+ height: @best-val-height/2;
+ }
+ }
+
+ /* highlight rows on hover */
+ tr:hover {
+ td {
+ background-color: @gray-lightest;
+ }
+ }
+ tr:first-child:hover {
+ background-color: transparent;
+ }
+ tr:last-child:hover {
+ background-color: transparent;
+ td {
+ background-color: transparent;
+ }
+ }
+
+ /* tooltip */
+ sup {
+ color: @red;
+ cursor: pointer;
+ margin-left: 5px;
+ }
+ .tooltip.in {
+ min-width: 200px
+ }
+}
diff --git a/services/web/public/stylesheets/core/scaffolding.less b/services/web/public/stylesheets/core/scaffolding.less
index df52ab1fb4..abadcabae9 100755
--- a/services/web/public/stylesheets/core/scaffolding.less
+++ b/services/web/public/stylesheets/core/scaffolding.less
@@ -161,4 +161,7 @@ hr {
margin-top: @line-height-computed / 2;
}
+.row-spaced-large {
+ margin-top: @line-height-computed * 2;
+}
diff --git a/services/web/public/stylesheets/core/type.less b/services/web/public/stylesheets/core/type.less
index ac1dcf3765..c8f2dcb9f1 100755
--- a/services/web/public/stylesheets/core/type.less
+++ b/services/web/public/stylesheets/core/type.less
@@ -122,6 +122,11 @@ cite { font-style: normal; }
text-align: center;
}
+// Transformations
+.text-capitalize {
+ text-transform: capitalize;
+}
+
// Contextual backgrounds
// For now we'll leave these alongside the text classes until v4 when we can
// safely shift things around (per SemVer rules).
@@ -256,7 +261,14 @@ blockquote {
vertical-align: -0.4em;
line-height: 0.1em;
}
-
+
+ &:after {
+ content: close-quote;
+ display: inherit;
+ height: 0;
+ visibility: hidden;
+ }
+
p {
display: inline;
}