hello: Redesign page.

Fixes #24082
This commit is contained in:
Aman Agrawal 2023-09-28 14:42:56 +00:00 committed by Tim Abbott
parent 7a97ceab86
commit 75a1a74adf
33 changed files with 1712 additions and 604 deletions

3
.gitignore vendored
View File

@ -83,6 +83,9 @@ zulip.kdev4
# Core dump files
core
# Static generated files for landing page.
/static/images/landing-page/hello/generated
## Miscellaneous
# (Ideally this section is empty.)
.transifexrc

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,562 +1,350 @@
{% extends "zerver/portico.html" %}
{% set entrypoint = "landing-page" %}
{% extends "zerver/base.html" %}
{% set entrypoint = "landing-page-hello" %}
{% set PAGE_TITLE = "Zulip: Open-source team chat with topic-based threading" %}
{% set PAGE_TITLE = "Zulip — organized team chat" %}
{% set PAGE_DESCRIPTION = "Zulip is the only modern team chat app that is
designed for both live and asynchronous conversations. Organized to help you
collaborate productively and efficiently." %}
{% set PAGE_DESCRIPTION = "Zulip is the only modern team chat app that is designed for both live and asynchronous conversations. Organized to help you collaborate productively and efficiently." %}
{% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
.portico-page {
padding-bottom: 0px;
}
</style>
{% endblock %}
{% block portico_content %}
{% include 'zerver/landing_nav.html' %}
<div class="gradients">
<div class="gradient sunburst"></div>
<div class="gradient dark-blue"></div>
<div class="gradient green"></div>
<div class="gradient blue"></div>
<div class="gradient white-fade"></div>
</div>
<div class="portico-landing hello show">
<div class="hero">
<div class="content">
<header>
<h1>Chat for distributed teams.</h1>
{% block content %}
{% include 'zerver/landing_nav.html' %}
<div class="portico-hello-page">
<div class='body-bg'>
<div class='body-bg__layer'></div>
</div>
<div class="screen-1">
<h1>Organized team&nbsp;chat</h1>
<div class='h1-subheader'>
The calmer, more efficient way to work
</div>
<div class='appshot-1'>
<picture>
<!-- dark theme is only with webp images, since webp support was at the same time or earlier than prefers-color-scheme https://caniuse.com/?search=prefers-color-scheme https://caniuse.com/webp -->
<source class='appshot-1__img'
type="image/webp"
srcset="{{ static('images/landing-page/hello/generated/screen-1-dark-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-1-dark-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-1-dark-3x.webp') }} 3x"
media="(min-width: 941px) and (prefers-color-scheme: dark)"/>
<source class='appshot-1__img'
type="image/webp"
srcset="{{ static('images/landing-page/hello/generated/screen-1-mobile-dark-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-1-mobile-dark-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-1-mobile-dark-3x.webp') }} 3x"
media="(max-width: 940px) and (prefers-color-scheme: dark)"/>
<source class='appshot-1__img'
type="image/webp"
srcset="{{ static('images/landing-page/hello/generated/screen-1-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-1-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-1-3x.webp') }} 3x"
media="(min-width: 941px)"/>
<source class='appshot-1__img'
type="image/webp"
srcset="{{ static('images/landing-page/hello/generated/screen-1-mobile-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-1-mobile-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-1-mobile-3x.webp') }} 3x"
media="(max-width: 940px)"/>
<source class='appshot-1__img'
srcset="{{ static('images/landing-page/hello/generated/screen-1-mobile-1x.jpg') }},
{{ static('images/landing-page/hello/generated/screen-1-mobile-2x.jpg') }} 2x,
{{ static('images/landing-page/hello/generated/screen-1-mobile-3x.jpg') }} 3x"
media="(max-width: 940px)"/>
<img alt="" class='appshot-1__img' src="{{ static('images/landing-page/hello/generated/screen-1-1x.jpg') }}"
srcset="{{ static('images/landing-page/hello/generated/screen-1-1x.jpg') }},
{{ static('images/landing-page/hello/generated/screen-1-2x.jpg') }} 2x,
{{ static('images/landing-page/hello/generated/screen-1-3x.jpg') }} 3x"/>
</picture>
<div class="cta-buttons">
<a href="/try-zulip/">
<i class="background-activity-icon"></i>
Try Zulip now
</a>
<a href="/communities/">
<i class="visibility-icon"></i>
See it in use
</a>
<a href="/new/">
<i class="add-box-icon"></i>
Create organization
</a>
</div>
</div>
<div class="client-logos">
<div class='client-logos__logo_akamai'></div>
<div class='client-logos__logo_tum'></div>
<div class='client-logos__logo_wikimedia'></div>
<div class='client-logos__logo_rust'></div>
<div class='client-logos__logo_dr_on_demand'></div>
<div class='client-logos__logo_maria'></div>
</div>
</div>
<div class="screen-2">
<div class="screen-2__container">
<div class="screen-2__content">
<h2 class="screen-2__header">Efficient communication</h2>
<div class="screen-2__desc">
Unlike other chat apps, Zulip lets you read and respond to&nbsp;every message <em>in&nbsp;context</em>, no&nbsp;matter when it&nbsp;was sent. Maintain your <em>focus</em> and catch up&nbsp;on&nbsp;your own time <em>without stress</em>, reading just the&nbsp;topics <em>you care about</em>.
</div>
<div class="quote">
<div class="quote__text">
Zulip&rsquo;s <strong>threading model</strong> makes it so&nbsp;much easier to <strong>manage my&nbsp;team</strong>... As&nbsp;a&nbsp;leader, <strong>in&nbsp;just a&nbsp;few minutes</strong> I&nbsp;can get an&nbsp;overview over what&rsquo;s going on and see where my&nbsp;attention is&nbsp;needed.
<div class="quote__text-tail"></div>
</div>
<div class="quote__source">
<a href="/case-studies/idrift/">Case study with
<strong>Gaute Lund</strong></a>,
<i>co-founder of iDrift AS</i>
</div>
</div>
</div>
<div class="appshot-2">
<picture>
<!-- dark theme is only with webp images, since webp support was at the same time or earlier than prefers-color-scheme https://caniuse.com/?search=prefers-color-scheme https://caniuse.com/webp -->
<source class='appshot-2__img'
type="image/webp"
srcset="{{ static('images/landing-page/hello/generated/screen-2-dark-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-2-dark-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-2-dark-3x.webp') }} 3x"
media="(min-width: 941px) and (prefers-color-scheme: dark)"/>
<source class='appshot-2__img'
type="image/webp"
srcset="{{ static('images/landing-page/hello/generated/screen-2-mobile-dark-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-2-mobile-dark-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-2-mobile-dark-3x.webp') }} 3x"
media="(max-width: 940px) and (prefers-color-scheme: dark)"/>
<source class='appshot-2__img'
type="image/webp"
srcset="{{ static('images/landing-page/hello/generated/screen-2-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-2-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-2-3x.webp') }} 3x"
media="(min-width: 941px)"/>
<source class='appshot-2__img'
type="image/webp"
srcset="{{ static('images/landing-page/hello/generated/screen-2-mobile-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-2-mobile-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-2-mobile-3x.webp') }} 3x"
media="(max-width: 940px)"/>
<source class='appshot-2__img'
srcset="{{ static('images/landing-page/hello/generated/screen-2-mobile-1x.jpg') }},
{{ static('images/landing-page/hello/generated/screen-2-mobile-2x.jpg') }} 2x,
{{ static('images/landing-page/hello/generated/screen-2-mobile-3x.jpg') }} 3x"
media="(max-width: 940px)"/>
<img alt="" class='appshot-2__img'
src="{{ static('images/landing-page/hello/generated/screen-2-1x.jpg') }}"
srcset="{{ static('images/landing-page/hello/generated/screen-2-1x.jpg') }},
{{ static('images/landing-page/hello/generated/screen-2-2x.jpg') }} 2x,
{{ static('images/landing-page/hello/generated/screen-2-3x.jpg') }} 3x"/>
</picture>
</div>
</div>
</div>
<div class="screen-3">
<div class="screen-3__container">
<div class="screen-3__content">
<h2 class="screen-3__header">Collaboration at scale</h2>
<div class="screen-3__desc">
Zulip helps teams of&nbsp;all sizes be <em>more productive</em> together, from a&nbsp;few friends hacking on&nbsp;a&nbsp;new idea, to&nbsp;<em>globally distributed</em> organizations with hundreds of&nbsp;people tackling the world&rsquo;s hardest problems.
</div>
<div class="quote">
<div class="quote__text">
Zulip has the <strong>best user experience</strong> of&nbsp;all the chat apps I&rsquo;ve tried... It&nbsp;is&nbsp;the only app that makes hundreds of&nbsp;conversations <strong>manageable</strong>.
<div class="quote__text-tail"></div>
</div>
<div class="quote__source">
<a href="/case-studies/tum/">Case study with
<strong>Tobias Lasser</strong></a>,
<i>Technical University of
Munich</i>
</div>
</div>
</div>
<div class="appshot-3">
<picture>
<!-- dark theme -->
<source class='appshot-3-1__img'
type="image/webp"
media='(prefers-color-scheme: dark)'
srcset="{{ static('images/landing-page/hello/generated/screen-3-1-dark-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-3-1-dark-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-3-1-dark-3x.webp') }} 3x"/>
<source class='appshot-3-1__img'
type="image/webp"
srcset="{{ static('images/landing-page/hello/generated/screen-3-1-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-3-1-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-3-1-3x.webp') }} 3x"/>
<img alt="" class='appshot-3-1__img'
src="{{ static('images/landing-page/hello/generated/screen-3-1-1x.jpg') }}"
srcset="{{ static('images/landing-page/hello/generated/screen-3-1-1x.jpg') }},
{{ static('images/landing-page/hello/generated/screen-3-1-2x.jpg') }} 2x,
{{ static('images/landing-page/hello/generated/screen-3-1-3x.jpg') }} 3x"/>
</picture>
<picture>
<!-- dark theme -->
<source class='appshot-3-2__img'
type="image/webp"
media='(prefers-color-scheme: dark)'
srcset="{{ static('images/landing-page/hello/generated/screen-3-2-dark-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-3-2-dark-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-3-2-dark-3x.webp') }} 3x"/>
<source class='appshot-3-2__img'
type="image/webp"
srcset="{{ static('images/landing-page/hello/generated/screen-3-2-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-3-2-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-3-2-3x.webp') }} 3x"/>
<img alt="" class='appshot-3-2__img'
src="{{ static('images/landing-page/hello/generated/screen-3-2-1x.jpg') }}"
srcset="{{ static('images/landing-page/hello/generated/screen-3-2-1x.jpg') }},
{{ static('images/landing-page/hello/generated/screen-3-2-2x.jpg') }} 2x,
{{ static('images/landing-page/hello/generated/screen-3-2-3x.jpg') }} 3x"/>
</picture>
</div>
</div>
</div>
<div class="screen-4">
<div class="screen-4__container">
<div class="screen-4__content">
<h2 class="screen-4__header">Enterprise ready</h2>
<div class="screen-4__desc">
<p>
Zulip combines the immediacy of real-time chat with an email
threading model. <br class="line-break-desktop" />With Zulip, you can catch
up on important conversations while ignoring
irrelevant ones.
</p>
</header>
<div class="tour">
<div id="tour-carousel" class="carousel slide carousel-fade">
<!-- Carousel items -->
<div class="carousel-inner">
<div class="item-container">
<div class="item active">
<div class="item-inner">
<button data-target="#tour-carousel" data-slide="next" type="button" name="button" class="start-button">Take the tour</button>
<img src="{{ static('images/story-tutorial/zulip-topic-blurred.png') }}" alt="" class="start-image" />
</div>
</div>
<div class="item">
<div class="item-inner">
<p class="tour-item-header">In Zulip, you subscribe to <b>streams.</b> Streams are like channels in Slack or IRC.</p>
<div class="zulip-slack-comparison">
<div class="comparison-zulip">
<div class="caption">Zulip</div>
<img src="{{ static('images/story-tutorial/zulip-streams.png') }}" class="zulip-streams" alt="{{ _('Streams in Zulip') }}" />
</div>
<div class="comparison-slack">
<div class="caption">Other team chat</div>
<img src="{{ static('images/story-tutorial/slack-streams.png') }}" class="slack-streams" alt="{{ _('Streams in Slack') }}" />
</div>
</div>
</div>
</div>
<div class="item">
<div class="item-inner">
<p class="tour-item-header">Each stream message also has a <b>topic.</b> Topics are unique to Zulip.</p>
<div class="zulip-slack-comparison">
<div class="comparison-zulip">
<div class="caption">Zulip</div>
<img src="{{ static('images/story-tutorial/zulip-streams-selected.png') }}" class="zulip-streams-selected" alt="{{ _('Topics in Zulip') }}" />
</div>
<div class="comparison-slack">
<div class="caption">Other team chat</div>
<img src="{{ static('images/story-tutorial/slack-streams-selected.png') }}" class="slack-streams-selected" alt="{{ _('Streams in Slack') }}" />
</div>
</div>
</div>
</div>
<div class="item">
<div class="item-inner">
<p class="tour-item-header">Topics make it easy to catch up after a day of meetings.</p>
<div class="zulip-slack-comparison">
<div class="comparison-zulip">
<div class="caption comparison-2-caption-zulip">Zulip</div>
<img src="{{ static('images/story-tutorial/zulip-streams-unreads-arrows.png') }}" alt="{{ _('Stream topics in Zulip') }}" class="zulip-unreads-arrows" />
</div>
<div class="comparison-slack">
<div class="caption comparison-2-caption-slack">Other team chat</div>
<img src="{{ static('images/story-tutorial/slack-streams-unreads.png') }}" class="slack-stream-unreads" alt="{{ _('Streams in Slack') }}" />
</div>
</div>
</div>
</div>
<div class="item">
<div class="item-inner">
<p class="tour-item-header">Let&rsquo;s click on &ldquo;Tuesday night catering.&rdquo;</p>
<img src="{{ static('images/story-tutorial/zulip-topic.png') }}" alt="{{ _('The Tuesday night catering topic in Zulip') }}" class="zulip-topic mobile-hide" />
<img src="{{ static('images/story-tutorial/zulip-topic-crop.png') }}" alt="{{ _('The Tuesday night catering topic in Zulip') }}" class="centered-image zulip-topic mobile-show" />
</div>
</div>
<div class="item">
<div class="item-inner">
<p class="tour-item-header">Messages in Zulip retain their context even if they&rsquo;re sent hours after the conversation started:</p>
<img src="{{ static('images/story-tutorial/zulip-streams-easy.png') }}" alt="{{ _('The Tuesday night catering topic in Zulip') }}" class="zulip-easy mobile-hide" />
<div class="mobile-show">
<img src="{{ static('images/story-tutorial/zulip-streams-easy-mobile-top.png') }}" class="centered-image" alt="{{ _('The Tuesday night catering topic in Zulip') }}" />
<p class="tour-explanation">Messages sent hours apart are linked in the same topic.</p>
<img src="{{ static('images/story-tutorial/zulip-streams-easy-mobile-bottom.png') }}" class="centered-image" alt="{{ _('The Tuesday night catering topic in Zulip - compose box') }}" />
</div>
</div>
</div>
<div class="item">
<div class="item-inner">
<p class="tour-item-header">Without topics, it&rsquo;s hard to catch up efficiently, and hard to participate in conversations that started while you were away.</p>
<img src="{{ static('images/story-tutorial/slack-overwhelming.png') }}" alt="{{ _('The Tuesday night catering topic in Slack') }}" class="slack-overwhelming mobile-hide" />
<div class="mobile-show">
<img src="{{ static('images/story-tutorial/slack-overwhelming-mobile.png') }}" class="centered-image" alt="{{ _('The Tuesday night catering topic in Slack') }}" />
<p class="tour-explanation">The last message about Tuesday night catering is hidden 56 messages ago. Meanwhile, you just see a mix of unrelated messages.</p>
</div>
</div>
</div>
<div class="item">
<div class="item-inner">
<p class="tour-item-header tour-item-header-top-push tour-item-header-centered">Zulip Free is free for an unlimited number of users.</p>
<a href="/plans/" class="call-to-action">
{{ _('See plans and pricing') }}
</a>
<div class="other-resources">
<div class="other-resources-section">
<a href="/why-zulip/"><img src="{{ static('images/landing-page/hello/organised.svg') }}" alt="" /></a>
<p><a href="/why-zulip/">Zulip vs Slack &rarr;</a></p>
</div>
<div class="other-resources-section">
<a href="/features/"><img src="{{ static('images/landing-page/hello/featured.svg') }}" alt="" /></a>
<p><a href="/features/">See all features &rarr;</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="carousel-control left visibility-control hide"
href="#tour-carousel" data-slide="prev"
aria-label="{{ _('Previous') }}" title="{{ _('Previous') }}">
<i class="fa fa-chevron-left"></i>
</a>
<a class="carousel-control right visibility-control"
href="#tour-carousel" data-slide="next"
aria-label="{{ _('Next') }}" title="{{ _('Next') }}">
<i class="fa fa-chevron-right"></i>
</a>
<ol class="carousel-indicators">
<li data-target="#tour-carousel" data-slide-to="0" class="active"></li>
<li data-target="#tour-carousel" data-slide-to="1"></li>
<li data-target="#tour-carousel" data-slide-to="2"></li>
<li data-target="#tour-carousel" data-slide-to="3"></li>
<li data-target="#tour-carousel" data-slide-to="4"></li>
<li data-target="#tour-carousel" data-slide-to="5"></li>
<li data-target="#tour-carousel" data-slide-to="6"></li>
<li data-target="#tour-carousel" data-slide-to="7"></li>
</ol>
</div>
</div>
</div>
</div>
<div class="apps">
<div class="triangle"></div>
<div class="left-side">
<div class="content">
<header>
<h1>Apps for every platform.</h1>
</header>
<p>
Zulip has modern apps for every major platform,
powered by Electron and React Native.
</p>
<br />
</div>
<div class="platform-icons">
<div class="group">
<h2>Web</h2>
<a href="{{ apps_page_web }}">
<i class="fa fa-desktop platform-icon" aria-hidden="true"></i>
</a>
</div>
<div class="group">
<h2>Desktop</h2>
<a href="{{ apps_page_url }}mac">
<i class="fa fa-apple platform-icon" aria-hidden="true"></i>
</a>
<a href="{{ apps_page_url }}windows">
<i class="fa fa-windows platform-icon" aria-hidden="true"></i>
</a>
<a href="{{ apps_page_url }}linux">
<i class="fa fa-linux platform-icon" aria-hidden="true"></i>
</a>
</div>
<div class="group">
<h2>Mobile</h2>
<a href="{{ apps_page_url }}ios">
<i class="fa fa-mobile-phone platform-icon" aria-hidden="true"></i>
</a>
<a href="{{ apps_page_url }}android">
<i class="fa fa-android platform-icon" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<div class="right-side">
<div class="screen ios">
<div class="main-page">
<div class="navbar"></div>
<div class="center-page">
<div class="message-feed">
<div class="stream">
<div class="line micro red"></div>
<div class="line nano"></div>
<div class="message">
<div class="top">
<div class="avatar"></div>
<div class="line top-line"></div>
</div>
<div class="content">
<div class="line"></div>
<div class="line"></div>
<div class="line med"></div>
</div>
</div>
<div class="message">
<div class="top">
<div class="avatar"></div>
<div class="line top-line"></div>
</div>
<div class="content">
<div class="line med"></div>
<div class="line"></div>
<div class="line small"></div>
</div>
</div>
</div>
<div class="stream">
<div class="line micro blue"></div>
<div class="line nano"></div>
<div class="message">
<div class="top">
<div class="avatar"></div>
<div class="line top-line"></div>
</div>
<div class="content">
<div class="line med"></div>
<div class="line med"></div>
<div class="line small"></div>
</div>
</div>
<div class="message">
<div class="top">
<div class="avatar"></div>
<div class="line top-line"></div>
</div>
<div class="content">
<div class="line"></div>
<div class="line"></div>
<div class="line small"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="screen android">
<div class="main-page">
<div class="navbar"></div>
<div class="center-page">
<div class="message-feed">
<div class="stream">
<div class="line micro red"></div>
<div class="line nano"></div>
<div class="message">
<div class="top">
<div class="avatar"></div>
<div class="line top-line"></div>
</div>
<div class="content">
<div class="line"></div>
<div class="line"></div>
<div class="line med"></div>
</div>
</div>
<div class="message">
<div class="top">
<div class="avatar"></div>
<div class="line top-line"></div>
</div>
<div class="content">
<div class="line med"></div>
<div class="line"></div>
<div class="line small"></div>
</div>
</div>
</div>
<div class="stream">
<div class="line micro blue"></div>
<div class="line nano"></div>
<div class="message">
<div class="top">
<div class="avatar"></div>
<div class="line top-line"></div>
</div>
<div class="content">
<div class="line med"></div>
<div class="line med"></div>
<div class="line small"></div>
</div>
</div>
<div class="message">
<div class="top">
<div class="avatar"></div>
<div class="line top-line"></div>
</div>
<div class="content">
<div class="line"></div>
<div class="line"></div>
<div class="line small"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="open-source">
<div class="flex">
<img src="{{ static('images/landing-page/hello/opensource.svg') }}" alt=""/>
<div class="il-block">
<h1>Open source.</h1>
<p>
Zulip is <a href="https://github.com/zulip/zulip">100% open source software</a>,
built by a vibrant community of over 1000 developers
from all around the world. With 160,000 words of
<a href="https://zulip.readthedocs.io/">developer documentation</a>,
a high quality code base, and a
<a href="/development-community/">welcoming community</a>,
it&rsquo;s easy to extend or tweak Zulip.
Take charge of&nbsp;your mission-critical communications with Zulip&rsquo;s reliable <a href="/for/open-source/">100% free and open-source software</a>, with no&nbsp;vendor lock-in. You can count on&nbsp;our industry-leading <a href="/security/">security practices</a> to&nbsp;keep your data safe.
</p>
<p>
Zulip has a significantly
<a href="https://github.com/zulip/zulip/graphs/contributors">larger and more active</a>
development community than other modern
open source group chat solutions like
<a href="https://github.com/mattermost/mattermost-server/graphs/contributors">Mattermost</a>,
<a href="https://github.com/RocketChat/Rocket.Chat/graphs/contributors">Rocket.Chat</a>,
and <a href="https://github.com/matrix-org/synapse/graphs/contributors">matrix.org</a>.
</p>
<p>
Learn about <a href="/self-hosting/">self-hosting Zulip</a>
or <a href="{{ latest_release_announcement }}">read the Zulip {{ latest_major_version }} release announcement</a>.
When you <a href="/self-hosting/">self-host Zulip</a>, you get all the features of&nbsp;our cloud offering, plus ultimate control and compliance.
</p>
</div>
<div class="screen-4__tags">
<a href="https://zulip.readthedocs.io/en/stable/production/authentication-methods.html">SAML authentication</a>
<a href="https://zulip.readthedocs.io/en/stable/production/authentication-methods.html#synchronizing-data">LDAP sync</a>
<a href="/help/export-your-organization">Import & export tools</a>
<a href="/help/roles-and-permissions">Fine-grained permissions</a>
<a href="https://zulip.readthedocs.io/en/stable/production/settings.html">Extensive configuration</a>
<a href="/integrations/">100s of integrations</a>
<a href="/integrations/doc/slack_incoming">Slack-compatible webhooks</a>
<a href="/help/gdpr-compliance">GDPR compliance</a>
<a href="/help/message-retention-policy">Retention policies</a>
</div>
</div>
<div class="integrations">
<div class="content">
<header>
<h1>Seamless integrations with everything you use.</h1>
</header>
<p>
Zulip has more than 90 native integrations. Several hundred more
are available through
<a href="/integrations/doc/zapier">Zapier</a>
and
<a href="/integrations/doc/ifttt">IFTTT</a>.
</p>
<p><a href="/integrations/">See all available integrations.</a></p>
<div class="appshot-4">
<picture>
<!-- dark theme -->
<source class='appshot-4__img'
type="image/webp"
media='(prefers-color-scheme: dark)'
srcset="{{ static('images/landing-page/hello/generated/screen-4-dark-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-4-dark-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-4-dark-3x.webp') }} 3x"/>
<source class='appshot-4__img'
type="image/webp"
srcset="{{ static('images/landing-page/hello/generated/screen-4-1x.webp') }},
{{ static('images/landing-page/hello/generated/screen-4-2x.webp') }} 2x,
{{ static('images/landing-page/hello/generated/screen-4-3x.webp') }} 3x"/>
<img alt="" class='appshot-4__img'
src="{{ static('images/landing-page/hello/generated/screen-4-1x.jpg') }}"
srcset="{{ static('images/landing-page/hello/generated/screen-4-1x.jpg') }},
{{ static('images/landing-page/hello/generated/screen-4-2x.jpg') }} 2x,
{{ static('images/landing-page/hello/generated/screen-4-3x.jpg') }} 3x"/>
</picture>
</div>
<div class="integration-icons">
<a href="/integrations/doc/travis">
<div class="group">
<img class="integration-logo" src="{{ static('images/integrations/logos/travis.svg') }}" alt="{{ _('Travis logo') }}" />
<h3 class="integration-name">Travis CI</h3>
<p class="integration-description">See build results immediately</p>
</div>
</a>
<a href="/integrations/doc/github">
<div class="group">
<img class="integration-logo" src="{{ static('images/integrations/logos/github.svg') }}" alt="{{ _('GitHub logo') }}" />
<h3 class="integration-name">GitHub</h3>
<p class="integration-description">Track issues and pull requests</p>
</div>
</a>
<a href="/integrations/doc/heroku">
<div class="group">
<img class="integration-logo" src="{{ static('images/integrations/logos/heroku.svg') }}" alt="{{ _('Heroku logo') }}" />
<h3 class="integration-name">Heroku</h3>
<p class="integration-description">Keep up with deployments</p>
<div class="screen-5">
<div class="screen-5__container">
<h2 class="screen-5__header">
Learn how Zulip can help your organization!
</h2>
<div class='screen-5__quotes'>
<div class="quote">
<div class="quote__text">
<div class="quote__text-industry">
<a href="/for/business/">Business</a>
<span class="quote__text-industry-icon quote-business-icon"></span>
</div>
</a>
<a href="/integrations/doc/zendesk">
<div class="group">
<img class="integration-logo" src="{{ static('images/integrations/logos/zendesk.svg') }}" alt="{{ _('Zendesk logo') }}" />
<h3 class="integration-name">Zendesk</h3>
<p class="integration-description">Receive support tickets and updates</p>
Zulip is everything Slack is, but it's <strong>smarter</strong> and more powerful... There's even support for getting messages from Slack and IRC
<div class="quote__text-tail"></div>
</div>
</a>
<a href="/integrations/doc/jira">
<div class="group">
<img class="integration-logo" src="{{ static('images/integrations/logos/jira.svg') }}" alt="{{ _('Jira logo') }}" />
<h3 class="integration-name">Jira</h3>
<p class="integration-description">Monitor project bugs and issues</p>
<div class="quote__source">
<a href="https://www.theregister.com/2021/07/28/zulip_open_source_chat_collaboration_software/">Review in <strong>The Register</strong></a>
</div>
</a>
<a href="/integrations/doc/sentry">
<div class="group">
<img class="integration-logo" src="{{ static('images/integrations/logos/sentry.svg') }}" alt="{{ _('Sentry logo') }}" />
<h3 class="integration-name">Sentry</h3>
<p class="integration-description">See real-time error tracking</p>
</div>
</a>
<a href="/integrations/doc/pagerduty" class="hide-1">
<div class="group">
<img class="integration-logo" src="{{ static('images/integrations/logos/pagerduty.svg') }}" alt="{{ _('Pagerduty logo') }}" />
<h3 class="integration-name">Pagerduty</h3>
<p class="integration-description">Connect to your monitoring systems</p>
<div class="quote">
<div class="quote__text">
<div class="quote__text-industry">
<a href="/for/open-source/">Open source</a>
<span class="quote__text-industry-icon quote-open-source-icon"></span>
</div>
Rust development would not be <strong>moving at the pace</strong> that it has been without Zulip...
<div class="quote__text-tail"></div>
</div>
<div class="quote__source">
<a href="https://www.rust-lang.org/governance/teams/lang"><strong>Josh Triplett</strong></a>,<i class="quote__source-title">Rust Language team co-lead</i>
</div>
</div>
<div class="quote">
<div class="quote__text">
<div class="quote__text-industry">
<a href="/for/education/">Education</a>
<span class="quote__text-industry-icon quote-education-icon"></span>
</div>
Zulips topics have made it much easier for me to keep things <strong>coherent</strong>...
<div class="quote__text-tail">
</div>
</div>
<div class="quote__source">
<a href="/case-studies/ucsd/"><strong>Kiran S. Kedlaya</strong></a>,<i class="quote__source-title">Professor of Mathematics at UCSD</i>
</div>
</div>
<div class="quote">
<div class="quote__text">
<div class="quote__text-industry">
<a href="/for/research/">Research</a>
<span class="quote__text-industry-icon quote-research-icon"></span>
</div>
The excellent LaTeX rendering and clever threading make it far <strong>superior to email</strong> and Slack
<div class="quote__text-tail">
</div>
</div>
<div class="quote__source">
<a href="https://twitter.com/TomGur/status/1294322062274842624"><strong>Tom Gur</strong></a>,<i class="quote__source-title">Associate Professor at Warwick</i>
</div>
</div>
<div class="quote">
<div class="quote__text">
<div class="quote__text-industry">
<a href="/for/events/">Events</a>
<span class="quote__text-industry-icon quote-events-icon"></span>
</div>
With Zulip, questions or ideas were <strong>spread openly</strong>...
<div class="quote__text-tail"></div>
</div>
<div class="quote__source">
<a href="https://perso.univ-rennes1.fr/christophe.ritzenthaler/"><strong>Christophe Ritzenthaler</strong></a>,<i class="quote__source-title">Executive Director of CIMPA</i>
</div>
</div>
<div class="quote">
<div class="quote__text">
<div class="quote__text-industry">
<a href="/for/communities/">Communities</a>
<span class="quote__text-industry-icon quote-communities-icon"></span>
</div>
No other tool has a user experience that <strong>scales</strong> to a community of our size...
<div class="quote__text-tail">
</div>
</div>
<div class="quote__source">
<a href="/case-studies/recurse-center/"><strong>Nick Bergson-Shilcock</strong></a>,<i class="quote__source-title">CEO of Recurse Center</i>
</div>
</div>
</div>
<div class="screen-5__badges">
<a class="screen-5__badge-capterra" href="https://www.capterra.com/p/197945/Zulip/">
<img alt="" src="{{ static('images/landing-page/hello/capterra-2023.png') }}"/>
</a>
</div>
<div class="content">
<p>Or build your own integrations with <a href="/api/integrations-overview">Zulip&rsquo;s powerful API</a>.</p>
</div>
</div>
<div class="testimonials">
<div class="padded-content">
<div id="quote-carousel" class="carousel slide" data-ride="carousel">
<div class="carousel-quotes">
<div class="carousel-inner">
<div class="item active quote-container">
<blockquote>
Zulip&rsquo;s unique threading saves me well
over an hour a day in working with our
distributed team of engineers and PMs
across 7+ time zones. We tried Slack,
Mattermost, and other team chat
products that claim to support
threading, and nothing handles
synchronous and asynchronous
communication so intuitively.
</blockquote>
<cite>Jacinda Shelly, CTO, Doctor on Demand</cite>
</div>
<div class="item quote-container">
<blockquote>
Akamais Zulip Enterprise deployment
connects over 2000 users around the planet.
The threading model of conversations
provides a large number of participants the
ability to engage in real, ongoing, and
substantive discussions, without the
overwhelming experience of many other chat
systems. This coordination across far-flung
teams has had a significant, positive impact
on the happiness and productivity of our
personnel, regardless of location or
seniority.
</blockquote>
<cite>Andy Ellis, Chief Security Officer, Akamai</cite>
</div>
<div class="item quote-container">
<blockquote>
Choosing Zulip over Slack as our group chat is one
of the best decisions weve ever made. Zulip makes
it easy for our community of 1000 Recursers around
the world to stay involved, even years after their
batches finish. No other tool has a user
experience that scales to a community of our
size.
</blockquote>
<cite>Nick Bergson-Shilcock, founder and CEO, Recurse Center</cite>
</div>
</div>
</div>
<div class="left visibility-control hide">
<a class="fa fa-chevron-left" aria-hidden="true" href="#quote-carousel" data-slide="prev"></a>
</div>
<div class="right visibility-control">
<a class="fa fa-chevron-right" aria-hidden="true" href="#quote-carousel" data-slide="next"></a>
</div>
</div>
<hr />
<div class="company-container">
<header>
<h2 class="float left">Trusted by thousands of teams.</h2>
<div class="float clear"></div>
</header>
<div class="company-box">
<div class="company akamai"></div>
<div class="company wikimedia-outreach"></div>
<div class="company doctorondemand"></div>
<div class="company mariadb"></div>
<div class="company levelup"></div>
<div class="company recurse"></div>
<div class="company cmt"></div>
<div class="company layershift"></div>
<div class="company panjiva"></div>
</div>
</div>
</div>
</div>
<div class="padded-content call-to-action-bottom">
<h1>Learn how Zulip can help your organization!</h1>
<div class="register-buttons">
<a href="/for/business/" class="register-now button">Business</a>
<a href="/for/open-source/" class="register-now button">Open source</a>
<a href="/for/education/" class="register-now button">Education</a>
<a href="/for/events/" class="register-now button">Events and Conferences</a>
<a href="/for/research/" class="register-now button">Research</a>
<a href="/for/communities/" class="register-now button">Communities</a>
</div>
{% if register_link_disabled %}
{% elif only_sso %}
<a href="{{ url('login-sso') }}" class="styled-button button green">
{{ _('Log in now') }}
</a>
{% else %}
<a href="/plans/" class="styled-button button green">
{{ _('See plans and pricing') }}
</a>
{% endif %}
<div class="zulip-octopus"></div>
</div>
</div>
{% include 'zerver/footer.html' %}
{% endblock %}

View File

@ -65,6 +65,12 @@ def build_timezones_data_paths() -> List[str]:
return paths
def build_landing_page_images_paths() -> List[str]:
paths = ["tools/setup/generate_landing_page_images.py"]
paths += glob.glob("static/images/landing-page/hello/original/*")
return paths
def compilemessages_paths() -> List[str]:
paths = ["zerver/management/commands/compilemessages.py"]
paths += glob.glob("locale/*/LC_MESSAGES/*.po")
@ -159,6 +165,16 @@ def need_to_run_build_timezone_data() -> bool:
)
def need_to_regenerate_landing_page_images() -> bool:
if not os.path.exists("static/images/landing-page/hello/generated"):
return True
return is_digest_obsolete(
"landing_page_images_hash",
build_landing_page_images_paths(),
)
def need_to_run_compilemessages() -> bool:
if not os.path.exists("locale/language_name_map.json"):
# User may have cleaned their Git checkout.
@ -227,6 +243,13 @@ def main(options: argparse.Namespace) -> int:
else:
print("No need to run `tools/setup/build_timezone_values`.")
if options.is_force or need_to_regenerate_landing_page_images():
run(["tools/setup/generate_landing_page_images.py"])
write_new_digest(
"landing_page_images_hash",
build_landing_page_images_paths(),
)
if not options.is_build_release_tarball_only:
# The following block is skipped when we just need the development
# environment to build a release tarball.

View File

@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""Generates versions of landing page images to be served in different conditions."""
import glob
import os
import sys
from pathlib import Path
from typing import Tuple
from PIL import Image
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
if ZULIP_PATH not in sys.path:
sys.path.append(ZULIP_PATH)
LANDING_IMAGES_DIR = os.path.join(ZULIP_PATH, "static", "images", "landing-page", "hello")
ORIGINAL_IMAGES_DIR = os.path.join(LANDING_IMAGES_DIR, "original")
GENERATED_IMAGES_DIR = os.path.join(LANDING_IMAGES_DIR, "generated")
def get_x_size(size: Tuple[float, float], x: int) -> Tuple[int, int]:
return int(x / 3 * size[0]), int(x / 3 * size[1])
def generate_landing_page_images() -> None:
if not os.path.exists(GENERATED_IMAGES_DIR):
os.mkdir(GENERATED_IMAGES_DIR)
for image_file_path in glob.glob(f"{ORIGINAL_IMAGES_DIR}/*"):
file_name = Path(image_file_path).stem
with Image.open(image_file_path) as image:
size_2x = get_x_size(image.size, 2)
size_1x = get_x_size(image.size, 1)
## Generate WEBP images.
image.save(f"{GENERATED_IMAGES_DIR}/{file_name}-3x.webp", quality=50)
image_2x = image.resize(size_2x)
image_2x.save(f"{GENERATED_IMAGES_DIR}/{file_name}-2x.webp", quality=50)
image_1x = image.resize(size_1x)
image_1x.save(f"{GENERATED_IMAGES_DIR}/{file_name}-1x.webp", quality=70)
## Generate JPG images.
# Convert from RGBA to RGB since jpg doesn't support transparency.
rgb_image = image.convert("RGB")
rgb_image.save(f"{GENERATED_IMAGES_DIR}/{file_name}-3x.jpg", quality=19, optimize=True)
rgb_image_2x = rgb_image.resize(size_2x)
rgb_image_2x.save(
f"{GENERATED_IMAGES_DIR}/{file_name}-2x.jpg", quality=50, optimize=True
)
rgb_image_1x = rgb_image.resize(size_1x)
rgb_image_1x.save(
f"{GENERATED_IMAGES_DIR}/{file_name}-1x.jpg", quality=70, optimize=True
)
if __name__ == "__main__":
generate_landing_page_images()

View File

@ -12,6 +12,7 @@ import "font-awesome/css/font-awesome.css";
import "../../images/icons/zulip-icons.font";
import "source-sans/source-sans-3VF.css";
import "source-code-pro/source-code-pro.css";
import "@fontsource-variable/open-sans";
import "../../styles/alerts.css";
import "../../styles/modal.css";
import "../../styles/progress_bar.css";

6
web/src/bundles/hello.ts Normal file
View File

@ -0,0 +1,6 @@
// TODO: Use `common.ts` directly in hello page when `boostrap` is
// removed from it.
import "source-sans/source-sans-3VF.css";
import "source-code-pro/source-code-pro.css";
import "@fontsource-variable/open-sans";

View File

@ -14,6 +14,59 @@ export function path_parts() {
return window.location.pathname.split("/").filter((chunk) => chunk !== "");
}
const hello_events = function () {
function get_new_rand(oldRand, max) {
const newRand = Math.floor(Math.random() * max);
return newRand === oldRand ? get_new_rand(newRand, max) : newRand;
}
function get_random_item_from_array(array) {
return array[Math.floor(Math.random() * array.length)];
}
const current_clint_logo_class_names = new Set([
"client-logos__logo_akamai",
"client-logos__logo_tum",
"client-logos__logo_wikimedia",
"client-logos__logo_rust",
"client-logos__logo_dr_on_demand",
"client-logos__logo_maria",
]);
const future_logo_class_names = new Set([
"client-logos__logo_pilot",
"client-logos__logo_recurse",
"client-logos__logo_level_up",
"client-logos__logo_layershift",
"client-logos__logo_julia",
"client-logos__logo_ucsd",
"client-logos__logo_lean",
"client-logos__logo_asciidoc",
]);
let current_clint_logo_class_namesIndex = 0;
function update_client_logo() {
if (document.hidden) {
return;
}
const logos = [...document.querySelectorAll("[class^='client-logos__']")];
current_clint_logo_class_namesIndex = get_new_rand(
current_clint_logo_class_namesIndex,
logos.length,
);
const el = logos[current_clint_logo_class_namesIndex];
const oldClass = el.className;
el.className = "";
current_clint_logo_class_names.delete(oldClass);
const newClass = get_random_item_from_array([...future_logo_class_names.values()]);
future_logo_class_names.delete(newClass);
el.className = newClass;
current_clint_logo_class_names.add(newClass);
future_logo_class_names.add(oldClass);
}
setInterval(update_client_logo, 2500);
};
const apps_events = function () {
const info = {
windows: {
@ -121,78 +174,16 @@ const apps_events = function () {
};
const events = function () {
// get the location url like `zulip.com/features/`, cut off the trailing
// `/` and then split by `/` to get ["zulip.com", "features"], then
// pop the last element to get the current section (eg. `features`).
const location = window.location.pathname.replace(/\/$/, "").split(/\//).pop();
$(`[data-on-page='${CSS.escape(location)}']`).addClass("active");
$("body").on("click", (e) => {
const $e = $(e.target);
if ($e.is("nav ul .exit")) {
$("nav ul").css("transform", "translate(-350px, 0)");
// See https://ishadeed.com/article/layout-flickering/ for
// more context as to why the following timeout is important.
setTimeout(() => {
$("nav ul").removeClass("show");
$("nav ul").css("transform", "");
$("body").removeClass("noscroll");
}, 500);
}
if ($("nav ul.show") && !$e.closest("nav ul.show").length && !$e.is("nav ul.show")) {
$("nav ul").removeClass("show");
$("body").removeClass("noscroll");
}
});
$(".hamburger").on("click", (e) => {
$("nav ul").addClass("show");
$("body").addClass("noscroll");
e.stopPropagation();
});
if (path_parts().includes("apps")) {
apps_events();
}
if (path_parts().includes("hello")) {
hello_events();
}
};
$(() => {
// Initiate the bootstrap carousel logic
$(".carousel").carousel({
interval: false,
});
// Move to the next slide on clicking inside the carousel container
$(".carousel-inner .item-container").on("click", function (e) {
const get_tag_name = e.target.tagName.toLowerCase();
const is_button = get_tag_name === "button";
const is_link = get_tag_name === "a";
const is_last_slide = $("#tour-carousel .carousel-inner .item:last-child").hasClass(
"active",
);
// Do not trigger this event if user clicks on a button, link
// or if it's the last slide
const move_slide_forward = !is_button && !is_link && !is_last_slide;
if (move_slide_forward) {
$(this).closest(".carousel").carousel("next");
}
});
$(".carousel").on("slid", function () {
const $this = $(this);
$this.find(".visibility-control").show();
if ($this.find(".carousel-inner .item").first().hasClass("active")) {
$this.find(".left.visibility-control").hide();
} else if ($this.find(".carousel-inner .item").last().hasClass("active")) {
$this.find(".right.visibility-control").hide();
}
});
// Set up events / categories / search
events();
@ -206,14 +197,14 @@ $(() => {
// Resize tweet to avoid overlapping with image. Since tweet uses an iframe which doesn't adjust with
// screen resize, we need to manually adjust its width.
function resizeIFrameToFitContent(iFrame) {
function resize_iframe_to_fit_content(iFrame) {
$(iFrame).width("38vw");
}
window.addEventListener("resize", () => {
const iframes = document.querySelectorAll(".twitter-tweet iframe");
for (const iframe of iframes) {
resizeIFrameToFitContent(iframe);
resize_iframe_to_fit_content(iframe);
}
});
});

1062
web/styles/portico/hello.css Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -51,6 +51,15 @@
"./src/portico/landing-page",
"./styles/portico/landing_page.css"
],
"landing-page-hello": [
"./src/bundles/hello",
"./src/portico/landing-page",
"./src/portico/header",
"./styles/portico/svg_icons.css",
"./styles/portico/hello.css",
"./styles/portico/navbar.css",
"./styles/portico/footer.css"
],
"integrations": [
"./src/bundles/portico",
"./src/portico/integrations",

View File

@ -253,7 +253,7 @@ class DocPageTest(ZulipTestCase):
# Test the i18n version of one of these pages.
self._test("/en/history/", ["Zulip released as open source!"])
self._test("/values/", ["designed our company"])
self._test("/hello/", ["Chat for distributed teams"])
self._test("/hello/", ["your mission-critical communications with Zulip"])
self._test("/communities/", ["Open communities directory"])
self._test("/development-community/", ["Zulip development community"])
self._test("/features/", ["Beautiful messaging"])

View File

@ -544,7 +544,7 @@ class HomeTest(ZulipTestCase):
result = self.client_post("/accounts/accept_terms/")
self.assertEqual(result.status_code, 200)
self.assert_in_response("I agree to the", result)
self.assert_in_response("Chat for distributed teams", result)
self.assert_in_response("your mission-critical communications with Zulip", result)
def test_accept_terms_of_service(self) -> None:
self.login("hamlet")
@ -1045,7 +1045,7 @@ class HomeTest(ZulipTestCase):
with patch("zerver.views.home.get_subdomain", return_value=""):
result = self._get_home_page()
self.assertEqual(result.status_code, 200)
self.assert_in_response("Chat for distributed teams", result)
self.assert_in_response("your mission-critical communications with Zulip", result)
with patch("zerver.views.home.get_subdomain", return_value="subdomain"):
result = self._get_home_page()