|
@ -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
|
||||
|
|
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 503 KiB |
After Width: | Height: | Size: 360 KiB |
After Width: | Height: | Size: 370 KiB |
After Width: | Height: | Size: 478 KiB |
After Width: | Height: | Size: 485 KiB |
After Width: | Height: | Size: 260 KiB |
After Width: | Height: | Size: 274 KiB |
After Width: | Height: | Size: 481 KiB |
After Width: | Height: | Size: 170 KiB |
After Width: | Height: | Size: 172 KiB |
After Width: | Height: | Size: 420 KiB |
After Width: | Height: | Size: 448 KiB |
After Width: | Height: | Size: 2.1 MiB |
After Width: | Height: | Size: 723 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -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 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 every message <em>in context</em>, no matter when it was sent. Maintain your <em>focus</em> and catch up on your own time <em>without stress</em>, reading just the topics <em>you care about</em>.
|
||||
</div>
|
||||
<div class="quote">
|
||||
<div class="quote__text">
|
||||
Zulip’s <strong>threading model</strong> makes it so much easier to <strong>manage my team</strong>... As a leader, <strong>in just a few minutes</strong> I can get an overview over what’s going on and see where my attention is 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 all sizes be <em>more productive</em> together, from a few friends hacking on a new idea, to <em>globally distributed</em> organizations with hundreds of people tackling the world’s hardest problems.
|
||||
</div>
|
||||
<div class="quote">
|
||||
<div class="quote__text">
|
||||
Zulip has the <strong>best user experience</strong> of all the chat apps I’ve tried... It is the only app that makes hundreds of 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’s click on “Tuesday night catering.”</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’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’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 →</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 →</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’s easy to extend or tweak Zulip.
|
||||
Take charge of your mission-critical communications with Zulip’s reliable <a href="/for/open-source/">100% free and open-source software</a>, with no vendor lock-in. You can count on our industry-leading <a href="/security/">security practices</a> to 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 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>
|
||||
Zulip’s 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’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’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>
|
||||
Akamai’s 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 we’ve 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 %}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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()
|
||||
|
|