Bläddra i källkod

Merge pull request #18931 from overleaf/jel-new-files-for-light-touch

[web] New files for plans light touch redesign

GitOrigin-RevId: beb517543c8341bbd8124fab87b532f62c5a0c11
Jessica Lawshe 2 år sedan
förälder
incheckning
1db5593c5a

+ 1 - 1
services/web/app/views/subscriptions/plans-light-design.pug

@@ -32,7 +32,7 @@ block content
 							+eyebrow(translate('plans_and_pricing_lowercase'))
 							| #{translate('choose_your_plan')}
 
-				include ./plans/_cards_controls_tables
+				include ./plans/light-redesign/_cards_controls_tables
 					
 				+currency_and_payment_methods()
 

+ 74 - 0
services/web/app/views/subscriptions/plans/light-redesign/_cards_controls_tables.pug

@@ -0,0 +1,74 @@
+include ./_mixins
+
+.row.plans-v2-top-switch
+	.col-xs-12
+		ul.nav.plans-v2-nav(role="tablist")
+			li.active.plans-v2-top-switch-individual(
+				data-ol-plans-v2-view-tab='individual'
+				event-tracking="plans-page-toggle-plan"
+				event-tracking-mb="true"
+				event-tracking-trigger="click"
+				event-segmentation='{"button": "individual"}'
+				role="presentation"
+			)
+				button.btn.btn-default-outline(role="tab" aria-controls="panel-individual" aria-selected="true") #{translate("indvidual_plans")}
+			li.plans-v2-top-switch-group(
+				data-ol-plans-v2-view-tab='group'
+				event-tracking="plans-page-toggle-plan"
+				event-tracking-mb="true"
+				event-tracking-trigger="click"
+				event-segmentation='{"button": "group"}'
+				role="presentation"
+			)
+				button.btn.btn-default-outline(
+					aria-controls="panel-group"
+					href="#"
+					role="tab"
+					aria-selected="false"
+				)
+					span #{translate("group_plans")} 
+					span (#{translate("save_30_percent_or_more")})
+			li.plans-v2-top-switch-student(
+				data-ol-plans-v2-view-tab='student'
+				event-tracking="plans-page-toggle-plan"
+				event-tracking-mb="true"
+				event-tracking-trigger="click"
+				event-segmentation='{"button": "student"}'
+				role="presentation"
+			)
+				button.btn.btn-default-outline(
+					aria-controls="panel-student"
+					href="#"
+					role="tab"
+					aria-selected="false"					
+				) #{translate("student_plans")}
+
++monthly_annual_switch("annual", "plans-page-toggle-period")
+
+.row(hidden data-ol-plans-v2-license-picker-container)
+	.col-sm-12
+		+group_plans_license_picker()
+
++table_sticky_header_all(plansConfig)
+
+.row.plans-v2-table-container(hidden data-ol-plans-v2-period='monthly')
+	.col-sm-12(data-ol-plans-v2-view='individual' role="tabpanel")
+		.row
+			+table_individual('monthly')
+	.col-sm-12(hidden data-ol-plans-v2-view='student' role="tabpanel")
+		.row
+			+table_student('monthly')
+
+.row.plans-v2-table-container(data-ol-plans-v2-period='annual')
+	.col-sm-12(data-ol-plans-v2-view='individual' id="panel-individual" role="tabpanel")
+		.row
+			+table_individual('annual')
+	.col-sm-12(hidden data-ol-plans-v2-view='group' id="panel-group" role="tabpanel")
+		.row
+			+table_group('annual')
+	.col-sm-12(hidden data-ol-plans-v2-view='student' id="panel-student" role="tabpanel")
+		.row
+			+table_student('annual')
+
+//- sticky header on mobile will be "hidden" (by removing its sticky position) if it reaches this div
+.invisible(aria-hidden="true" data-ol-plans-v2-table-sticky-header-stop)

+ 642 - 0
services/web/app/views/subscriptions/plans/light-redesign/_mixins.pug

@@ -0,0 +1,642 @@
+mixin features_premium
+	li  
+	li
+		strong #{translate('all_premium_features')}
+	li #{translate('sync_dropbox_github')}
+	li #{translate('full_doc_history')}
+	li #{translate('track_changes')}
+	li + #{translate('more').toLowerCase()}
+
+mixin gen_localized_price_for_plan_view(plan, view)
+	span #{formatCurrency(settings.localizedPlanPricing[recommendedCurrency][plan][view], recommendedCurrency, language, true)}
+
+mixin currency_and_payment_methods()
+	.row.row-spaced-large.text-centered
+		.col-xs-12
+			p.text-centered
+				strong #{translate("all_prices_displayed_are_in_currency", { recommendedCurrency })}
+				|  
+				span #{translate("subject_to_additional_vat")}
+			i.fa.fa-cc-mastercard.fa-2x(aria-hidden="true")  
+			span.sr-only #{translate('payment_method_accepted', { paymentMethod: 'Mastercard' })}
+			i.fa.fa-cc-visa.fa-2x(aria-hidden="true")  
+			span.sr-only #{translate('payment_method_accepted', { paymentMethod: 'Visa' })}
+			i.fa.fa-cc-amex.fa-2x(aria-hidden="true")  
+			span.sr-only #{translate('payment_method_accepted', { paymentMethod: 'Amex' })}
+			i.fa.fa-cc-paypal.fa-2x(aria-hidden="true")  
+			span.sr-only #{translate('payment_method_accepted', { paymentMethod: 'Paypal' })}
+
+
+mixin plans_v2_table(period, config)
+	- var baseColspan = config.baseColspan || 1
+	- var maxColumn = config.maxColumn || 4
+	- var tableHeadKeys = Object.keys(config.tableHead)
+	tr(class=`plans-v2-table-cols-${tableHeadKeys.length}`)
+		th(colspan=baseColspan)
+		- for (var i = 0; i < maxColumn; i++)
+			- var tableHeadKey = tableHeadKeys[i]
+			- var tableHeadOptions = Object.values(config.tableHead)[i] || {}
+			- var colspan = tableHeadOptions.colspan || baseColspan
+			- var highlighted = i === config.highlightedColumn.index
+			- var eventTrackingKey = config.eventTrackingKey
+			- var additionalEventSegmentation = config.additionalEventSegmentation || {}
+			-
+				if (highlighted) {
+					var thClass = 'plans-v2-table-green-highlighted'
+				} else if (i === config.highlightedColumn.index - 1) {
+					var thClass = 'plans-v2-table-cell-before-green-highlighted-column'
+				} else {
+					var thClass = ''
+				}
+				thClass += ' plans-v2-table-column-header'
+				if (colspan > 1) {
+					var scopeValue = 'colgroup'
+				}
+				else {
+					var scopeValue = 'col'
+				}
+			case tableHeadKey
+				when 'individual_free'
+					- var ariaLabel = translate("free")
+				when 'individual_collaborator'
+					- var ariaLabel = translate("standard")
+				when 'individual_professional'
+					- var ariaLabel = translate("professional")
+				when 'group_collaborator'
+					- var ariaLabel = translate("group_standard")
+				when 'group_professional'
+					- var ariaLabel = translate("group_professional")
+				when 'group_organization'
+					- var ariaLabel = translate("organization")
+				when 'student_free'
+					- var ariaLabel = translate("free")
+				when 'student_student'
+					- var ariaLabel = translate("student")
+				when 'student_university'
+					- var ariaLabel = translate("university")
+				default
+					- var ariaLabel = undefined
+			th(
+				aria-label=ariaLabel
+				class=thClass
+				colspan=colspan
+				scope=scopeValue
+			)
+				.plans-v2-table-th
+					if (highlighted)
+						p.plans-v2-table-green-highlighted-text #{translate(config.highlightedColumn.text[period]).toUpperCase()}
+					case tableHeadKey
+						when 'individual_free'
+							+table_head_individual_free(highlighted, period)
+						when 'individual_collaborator'
+							+table_head_individual_collaborator(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+						when 'individual_professional'
+							+table_head_individual_professional(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+						when 'group_collaborator'
+							+table_head_group_collaborator(highlighted, eventTrackingKey, additionalEventSegmentation)
+						when 'group_professional'
+							+table_head_group_professional(highlighted, eventTrackingKey, additionalEventSegmentation)
+						when 'group_organization'
+							+table_head_group_organization(highlighted, eventTrackingKey, additionalEventSegmentation)
+						when 'student_free'
+							+table_head_student_free(highlighted, period)
+						when 'student_student'
+							+table_head_student_student(highlighted, eventTrackingKey, additionalEventSegmentation, period, tableHeadOptions.showExtraContent)
+						when 'student_university'
+							+table_head_student_university(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+
+	for featuresPerSection in config.features
+		- var dividerColspan = Object.values(config.tableHead).reduce((prev, curr) => (prev) + (curr.colspan || 1), baseColspan)
+		if featuresPerSection.divider
+			tr.plans-v2-table-divider
+				td(
+					colspan=dividerColspan
+					class=((config.highlightedColumn.index === Object.keys(config.tableHead).length - 1) ? 'plans-v2-table-divider-highlighted' : '')
+				)
+					div
+						b.plans-v2-table-divider-label #{translate(featuresPerSection.dividerLabel)}
+						//- will only appear on screen width >= 768px (using CSS)
+						i.fa.fa-question-circle.plans-v2-table-divider-question-icon(
+							data-toggle="tooltip"
+							title=translate(featuresPerSection.dividerInfo),
+							data-placement="top"
+						)
+						//- will only appear on screen width < 768px (using CSS)
+						span.plans-v2-table-divider-learn-more-container
+							span (
+							span.plans-v2-table-divider-learn-more-text(
+								data-toggle="tooltip"
+								title=translate(featuresPerSection.dividerInfo),
+								data-placement="top"
+							) #{translate("learn_more_lowercase")}
+							span )
+						span.sr-only #{translate(featuresPerSection.dividerInfo)}
+		for feature, featureIndex in featuresPerSection.items
+			tr(
+				class=(featureIndex === (featuresPerSection.items.length - 1) ? `plans-v2-table-row-last-row-per-section plans-v2-table-cols-${tableHeadKeys.length}` : `plans-v2-table-cols-${tableHeadKeys.length}`)
+			)
+				th(
+					class="plans-v2-table-row-header"
+					event-tracking="plans-page-table"
+					event-tracking-trigger="hover"
+					event-tracking-ga="subscription-funnel"
+					event-tracking-label=`${feature.feature}`
+					colspan=baseColspan
+					scope="row"
+				)
+					.plans-v2-table-feature-name
+						if feature.info
+							span #{translate(feature.feature)}
+							//- will only appear on screen width >= 768px (using CSS)
+							i.fa.fa-question-circle.plans-v2-table-feature-name-question-icon(
+								data-toggle="tooltip"
+								title=translate(feature.info),
+								data-placement="right"
+							)
+							//- will only appear on screen width < 768px (using CSS)
+							span.plans-v2-table-feature-name-learn-more-container
+								span (
+								span.plans-v2-table-feature-name-learn-more-text(
+									data-toggle="tooltip"
+									title=translate(feature.info),
+									data-placement="top"
+								) #{translate("learn_more_lowercase")}
+								span )
+							span.sr-only #{translate(feature.info)}
+						else
+							| #{translate(feature.feature)}
+				for plan, planIndex in Object.keys(feature.plans)
+					- var tableHeadOptions = Object.values(config.tableHead)[planIndex] || {}
+					- var colspan = tableHeadOptions.colspan || baseColspan
+					-
+						if (planIndex === config.highlightedColumn.index) {
+							var tdClass = 'plans-v2-table-green-highlighted'
+						} else if (planIndex === config.highlightedColumn.index - 1) {
+							var tdClass = 'plans-v2-table-cell-before-green-highlighted-column'
+						} else {
+							var tdClass = ''
+						}
+					td(
+						class=tdClass
+						colspan=colspan
+					)
+						+table_cell(feature, plan)
+
+mixin table_individual(period)
+	table.card.plans-v2-table.plans-v2-table-individual
+		+plans_v2_table(period, plansConfig.individual)
+
+mixin table_group
+	table.card.plans-v2-table.plans-v2-table-group
+		+plans_v2_table('annual', plansConfig.group)
+
+mixin table_student(period)
+	table.card.plans-v2-table.plans-v2-table-student
+		+plans_v2_table(period, plansConfig.student)
+
+mixin table_head_individual_free(highlighted, period)
+	.plans-v2-table-th-content
+		p.plans-v2-table-th-content-title #{translate("free")}
+		+table_head_price('free', period)
+		.plans-v2-table-btn-buy-container-mobile
+			+btn_buy_individual_free(highlighted)
+		ul.plans-v2-table-th-content-benefit
+			li #{translate("one_collaborator")}
+		.plans-v2-table-btn-buy-container-desktop
+			+btn_buy_individual_free(highlighted)
+
+mixin table_head_individual_collaborator(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+	.plans-v2-table-th-content
+		p.plans-v2-table-th-content-title #{translate("standard")}
+		+table_head_price('collaborator', period)
+		.plans-v2-table-btn-buy-container-mobile
+			+btn_buy_individual_collaborator(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+		ul.plans-v2-table-th-content-benefit
+			li !{translate("x_collaborators_per_project", {collaboratorsCount: '10'})}
+			li #{translate("all_premium_features")}
+		.plans-v2-table-btn-buy-container-desktop
+			+btn_buy_individual_collaborator(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+
+mixin table_head_individual_professional(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+	.plans-v2-table-th-content
+		p.plans-v2-table-th-content-title #{translate("professional")}
+		+table_head_price('professional', period)
+		.plans-v2-table-btn-buy-container-mobile
+			+btn_buy_individual_professional(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+		ul.plans-v2-table-th-content-benefit
+			li !{translate("unlimited_collabs_rt",{},["b"])}
+			li #{translate("all_premium_features")}
+		.plans-v2-table-btn-buy-container-desktop
+			+btn_buy_individual_professional(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+
+mixin table_head_group_collaborator(highlighted, eventTrackingKey, additionalEventSegmentation)
+	.plans-v2-table-th-content
+		p.plans-v2-table-th-content-title #{translate("group_standard")}
+		.plans-v2-table-price-container
+			strike.plans-v2-table-price-before-discount
+				+gen_localized_price_for_plan_view('collaborator', 'annual')
+			p.plans-v2-table-price
+				span(data-ol-plans-v2-group-price-per-user='collaborator') #{initialLocalizedGroupPrice.pricePerUser.collaborator}
+			p.plans-v2-table-price-period-label
+				| #{translate('per_user_year')}
+		.plans-v2-table-btn-buy-container-mobile
+			+btn_buy_group_collaborator(highlighted, eventTrackingKey)
+			+additional_link_group(eventTrackingKey, additionalEventSegmentation, 'group_collaborator')
+		ul.plans-v2-table-th-content-benefit
+			li #{translate("up_to")} !{translate("x_collaborators_per_project", {collaboratorsCount: '10'})}
+			li
+				+table_head_group_total_per_year('collaborator')
+		.plans-v2-table-btn-buy-container-desktop
+			+btn_buy_group_collaborator(highlighted, eventTrackingKey)
+			+additional_link_group(eventTrackingKey, additionalEventSegmentation, 'group_collaborator')
+
+mixin table_head_group_professional(highlighted, eventTrackingKey, additionalEventSegmentation)
+	.plans-v2-table-th-content
+		p.plans-v2-table-th-content-title #{translate("group_professional")}
+		.plans-v2-table-price-container
+			strike.plans-v2-table-price-before-discount
+				+gen_localized_price_for_plan_view('professional', 'annual')
+			p.plans-v2-table-price
+				span(data-ol-plans-v2-group-price-per-user='professional') #{initialLocalizedGroupPrice.pricePerUser.professional}
+			p.plans-v2-table-price-period-label
+				| #{translate('per_user_year')}
+		.plans-v2-table-btn-buy-container-mobile
+			+btn_buy_group_professional(highlighted, eventTrackingKey)
+			+additional_link_group(eventTrackingKey, additionalEventSegmentation, 'group_professional')
+		ul.plans-v2-table-th-content-benefit
+			li #{translate("unlimited_collaborators_in_each_project")}
+			li
+				+table_head_group_total_per_year('professional')
+		.plans-v2-table-btn-buy-container-desktop
+			+btn_buy_group_professional(highlighted, eventTrackingKey)
+			+additional_link_group(eventTrackingKey, additionalEventSegmentation, 'group_professional')
+
+mixin table_head_group_total_per_year(groupPlan)
+	- var initialLicenseSize = '2'
+	span.plans-v2-group-total-price(data-ol-plans-v2-group-total-price=groupPlan) #{initialLocalizedGroupPrice.price[groupPlan]}
+	| &nbsp;
+	for licenseSize in groupPlanModalOptions.sizes
+		span(
+			hidden=(licenseSize !== initialLicenseSize)
+			data-ol-plans-v2-table-th-group-license-size=licenseSize
+		) !{translate("total_per_year_for_x_users", {licenseSize})}
+
+mixin table_head_group_organization(highlighted, eventTrackingKey, additionalEventSegmentation)
+	- var segmentation = JSON.stringify(Object.assign({}, {button: 'group_organization-link', location: 'table-header-list', period: 'annual'}, additionalEventSegmentation))
+	.plans-v2-table-th-content
+		p.plans-v2-table-th-content-title #{translate("organization")}
+		.plans-v2-table-comments-icon
+			i.fa.fa-comments-o
+		.plans-v2-table-btn-buy-container-mobile
+			+btn_buy_group_organization(highlighted, eventTrackingKey)
+			small.plans-v2-table-th-content-additional-link.invisible(aria-hidden="true")
+		ul.plans-v2-table-th-content-benefit
+			li #{translate("best_choices_companies_universities_non_profits")}
+			li #{translate("for_groups_or_site_wide")}
+			li
+				a(
+					target="_blank"
+					href="/for/contact-sales"
+					event-tracking="plans-page-click"
+					event-tracking-mb="true"
+					event-tracking-trigger="click"
+					event-segmentation=segmentation
+				) #{translate("also_available_as_on_premises")}
+		.plans-v2-table-btn-buy-container-desktop
+			+btn_buy_group_organization(highlighted, eventTrackingKey)
+			small.plans-v2-table-th-content-additional-link.invisible(aria-hidden="true")
+
+mixin table_head_student_free(highlighted, period)
+	div.plans-v2-table-th-content
+		p.plans-v2-table-th-content-title #{translate("free")}
+		+table_head_price('free', period)
+		.plans-v2-table-btn-buy-container-mobile
+			+btn_buy_student_free(highlighted)
+		ul.plans-v2-table-th-content-benefit
+			li #{translate("one_collaborator")}
+		.plans-v2-table-btn-buy-container-desktop
+			+btn_buy_student_free(highlighted)
+
+mixin table_head_student_student(highlighted, eventTrackingKey, additionalEventSegmentation, period, showExtraContent)
+	div.plans-v2-table-th-content
+		p.plans-v2-table-th-content-title #{translate("student")}
+		+table_head_price('student', period)
+		.plans-v2-table-btn-buy-container-mobile
+			+btn_buy_student_student(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+		ul.plans-v2-table-th-content-benefit
+			li !{translate("x_collaborators_per_project", {collaboratorsCount: '6'})}
+			li #{translate("all_premium_features")}
+			if showExtraContent
+				li 
+					b !{translate("for_students_only")}
+
+		.plans-v2-table-btn-buy-container-desktop
+			+btn_buy_student_student(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+
+mixin table_head_student_university(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+	div.plans-v2-table-th-content
+		p.plans-v2-table-th-content-title #{translate("university")}
+		div.plans-v2-table-comments-icon
+			i.fa.fa-comments-o
+		.plans-v2-table-btn-buy-container-mobile
+			+btn_buy_student_university(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+		p.plans-v2-table-th-content-benefit !{translate("all_our_group_plans_offer_educational_discount", {}, [{name: 'b'}, {name: 'b'}])}
+		.plans-v2-table-btn-buy-container-desktop
+			+btn_buy_student_university(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+
+mixin table_head_price(plan, period)
+	div.plans-v2-table-price-container
+		if plan !== 'free' && period === 'annual'
+			strike.plans-v2-table-price-before-discount
+				+gen_localized_price_for_plan_view(plan, 'monthlyTimesTwelve')
+		p.plans-v2-table-price
+			+gen_localized_price_for_plan_view(plan, period)
+		p.plans-v2-table-price-period-label
+			if period == 'annual'
+				| #{translate("per_year")}
+			else
+				| #{translate("per_month")}
+
+mixin table_cell(feature, plan)
+	- var planValue = feature.plans[plan]
+	- var featureName = feature.feature
+
+	.plans-v2-table-cell
+		.plans-v2-table-cell-content(
+			data-ol-plans-v2-table-cell-plan=plan
+			data-ol-plans-v2-table-cell-feature=featureName
+		)
+			if (feature.value === 'str')
+				| !{translate(planValue, {}, ['strong'])}
+			else if (feature.value === 'bool')
+				if (planValue)
+					i.fa.fa-check(aria-hidden="true")
+					span.sr-only #{translate("feature_included")}
+				else
+					span(aria-hidden="true") -
+					span.sr-only #{translate("feature_not_included")}
+
+mixin group_plans_license_picker()
+	form.plans-v2-license-picker-form(data-ol-plans-v2-license-picker-form)
+		.plans-v2-license-picker-select-container
+			span #{translate("number_of_users_with_colon")}
+			select.plans-v2-license-picker-select(
+				name="plans-v2-license-picker-select"
+				id="plans-v2-license-picker-select"
+				autocomplete="off"
+				data-ol-plans-v2-license-picker-select
+				event-tracking="plans-page-group-size"
+				event-tracking-mb="true"
+				event-tracking-trigger="click"
+				event-tracking-element="select"
+			)
+				option(value="2") 2
+				option(value="3") 3
+				option(value="4") 4
+				option(value="5") 5
+				option(value="10") 10
+				option(value="20") 20
+				option(value="50") 50
+		.plans-v2-license-picker-educational-discount
+			label.plans-v2-license-picker-educational-discount-label(data-ol-plans-v2-license-picker-educational-discount-label)
+				input.plans-v2-license-picker-educational-discount-checkbox(
+					type="checkbox"
+					id="license-picker-educational-discount"
+					autocomplete="off"
+					data-ol-plans-v2-license-picker-educational-discount-input
+					event-tracking="plans-page-edu-discount"
+					event-tracking-mb="true"
+					event-tracking-trigger="click"
+					event-tracking-element="checkbox"
+				)
+				span #{translate("apply_educational_discount")}
+			//- will only appear on screen width >= 768px (using CSS)
+			i.fa.fa-question-circle.plans-v2-license-picker-educational-discount-question-icon(
+				data-toggle="tooltip"
+				title=translate("apply_educational_discount_info"),
+				data-placement="bottom"
+			)
+			//- will only appear on screen width < 768px (using CSS)
+			span.plans-v2-license-picker-educational-discount-learn-more-container
+				span (
+				span.plans-v2-license-picker-educational-discount-learn-more-text(
+					data-toggle="tooltip"
+					title=translate("apply_educational_discount_info"),
+					data-placement="bottom"
+				) #{translate("learn_more_lowercase")}
+				span )
+			span.sr-only #{translate("apply_educational_discount_info")}
+
+mixin btn_buy_individual(highlighted, eventTrackingKey, subscriptionPlan, period) 
+	a.btn.plans-v2-table-btn-buy(
+		data-ol-start-new-subscription=subscriptionPlan
+		data-ol-event-tracking-key=eventTrackingKey
+		data-ol-item-view=period
+		class=(highlighted ? 'btn-primary' : 'btn-default')
+	)
+		if (period === 'monthly')
+			span #{translate("try_for_free")}
+		else 
+			span #{translate("buy_now_no_exclamation_mark")}
+
+mixin btn_buy_individual_free()
+	if (!getSessionUser())
+		a.btn.plans-v2-table-btn-buy(
+			href="/register"
+			class=(highlighted ? 'btn-primary' : 'btn-default')
+		)
+			span #{translate("try_for_free")}
+	else
+		a.btn.plans-v2-table-btn-buy.invisible(
+			aria-hidden="true"
+			class=(highlighted ? 'btn-primary' : 'btn-default')
+		)
+
+mixin btn_buy_individual_collaborator(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+	+btn_buy_individual(highlighted, eventTrackingKey, 'collaborator', period)
+	if (period === 'monthly')
+		+additional_link_buy(eventTrackingKey, additionalEventSegmentation, 'collaborator', period)
+
+mixin btn_buy_individual_professional(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+	+btn_buy_individual(highlighted, eventTrackingKey, 'professional', period)
+	if (period === 'monthly')
+		+additional_link_buy(eventTrackingKey, additionalEventSegmentation, 'professional', period)
+
+mixin btn_buy_group_collaborator(highlighted, eventTrackingKey) 
+	a.btn.plans-v2-table-btn-buy(
+		data-ol-start-new-subscription='group_collaborator'
+		data-ol-event-tracking-key=eventTrackingKey
+		data-ol-item-view='annual'
+		data-ol-has-custom-href
+		data-ol-location='table-header'
+		class=(highlighted ? 'btn-primary' : 'btn-default')
+	)
+		span.hidden-desktop #{translate("customize")} 
+		span.hidden-mobile #{translate("customize_your_plan")}
+
+mixin btn_buy_group_professional(highlighted, eventTrackingKey) 
+	a.btn.plans-v2-table-btn-buy(
+		data-ol-start-new-subscription='group_professional'
+		data-ol-event-tracking-key=eventTrackingKey
+		data-ol-item-view='annual'
+		data-ol-has-custom-href
+		data-ol-location='table-header'
+		class=(highlighted ? 'btn-primary' : 'btn-default')
+	)
+		span.hidden-desktop #{translate("customize")} 
+		span.hidden-mobile #{translate("customize_your_plan")}
+
+mixin btn_buy_group_organization(highlighted, eventTrackingKey) 
+	a.btn.plans-v2-table-btn-buy(
+		data-ol-start-new-subscription='group_organization'
+		data-ol-event-tracking-key=eventTrackingKey
+		data-ol-item-view='annual'
+		data-ol-has-custom-href
+		data-ol-location='table-header'
+		href='/for/contact-sales'
+		target='_blank'
+		class=(highlighted ? 'btn-primary' : 'btn-default')
+	)
+		span #{translate("contact_us_lowercase")}
+
+mixin btn_buy_student_free(highlighted)
+	if (!getSessionUser())
+		a.btn.plans-v2-table-btn-buy(
+			href="/register"
+			class=(highlighted ? 'btn-primary' : 'btn-default')
+		)
+			span #{translate("try_for_free")}
+
+mixin btn_buy_student_student(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+	a.btn.plans-v2-table-btn-buy(
+		data-ol-start-new-subscription='student'
+		data-ol-event-tracking-key=eventTrackingKey
+		data-ol-item-view=period
+		data-ol-location='card'
+		class=(highlighted ? 'btn-primary' : 'btn-default')
+	)
+		if (period === 'monthly')
+			span #{translate("try_for_free")}
+		else 
+			span #{translate("buy_now_no_exclamation_mark")}
+	if (period === 'monthly')
+		+additional_link_buy(eventTrackingKey, additionalEventSegmentation, 'student', period)
+
+mixin btn_buy_student_university(highlighted, eventTrackingKey, additionalEventSegmentation, period)
+	- var segmentation = JSON.stringify(Object.assign({}, {button: 'student-university', location: 'table-header-list', period}, additionalEventSegmentation))
+	a.btn.plans-v2-table-btn-buy(
+		href="/for/contact-sales"
+		target="_blank"
+		event-tracking=eventTrackingKey
+		event-tracking-mb="true"
+		event-tracking-trigger="click"
+		event-segmentation=segmentation
+		class=(highlighted ? 'btn-primary' : 'btn-default')
+	)
+		span #{translate("contact_us_lowercase")}
+
+mixin additional_link_group(eventTrackingKey, additionalEventSegmentation, plan)
+	- var buttonSegmentation = plan + '-link'
+	- var segmentation = JSON.stringify(Object.assign({}, {button: buttonSegmentation, location: 'table-header'}, additionalEventSegmentation))
+	small.plans-v2-table-th-content-additional-link
+		| #{translate("or")} 
+		a(
+			href="/for/contact-sales"
+			target="_blank"
+			event-tracking=eventTrackingKey
+			event-tracking-mb="true"
+			event-tracking-trigger="click"
+			event-segmentation=segmentation
+		) #{translate("contact_us_lowercase")}
+
+mixin additional_link_buy(eventTrackingKey, additionalEventSegmentation, plan, period)
+	- var buttonSegmentation = plan + '-link'
+	- var segmentation = JSON.stringify(Object.assign({}, {button: buttonSegmentation, location: 'table-header', period}, additionalEventSegmentation))
+	- var itmCampaign = itm_campaign ? { itm_campaign } : {itm_campaign: 'plans'}
+	- var itmReferrer = itm_referrer ? { itm_referrer } : {}
+	- var qs = new URLSearchParams({planCode: plan, currency: recommendedCurrency, itm_content: 'card', ...itmCampaign, ...itmReferrer})
+	small.plans-v2-table-th-content-additional-link
+		| #{translate("or")} 
+		a(
+			href=`/user/subscription/new?${qs.toString()}`
+			event-tracking=eventTrackingKey
+			event-tracking-mb="true"
+			event-tracking-trigger="click"
+			event-segmentation=segmentation
+		) #{translate("buy_now_no_exclamation_mark")}
+
+mixin plans_v2_table_sticky_header(withSwitch, config)
+	- var tableHeadKeys = Object.keys(config.tableHead)
+	.row.plans-v2-table-sticky-header.sticky(
+		data-ol-plans-v2-table-sticky-header
+		class=(withSwitch ? 'plans-v2-table-sticky-header-with-switch' : 'plans-v2-table-sticky-header-without-switch')
+	)
+		- for (var i = 0; i < tableHeadKeys.length; i++)
+			- var tableHeadKey = tableHeadKeys[i]
+			- var translateKey = tableHeadKey.split('_')[1]
+			-
+				if (config.highlightedColumn.index === i) {
+					var elClass = 'plans-v2-table-sticky-header-item-green-highlighted'
+				} else {
+					var elClass = ''
+				}
+			.plans-v2-table-sticky-header-item(
+				class=elClass
+			)
+				case tableHeadKey
+					when 'individual_collaborator'
+						span #{translate('standard')}
+					when 'group_professional'
+						span #{translate(tableHeadKey)}
+					when 'group_collaborator'
+						span #{translate('group_standard')}
+					default
+						span #{translate(translateKey)}
+
+mixin table_sticky_header_all(plansConfig)
+	.row.plans-v2-table-sticky-header-container(
+		data-ol-plans-v2-view='individual'
+	)
+		+plans_v2_table_sticky_header(true, plansConfig.individual)
+	.row.plans-v2-table-sticky-header-container(
+		hidden
+		data-ol-plans-v2-view='group'
+	)
+		+plans_v2_table_sticky_header(false, plansConfig.group)
+	.row.plans-v2-table-sticky-header-container(
+		hidden
+		data-ol-plans-v2-view='student'
+	)
+		+plans_v2_table_sticky_header(true, plansConfig.student)
+
+mixin monthly_annual_switch(initialState, eventTracking, eventSegmentation)
+	- var monthlyAnnualToggleChecked = initialState === 'monthly'
+	.row
+		.col-md-4.col-md-offset-4.text-centered.plans-v2-m-a-switch-container(data-ol-plans-v2-m-a-switch-container)
+			.plans-v2-m-a-switch-annual-text-container
+				span.underline(data-ol-plans-v2-m-a-switch-text='annual') #{translate("annual")}
+				.tooltip.in.left.plans-v2-m-a-tooltip(
+					role="tooltip"
+					data-ol-plans-v2-m-a-tooltip
+					class=monthlyAnnualToggleChecked ? 'plans-v2-m-a-tooltip-monthly-selected' : ''
+				)
+					.tooltip-arrow
+					.tooltip-inner
+						span(hidden=!monthlyAnnualToggleChecked data-ol-tooltip-period='monthly') #{translate("save_20_percent_by_paying_annually")}
+						span(hidden=monthlyAnnualToggleChecked data-ol-tooltip-period='annual') #{translate("saving_20_percent")}
+
+			label.plans-v2-m-a-switch(data-ol-plans-v2-m-a-switch)
+				input(
+					type="checkbox"
+					checked=monthlyAnnualToggleChecked
+					role="switch"
+					aria-label=translate("select_monthly_plans")
+					autocomplete="off"
+					event-tracking=eventTracking
+					event-tracking-mb="true"
+					event-tracking-trigger="click"
+					event-tracking-element="checkbox"
+					event-segmentation=eventSegmentation
+				)
+				span
+			span(data-ol-plans-v2-m-a-switch-text='monthly') #{translate("monthly")}

+ 1295 - 0
services/web/frontend/stylesheets/app/plans/plans-light-touch-redesign.less

@@ -0,0 +1,1295 @@
+// m-a stands for: monthly annual
+
+@plans-v2-top-switch-item-height: 34px;
+@plans-v2-top-switch-item-border-radius: @plans-v2-top-switch-item-height / 2;
+@plans-v2-m-a-switch-height: 34px;
+@plans-v2-highlighted-text-height-desktop: 30px;
+@plans-v2-highlighted-text-height-mobile: 41px;
+@plans-v2-learn-more-link-color: hsl(206, 100%, 52%);
+@plans-v2-top-switch-group-width-mobile: 46%;
+@plans-v2-table-border-radius: 20px;
+@plans-v2-table-td-mobile-min-height: 34px;
+
+.plans-page {
+  p {
+    color: @neutral-70;
+    margin-bottom: @line-height-computed;
+  }
+
+  .plans-header {
+    h1,
+    h2 {
+      color: @neutral-70;
+    }
+  }
+
+  .circle {
+    font-size: 1.5rem;
+    font-weight: 700;
+    padding: 46px 18px;
+    margin: 0 auto @line-height-computed;
+    text-shadow: 0 -1px 1px darken(@link-color, 10%);
+    width: 120px;
+    height: 120px;
+    border-radius: 50%;
+    background-color: @brand-secondary;
+    color: white;
+    white-space: nowrap;
+    line-height: 1;
+
+    .small {
+      color: rgba(255, 255, 255, 0.85);
+      font-size: @font-size-base * 0.8;
+    }
+  }
+
+  .circle-lg {
+    height: 180px;
+    padding: 70px 8px;
+    width: 180px;
+  }
+
+  .circle-subtext {
+    font-size: 1rem;
+  }
+
+  .circle-img {
+    float: right;
+
+    @media (max-width: @screen-sm-max) {
+      float: left;
+      margin: 0 15px;
+    }
+  }
+
+  @media (max-width: @screen-xs-max) {
+    [data-ol-current-view='group'] [data-ol-plans-v2-m-a-switch-container] {
+      display: none;
+    }
+  }
+
+  .plans-v2-top-switch ul.plans-v2-nav {
+    display: flex;
+    justify-content: center;
+
+    li {
+      border-top: 1px solid @ol-blue-gray-3;
+      border-bottom: 1px solid @ol-blue-gray-3;
+      height: @plans-v2-top-switch-item-height;
+
+      &.plans-v2-top-switch-individual {
+        border-right: 1px solid @ol-blue-gray-3;
+        border-left: 1px solid @ol-blue-gray-3;
+        border-top-left-radius: @plans-v2-top-switch-item-border-radius;
+        border-bottom-left-radius: @plans-v2-top-switch-item-border-radius;
+      }
+
+      &.plans-v2-top-switch-student {
+        border-right: 1px solid @ol-blue-gray-3;
+        border-left: 1px solid @ol-blue-gray-3;
+        border-top-right-radius: @plans-v2-top-switch-item-border-radius;
+        border-bottom-right-radius: @plans-v2-top-switch-item-border-radius;
+      }
+
+      button {
+        padding: 4px 16px;
+        height: 100%;
+
+        // remove bootstrap default
+        &.btn:active {
+          box-shadow: none;
+        }
+
+        // on Firefox, there will be a visible dotted border on the li element
+        // when it's `active` / `focus`
+        // `outline: none` rule intends to eliminate that border
+        &:active,
+        &:focus {
+          outline: none;
+        }
+      }
+
+      &.active {
+        background-color: @ol-blue-gray-4;
+
+        button.btn-default-outline {
+          color: @white;
+        }
+      }
+
+      &:not(.active) {
+        background-color: @white;
+      }
+    }
+
+    @media (max-width: @screen-xs-max) {
+      margin: 0 13px;
+
+      li {
+        height: unset;
+
+        &.plans-v2-top-switch-individual,
+        &.plans-v2-top-switch-student {
+          width: (100% - @plans-v2-top-switch-group-width-mobile) / 2;
+        }
+
+        &.plans-v2-top-switch-group {
+          width: @plans-v2-top-switch-group-width-mobile;
+
+          // force newline for the second span
+          span:first-child::after {
+            content: '';
+            display: block;
+          }
+
+          span:last-child {
+            font-weight: 400;
+          }
+        }
+
+        &.plans-v2-top-switch-individual,
+        &.plans-v2-top-switch-student {
+          button {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            text-align: center;
+          }
+        }
+
+        button {
+          white-space: unset;
+          padding: 4px 8px;
+          font-size: 14px;
+          width: 100%;
+        }
+      }
+    }
+  }
+
+  a.btn.plans-v2-btn-header,
+  button.plans-v2-btn-header {
+    font-family: @font-family-sans-serif;
+    margin-top: @margin-sm;
+    text-shadow: 0 0 0;
+    background-color: @ol-blue;
+    color: @white;
+
+    &:hover {
+      color: @white;
+    }
+  }
+
+  .plans-v2-m-a-switch-container {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: relative;
+    margin-top: @margin-xl;
+
+    .underline {
+      text-decoration: underline;
+    }
+
+    &.disabled {
+      span {
+        color: @ol-blue-gray-2;
+      }
+
+      .plans-v2-m-a-switch {
+        input + span {
+          background-color: @ol-blue-gray-2;
+          cursor: not-allowed;
+        }
+      }
+    }
+
+    @media (max-width: @screen-xs-max) {
+      margin-top: 25px;
+
+      &[data-ol-current-view='group'] {
+        display: none;
+      }
+    }
+  }
+
+  .plans-v2-m-a-switch {
+    position: relative;
+    display: inline-block;
+    width: 60px;
+    height: @plans-v2-m-a-switch-height;
+    margin: 0 @margin-lg;
+
+    input {
+      opacity: 0;
+      width: 0;
+      height: 0;
+    }
+
+    input + span {
+      background-color: @ol-green;
+    }
+
+    input:checked + span {
+      background-color: @ol-blue-gray-4;
+    }
+
+    input:checked + span::before {
+      transform: translateX(26px);
+    }
+
+    span {
+      position: absolute;
+      cursor: pointer;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      background-color: @ol-blue-gray-1;
+      transition: 0.4s;
+      border-radius: @plans-v2-m-a-switch-height;
+
+      &::before {
+        position: absolute;
+        content: '';
+        height: 32px;
+        width: 32px;
+        left: 1px;
+        bottom: 1px;
+        background-color: @white;
+        transition: 0.4s;
+        border-radius: 50%;
+      }
+    }
+  }
+
+  .plans-v2-m-a-switch-annual-text-container {
+    position: relative;
+  }
+
+  .plans-v2-m-a-tooltip {
+    position: absolute;
+    color: @white;
+    width: auto;
+    border-radius: 4px;
+    right: 110%;
+    top: 0;
+    z-index: 0;
+
+    &.tooltip.in.left {
+      .tooltip-inner {
+        background-color: @ol-green;
+      }
+
+      .tooltip-arrow {
+        border-left-color: @ol-green;
+      }
+    }
+
+    span {
+      white-space: nowrap;
+    }
+
+    @media (max-width: @screen-xs-max) {
+      right: -31%;
+      top: unset;
+
+      &.tooltip.in.bottom {
+        .tooltip-inner {
+          background-color: @ol-green;
+        }
+
+        .tooltip-arrow {
+          border-left-color: unset;
+          border-bottom-color: @ol-green;
+        }
+      }
+
+      &.plans-v2-m-a-tooltip-monthly-selected {
+        right: -119%;
+      }
+
+      span {
+        font-size: @font-size-extra-small;
+        position: relative;
+      }
+    }
+  }
+
+  .page-header.top-page-header {
+    // remove page-header border & margin on mobile
+    @media (max-width: @screen-xs-max) {
+      border-bottom: none;
+      margin-bottom: 0;
+      padding-bottom: 0;
+
+      h1 {
+        margin-top: 0;
+      }
+    }
+  }
+
+  .plans-v2-table-comments-icon {
+    height: 65px;
+
+    i {
+      font-size: 50px;
+    }
+
+    @media (max-width: @screen-xs-max) {
+      height: 58px;
+    }
+  }
+
+  .plans-v2-table-container {
+    position: relative;
+  }
+
+  .plans-v2-license-picker-form {
+    margin-top: 25px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+
+    @media (max-width: @screen-xs-max) {
+      font-size: @font-size-small;
+      margin-top: 15px;
+      flex-direction: column;
+    }
+  }
+
+  .plans-v2-license-picker-select {
+    background-color: @white;
+    border: 1px solid #cccccc;
+    width: 64px;
+    height: 30px;
+    margin: 0 @margin-sm;
+    padding-left: @padding-sm;
+
+    @media (max-width: @screen-xs-max) {
+      margin: 0 15px;
+    }
+  }
+
+  .plans-v2-license-picker-educational-discount {
+    display: flex;
+    align-items: center;
+
+    @media (max-width: @screen-xs-max) {
+      margin-top: 15px;
+    }
+  }
+
+  .plans-v2-license-picker-educational-discount-label {
+    margin-bottom: 0;
+    display: flex;
+    align-items: center;
+    font-weight: normal;
+
+    &.disabled span {
+      color: @ol-blue-gray-2;
+    }
+  }
+
+  input[type='checkbox'].plans-v2-license-picker-educational-discount-checkbox {
+    margin-top: 0;
+    margin-right: @margin-sm;
+
+    @media (max-width: @screen-xs-max) {
+      margin-right: 25px;
+      // scale the checkbox to around 30px width and height
+      transform: scale(2.307);
+    }
+  }
+
+  i.plans-v2-license-picker-educational-discount-question-icon {
+    margin-left: @margin-xs;
+
+    @media (max-width: @screen-xs-max) {
+      display: none;
+    }
+  }
+
+  span.plans-v2-license-picker-educational-discount-learn-more-container {
+    display: none;
+
+    @media (max-width: @screen-xs-max) {
+      margin-left: 5px;
+      display: inline;
+      // this white-space is important to ensure the "learn more" text won't get separated by a newline
+      white-space: nowrap;
+
+      span.plans-v2-license-picker-educational-discount-learn-more-text {
+        color: @plans-v2-learn-more-link-color;
+      }
+
+      .tooltip {
+        // force white-space to have initial value since the `white-space: nowrap` rule
+        // on .plans-v2-license-picker-educational-discount-learn-more-container selector (the current code block) above
+        // will also affect every child inside of it, including the generated tooltip
+        white-space: initial;
+      }
+    }
+  }
+
+  .plans-v2-table-individual,
+  .plans-v2-table-student {
+    margin-top: 40px + @plans-v2-highlighted-text-height-desktop;
+
+    @media (max-width: @screen-xs-max) {
+      margin-top: 60px + @plans-v2-highlighted-text-height-desktop;
+
+      th .plans-v2-table-th-content {
+        min-height: 280px;
+      }
+    }
+  }
+
+  .plans-v2-table-student {
+    tr {
+      // "hide" the last column on desktop by making the background-color as the default background color
+      th:last-of-type > .plans-v2-table-th {
+        background-color: @ol-blue-gray-0;
+      }
+
+      &:last-child {
+        td:first-child {
+          background-color: @ol-blue-gray-0;
+
+          .plans-v2-table-feature-name {
+            border-bottom-left-radius: @plans-v2-table-border-radius;
+
+            @media (max-width: @screen-xs-max) {
+              border-bottom-left-radius: 0;
+            }
+          }
+        }
+
+        td:last-child {
+          background-color: @ol-blue-gray-0;
+
+          .plans-v2-table-cell {
+            border-bottom-right-radius: @plans-v2-table-border-radius;
+
+            @media (max-width: @screen-xs-max) {
+              border-bottom-right-radius: 0;
+            }
+          }
+        }
+      }
+    }
+
+    // only make corners have round borders if the column is not highlighted
+    th:not(.plans-v2-table-green-highlighted) {
+      &:nth-last-child(2) .plans-v2-table-th {
+        border-top-right-radius: @plans-v2-table-border-radius;
+
+        @media (max-width: @screen-xs-max) {
+          border-top-right-radius: 0;
+        }
+      }
+
+      &:nth-child(2) .plans-v2-table-th {
+        border-top-left-radius: @plans-v2-table-border-radius;
+
+        @media (max-width: @screen-xs-max) {
+          border-top-right-radius: 0;
+        }
+      }
+    }
+
+    @media (max-width: @screen-md-max) {
+      tr:first-of-type {
+        th:last-of-type {
+          // Last column is only a UI placeholder for desktop view.
+          // We need to hide it for mobile to have the full table fully visible on mobile
+          // without any empty horizontal space.
+          display: none;
+        }
+      }
+    }
+  }
+
+  .plans-v2-table-individual {
+    tr {
+      &:last-child {
+        td:first-child {
+          background-color: @ol-blue-gray-0;
+
+          .plans-v2-table-feature-name {
+            border-bottom-left-radius: @plans-v2-table-border-radius;
+
+            @media (max-width: @screen-xs-max) {
+              border-bottom-left-radius: 0;
+            }
+          }
+        }
+
+        td:last-child {
+          background-color: @ol-blue-gray-0;
+
+          .plans-v2-table-cell {
+            border-bottom-right-radius: @plans-v2-table-border-radius;
+
+            @media (max-width: @screen-xs-max) {
+              border-bottom-right-radius: 0;
+            }
+          }
+        }
+      }
+
+      &:not(.plans-v2-table-divider) td {
+        background-color: @white;
+      }
+    }
+
+    th {
+      &:last-child .plans-v2-table-th {
+        border-top-right-radius: @plans-v2-table-border-radius;
+
+        @media (max-width: @screen-xs-max) {
+          border-top-right-radius: 0;
+        }
+      }
+
+      &:nth-child(2) .plans-v2-table-th {
+        border-top-left-radius: @plans-v2-table-border-radius;
+
+        @media (max-width: @screen-xs-max) {
+          border-top-left-radius: 0;
+        }
+      }
+    }
+  }
+
+  .plans-v2-table-group {
+    margin-top: 26px + @plans-v2-highlighted-text-height-desktop;
+
+    p.plans-v2-table-th-content-title {
+      // Reduce font size of table heading for screen size >= @screen-lg (1200px) from 20px to 19px
+      // In the English version of overleaf, 20px is enough for the text to not be wrapped to the next line, the longest text is the 2nd column of group tab: "Group Professional".
+      // In the English version, "Group Professional" won't be wrapped to two separate lines with 20px font size for big screen.
+      // In other languages, for example, German (subdomain `de`), 20px is too big so the text is wrapped and breaks the UI. 19px is enough to fit the text on one line on the German site.
+      // This font-size reduction is a case-to-case basis since we don't have any way to know the max length of every language unless we've translated them all.
+      // There's probably a smarter way to do it, but probably also more complex. This change in font should be acceptable and we can revisit it every time there's a longer text.
+      font-size: 19px;
+
+      @media (max-width: @screen-md-max) {
+        font-size: @font-size-base;
+      }
+
+      @media (max-width: @screen-xs-max) {
+        font-size: @font-size-small;
+      }
+    }
+
+    tr {
+      // top right border radius on desktop only
+      &:first-of-type {
+        th:last-child > .plans-v2-table-th {
+          border-top-right-radius: @plans-v2-table-border-radius;
+
+          @media (max-width: @screen-xs-max) {
+            border-top-right-radius: 0;
+          }
+        }
+      }
+
+      // bottom right border radius on desktop only
+      &:last-of-type {
+        td:last-child > .plans-v2-table-cell {
+          border-bottom-right-radius: @plans-v2-table-border-radius;
+
+          @media (max-width: @screen-xs-max) {
+            border-bottom-right-radius: 0;
+          }
+        }
+      }
+    }
+  }
+
+  .plans-v2-table {
+    background-color: @ol-blue-gray-0;
+    margin-bottom: 15px;
+    table-layout: fixed;
+    width: 100%;
+    // We want these `div` inside `th` and `td` to have a 100% height since
+    // white backgrounds are defined here:
+    // 1. `td > div.plans-v2-table-cell`
+    // 2. `th > div.plans-v2-table-th`
+    //
+    // To make both of them have 100% height of their respective `td` and `th`,
+    // we need to have the `table` has an explicit `height` rule.
+    // The value can be arbitrary, since it will be ignored by the browser
+    //
+    // See https://stackoverflow.com/a/56913789 for more details
+    height: 100%;
+
+    .plans-v2-table-row-header {
+      font-weight: 500;
+    }
+
+    tr {
+      height: 100%;
+    }
+
+    .plans-v2-table-column-header,
+    td {
+      background-clip: padding-box; /* needed for firefox when there is bg color */
+      text-align: center;
+
+      &:not(.plans-v2-table-cell-before-green-highlighted-column):not(
+          .plans-v2-table-green-highlighted
+        ):not(.plans-v2-table-divider-highlighted) {
+        border-right: 1px solid @ol-blue-gray-0;
+
+        @media (max-width: @screen-xs-max) {
+          border-right: none;
+        }
+      }
+    }
+
+    .plans-v2-table-column-header {
+      border-top: 0;
+      font-family: @headings-font-family;
+      font-size: @font-size-h2;
+      font-weight: @headings-font-weight;
+      line-height: @headings-line-height;
+      vertical-align: top;
+      padding: 0;
+      position: relative;
+      background-color: @ol-blue-gray-0;
+
+      &:first-child {
+        border-left: 0;
+      }
+
+      &:last-child {
+        border-right: 0;
+      }
+    }
+
+    td {
+      vertical-align: middle;
+      height: 100%;
+
+      &:last-child:not(.plans-v2-table-green-highlighted):not(
+          .plans-v2-table-divider-highlighted
+        ) {
+        border-right: 0;
+      }
+    }
+
+    tr {
+      &:first-child th {
+        // keep the position here, otherwise messes up border on safari
+        position: relative;
+      }
+
+      // css hack for table border-radius on the first feature name
+      &:nth-child(2) {
+        .plans-v2-table-row-header:first-child {
+          background-color: @ol-blue-gray-0;
+
+          .plans-v2-table-feature-name {
+            border-top-left-radius: @plans-v2-table-border-radius;
+
+            @media (max-width: @screen-xs-max) {
+              border-top-left-radius: 0;
+            }
+          }
+        }
+      }
+
+      &:not(.plans-v2-table-row-last-row-per-section):not(
+          .plans-v2-table-divider
+        ):not(:last-of-type) {
+        th > .plans-v2-table-th > .plans-v2-table-th-content,
+        td > .plans-v2-table-feature-name,
+        td > .plans-v2-table-cell > .plans-v2-table-cell-content {
+          border-bottom: 1px solid @ol-blue-gray-0;
+
+          @media (max-width: @screen-xs-max) {
+            border-bottom: unset;
+          }
+        }
+      }
+
+      // this will show highlghted border-bottom on the last row
+      &:last-child td.plans-v2-table-green-highlighted {
+        > .plans-v2-table-cell {
+          border-bottom: 2px solid @ol-green;
+
+          @media (max-width: @screen-xs-max) {
+            border-bottom: unset;
+          }
+        }
+      }
+    }
+
+    .fa-check {
+      color: @green;
+    }
+
+    @media (min-width: @screen-sm-min) {
+      // highlight rows on hover
+      tr:not(.plans-v2-table-divider):not(:first-child):hover {
+        background-color: @table-hover-bg;
+
+        .plans-v2-table-feature-name,
+        .plans-v2-table-cell {
+          background-color: @table-hover-bg;
+        }
+      }
+    }
+
+    @media (max-width: @screen-sm-max) {
+      font-size: @font-size-small;
+    }
+
+    @media (max-width: @screen-xs-max) {
+      tr {
+        display: flex;
+        // for mobile, we need to show the feature name (the first column) as its own row
+        // the flex-flow will make the whole tr overflow to a next row if
+        // total td width combined is bigger than viewport width
+        // so, one tr can have multiple "visible" row depending on the total width.
+        flex-flow: row wrap;
+        justify-content: space-around;
+      }
+
+      // The forced width below (plans-v2-table-cols-n) is to ensure the table columns have an equal width
+      // because on mobile, the first column (empty cell on first `tr` and feature name on the rest of `tr`) will be shown as its own row
+      // and the rest of the `tr` will incorporate a full width of the viewport
+      tr.plans-v2-table-cols-4:not(.plans-v2-table-divider) {
+        td,
+        th:not(:first-of-type) {
+          width: 25%;
+        }
+      }
+
+      tr.plans-v2-table-cols-3:not(.plans-v2-table-divider) {
+        td,
+        th:not(:first-of-type) {
+          width: calc(100% / 3);
+        }
+      }
+
+      tr.plans-v2-table-cols-2:not(.plans-v2-table-divider) {
+        td,
+        th:not(:first-of-type) {
+          width: 50%;
+        }
+      }
+
+      .plans-v2-table-column-header {
+        font-size: 12px;
+      }
+
+      // hide the first column header
+      tr:first-child th:first-child {
+        display: none;
+      }
+
+      // for mobile, we need to show the feature name (the first td) as its own row
+      // the width: 100vw rule will enhance the flex-flow on the tr by ensuring the feature name (first column of each feature row)
+      // will have the full width of viewport,
+      // and making the first td appear like the it is in its own row (although it's technically still on the same tr as the other tds)
+      .plans-v2-table-row-header {
+        background: @gray-lightest;
+        width: 100vw;
+
+        span.plans-v2-table-feature-name {
+          width: 100%;
+
+          span {
+            text-align: left;
+          }
+        }
+      }
+    }
+  }
+
+  // plans-v2-table-cell class only appears for the inner `div` of a `td`
+  // that doesn't contain divider or feature name
+  // (i.e it contains either check mark icon or a dash)
+  .plans-v2-table-cell {
+    background-color: @white;
+    height: 100%;
+
+    .plans-v2-table-cell-content {
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    @media (max-width: @screen-xs-max) {
+      .plans-v2-table-cell-content {
+        padding: 6px;
+        min-height: @plans-v2-table-td-mobile-min-height;
+      }
+    }
+  }
+
+  .plans-v2-table-th {
+    height: 100%;
+    background-color: @white;
+
+    .plans-v2-table-th-content {
+      display: flex;
+      flex-direction: column;
+      min-height: 321px;
+      height: 100%;
+      padding: 10px 13px;
+
+      @media (max-width: @screen-md-max) {
+        min-height: 330px;
+      }
+
+      @media (max-width: @screen-sm-max) {
+        padding-left: @padding-xs;
+        padding-right: @padding-xs;
+      }
+    }
+  }
+
+  .plans-v2-table-btn-buy-container-desktop {
+    margin-top: auto;
+    display: flex;
+    flex-direction: column;
+    min-height: 60px;
+
+    @media (max-width: @screen-xs-max) {
+      display: none;
+    }
+  }
+
+  .plans-v2-table-btn-buy-container-mobile {
+    display: none;
+
+    @media (max-width: @screen-xs-max) {
+      display: flex;
+      flex-direction: column;
+      min-height: 50px;
+      margin-top: @padding-sm;
+    }
+  }
+
+  .plans-v2-table-btn-buy {
+    padding: 4px 15px 5px;
+
+    @media (min-width: (@screen-xs-max+1)) {
+      .hidden-desktop {
+        display: none;
+      }
+    }
+
+    @media (max-width: @screen-sm-max) {
+      font-size: @font-size-small;
+    }
+
+    @media (max-width: @screen-xs-max) {
+      padding-left: 9px;
+      padding-right: 9px;
+      // truncating group plans long button text for mobile
+      .hidden-mobile {
+        display: none;
+      }
+    }
+  }
+
+  p.plans-v2-table-th-content-title {
+    font-size: @font-size-large;
+    padding-bottom: @padding-sm;
+    border-bottom: 1px solid @gray-lighter;
+    margin-bottom: @margin-lg;
+
+    @media (max-width: @screen-md-max) {
+      font-size: @font-size-base;
+    }
+
+    // `plans-v2-table-th-content-title` is visually invisible on screen size less than @screen-xs-max
+    // because there is a sticky header (`plans-v2-table-sticky-header` class) that "replace" the content visually
+    // (see the `@plans-v2-table-sticky-header-z-index` variable that make the sticky header element is "above" the whole table)
+    // however, we still need to properly styling the `plans-v2-table-th-content-title` for small screen size to
+    // fit this element to the size of the sticky header
+    // this trick is used because th element can not have a `position: sticky` rule, at least for our use case
+    @media (max-width: @screen-xs-max) {
+      min-height: 40px;
+      margin-bottom: @margin-md;
+      font-size: @font-size-small;
+      padding-bottom: 0;
+    }
+  }
+
+  p.plans-v2-table-th-content-benefit,
+  ul.plans-v2-table-th-content-benefit {
+    padding-top: 15px;
+    font-size: @font-size-small;
+    font-family: @font-family-sans-serif;
+    line-height: 23px;
+    hyphens: auto;
+  }
+
+  ul.plans-v2-table-th-content-benefit {
+    padding-left: 15px;
+    text-align: left;
+    margin-bottom: 0;
+
+    li a {
+      color: @ol-blue-gray-3;
+      text-decoration: underline;
+    }
+  }
+
+  // this class should only appear on the group table
+  .plans-v2-group-total-price {
+    font-weight: 700;
+  }
+
+  .plans-v2-table-price-container {
+    position: relative;
+  }
+
+  p.plans-v2-table-price-period-label {
+    margin: 0;
+    font-size: @font-size-small;
+    line-height: 23px;
+    color: @ol-blue-gray-3;
+  }
+
+  p.plans-v2-table-price {
+    margin: 0;
+
+    span {
+      font-weight: 700;
+      font-size: 32px;
+      line-height: 42px;
+
+      @media (max-width: @screen-xs-max) {
+        line-height: 35px;
+        font-size: @font-size-large;
+      }
+    }
+  }
+
+  strike.plans-v2-table-price-before-discount {
+    position: absolute;
+    top: -27px;
+    font-weight: 700;
+    font-size: @font-size-small;
+    line-height: 42px;
+    display: flex;
+    justify-content: center;
+    width: 100%;
+    color: @ol-blue-gray-2;
+
+    @media (max-width: @screen-xs-max) {
+      font-size: @font-size-extra-small;
+    }
+  }
+
+  small.plans-v2-table-th-content-additional-link {
+    text-transform: lowercase;
+    margin-top: @margin-xs;
+    font-size: @font-size-extra-small;
+    height: 16px;
+    font-family: @font-family-sans-serif;
+
+    a {
+      color: @ol-blue-gray-5;
+      text-decoration: underline;
+    }
+  }
+
+  .plans-v2-table-feature-name {
+    background-color: @white;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 6px 6px 6px 18px;
+
+    i.plans-v2-table-feature-name-question-icon {
+      margin-left: 10px;
+    }
+
+    span.plans-v2-table-feature-name-learn-more-container {
+      display: none;
+    }
+
+    @media (max-width: @screen-xs-max) {
+      display: inline-block;
+      text-align: left;
+      padding: 6px;
+      width: 100%;
+      background-color: @ol-blue-gray-0;
+      min-height: @plans-v2-table-td-mobile-min-height;
+
+      i.plans-v2-table-feature-name-question-icon {
+        display: none;
+      }
+
+      span.plans-v2-table-feature-name-learn-more-container {
+        margin-left: 5px;
+        display: inline;
+        // this white-space is important to ensure the "learn more" text won't get separated by a newline
+        white-space: nowrap;
+
+        span.plans-v2-table-feature-name-learn-more-text {
+          color: @plans-v2-learn-more-link-color;
+        }
+
+        .tooltip {
+          // force white-space to have initial value since the `white-space: nowrap` rule
+          // on .plans-v2-table-feature-name-learn-more-container selector (the current code block) above
+          // will also affect every child inside of it, including the generated tooltip
+          white-space: initial;
+        }
+      }
+    }
+  }
+
+  tr.plans-v2-table-divider {
+    background-color: @ol-blue-gray-1;
+
+    // direct child selector to NOT affect the generated tooltip
+    td > div {
+      text-align: center;
+      padding: 6px;
+
+      .plans-v2-table-divider-label {
+        margin-bottom: 0;
+        margin-right: 5px;
+      }
+
+      .plans-v2-table-divider-learn-more-container {
+        display: none;
+      }
+    }
+
+    // highlighted divider will only show if the highlighted column is on the last visible column
+    .plans-v2-table-divider-highlighted > div {
+      border-right: 2px solid @ol-green;
+    }
+
+    @media (max-width: @screen-xs-max) {
+      // need the colspan attribute selector to increase specificity to override more specific css rule on another selector
+      td[colspan] {
+        padding-top: 0;
+        padding-right: 0;
+        padding-left: 0;
+
+        // direct child selector to NOT affect the generated tooltip
+        > div {
+          background-color: @ol-blue-gray-1;
+          padding: 15px 5px;
+
+          i.plans-v2-table-divider-question-icon {
+            display: none;
+          }
+
+          .plans-v2-table-divider-learn-more-container {
+            display: inline;
+            // this white-space is important to ensure the "learn more" text won't get separated by a newline
+            white-space: nowrap;
+
+            span.plans-v2-table-divider-learn-more-text {
+              color: @plans-v2-learn-more-link-color;
+            }
+
+            // force white-space to have initial value since the `white-space: nowrap` rule
+            // on .plans-v2-table-divider-learn-more-container selector (the current code block) above
+            // will also affect every child inside of it, including the generated tooltip
+            .tooltip {
+              white-space: initial;
+            }
+          }
+        }
+      }
+
+      .plans-v2-table-divider-highlighted > div {
+        border-right: none;
+      }
+    }
+  }
+
+  // the `.plans-v2-table-green-highlighted` class below can be applied to both `th` and `td`
+  .plans-v2-table-green-highlighted {
+    > .plans-v2-table-cell,
+    > .plans-v2-table-th {
+      border-left: 2px solid @ol-green;
+      border-right: 2px solid @ol-green;
+
+      @media (max-width: @screen-xs-max) {
+        border-left: unset;
+        border-right: unset;
+      }
+    }
+  }
+
+  p.plans-v2-table-green-highlighted-text {
+    border: 2px solid @ol-green;
+    position: absolute;
+    top: -1 * @plans-v2-highlighted-text-height-desktop;
+    left: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 100%;
+    font-size: @font-size-small;
+    line-height: 19px;
+    font-weight: 700;
+    background-color: @ol-green;
+    border-radius: @plans-v2-table-border-radius @plans-v2-table-border-radius 0
+      0;
+    color: @white;
+    height: @plans-v2-highlighted-text-height-desktop;
+    margin: 0;
+
+    @media (max-width: @screen-sm-max) {
+      top: -1 * @plans-v2-highlighted-text-height-mobile;
+      height: @plans-v2-highlighted-text-height-mobile;
+    }
+
+    @media (max-width: @screen-xs-max) {
+      padding-left: 5px;
+      padding-right: 5px;
+      font-size: @font-size-extra-small;
+      line-height: 15px;
+    }
+  }
+
+  .plans-v2-table-sticky-header-container {
+    @media (max-width: @screen-xs-max) {
+      // `height: 60%` is just an arbitrary percentage
+      // since we need to cover the whole plans_v2_table
+      // but not too much so it vertically overflows the page
+      // and it's not trivial to calculate the precise table height
+      height: 60%;
+      width: 100%;
+      position: absolute;
+    }
+  }
+
+  .plans-v2-table-sticky-header-without-switch {
+    margin-bottom: -107px;
+    margin-top: 67px;
+  }
+
+  .plans-v2-table-sticky-header-with-switch {
+    margin-bottom: -140px;
+    margin-top: 100px;
+  }
+
+  .plans-v2-table-sticky-header {
+    display: none;
+
+    @media (max-width: @screen-xs-max) {
+      display: flex;
+      width: 100vw;
+      top: 0;
+      left: 0;
+      z-index: @z-index-plans-page-table-header-mobile;
+      height: 40px;
+
+      // separate sticky class because we want to remove the class (to visually remove the sticky header)
+      // with javascript whenever the table is not visible anymore
+      &.sticky {
+        position: sticky;
+      }
+    }
+  }
+
+  .plans-v2-table-sticky-header-item {
+    background-color: @white;
+    flex: 1 1 0px;
+
+    span {
+      margin: 0px @margin-xs;
+      border-bottom: 1px solid #cccccc;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-family: @font-family-serif;
+
+      @media (max-width: @screen-xs-max) {
+        font-size: @font-size-small;
+        text-align: center;
+        line-height: 19px;
+      }
+    }
+  }
+
+  .plans-v2-table-sticky-header-item-green-highlighted span {
+    color: @ol-green;
+  }
+
+  .plans-v2-faq {
+    p {
+      margin: 0;
+    }
+
+    // need for specificity to override default a color
+    p > a {
+      color: @ol-blue;
+
+      &:hover {
+        color: @ol-dark-blue;
+      }
+    }
+  }
+
+  .plans-v2-university-info {
+    padding: @padding-lg @padding-xxl;
+    background-color: @ol-blue-gray-1;
+    border-radius: 20px;
+
+    h3.plans-v2-university-info-header {
+      margin: 0 @margin-xl @margin-md @margin-xl;
+    }
+
+    p.plans-v2-university-info-text {
+      margin: 0 @margin-xl;
+    }
+
+    a.plans-v2-btn-university-info {
+      margin-top: @margin-md;
+    }
+
+    @media (max-width: @screen-sm-max) {
+      h3.plans-v2-university-info-header {
+        margin: 0 0 @margin-md 0;
+      }
+
+      p.plans-v2-university-info-text {
+        margin: 0;
+      }
+    }
+
+    @media (max-width: @screen-xs-max) {
+      padding: @padding-lg;
+    }
+  }
+
+  padding-top: calc(var(--spacing-16) + var(--header-height));
+
+  h1 {
+    margin-top: 0;
+    margin-bottom: var(--spacing-08);
+  }
+  .plans-v2-top-switch {
+    margin-top: var(--spacing-09);
+  }
+
+  .plans-v2-table,
+  .plans-v2-table-column-header {
+    background-color: transparent; // remove colors set by default variant
+  }
+
+  .plans-page-quote-row {
+    margin: var(--spacing-13) 0;
+  }
+}

+ 1 - 0
services/web/frontend/stylesheets/main-style.less

@@ -106,6 +106,7 @@
 @import 'app/editor.less';
 @import 'app/homepage.less';
 @import 'app/plans/plans-v2.less';
+@import 'app/plans/plans-light-touch-redesign.less';
 @import 'app/plans/plans-new-design.less';
 @import 'app/recurly.less';
 @import 'app/bonus.less';