Implement footer, redesign other sections

This commit is contained in:
2026-03-15 14:42:00 +03:00
Unverified
parent 0b81f7b947
commit 40968e9368
5 changed files with 302 additions and 171 deletions
+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 42 42">
<path fill="#FFFFFF" fill-rule="evenodd" d="M21.47 41.88c-4.11 0-6.02-.6-9.34-3-2.1 2.7-8.75 4.81-9.04 1.2 0-2.71-.6-5-1.28-7.5C1 29.5.08 26.07.08 21.1.08 9.23 9.82.3 21.36.3c11.55 0 20.6 9.37 20.6 20.91a20.6 20.6 0 0 1-20.49 20.67Zm.17-31.32c-5.62-.29-10 3.6-10.97 9.7-.8 5.05.62 11.2 1.83 11.52.58.14 2.04-1.04 2.95-1.95a10.4 10.4 0 0 0 5.08 1.81 10.7 10.7 0 0 0 11.19-9.97 10.7 10.7 0 0 0-10.08-11.1Z" clip-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 497 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 200 200" width="200px" height="200px">
<path fill-rule="evenodd" clip-rule="evenodd" d="M 13.693 88.614 C 67.369 65.304 103.102 49.815 121.044 42.299 C 172.114 20.984 182.849 17.303 189.751 17.149 C 191.284 17.149 194.658 17.456 196.958 19.295 C 198.799 20.829 199.259 22.823 199.565 24.356 C 199.873 25.889 200.18 29.111 199.873 31.565 C 197.112 60.703 185.15 131.402 179.016 163.916 C 176.408 177.717 171.347 182.317 166.441 182.778 C 155.705 183.699 147.577 175.724 137.302 168.976 C 121.044 158.395 111.997 151.8 96.202 141.371 C 77.951 129.408 89.76 122.815 100.188 112.08 C 102.948 109.319 150.031 66.378 150.95 62.545 C 151.104 62.084 151.104 60.243 150.031 59.323 C 148.956 58.403 147.423 58.71 146.197 59.016 C 144.509 59.323 118.745 76.5 68.596 110.391 C 61.235 115.453 54.64 117.906 48.659 117.754 C 42.065 117.6 29.49 114.072 19.981 111.005 C 8.479 107.325 -0.723 105.331 0.045 98.89 C 0.503 95.516 5.105 92.142 13.693 88.614 Z" style="fill: rgb(255, 255, 255);"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -11,7 +11,7 @@ export default function DownloadAppPage() {
вам нужно скачать приложение мессенджера. вам нужно скачать приложение мессенджера.
</p> </p>
<a href="https://github.com/denis0001-dev/FromChat-android/releases/latest"> <a href="https://github.com/fromchat-messenger/app/releases/latest" target="_blank" rel="noopener noreferrer">
<MaterialButton>Скачать на GitHub</MaterialButton> <MaterialButton>Скачать на GitHub</MaterialButton>
</a> </a>
+99 -29
View File
@@ -1,21 +1,27 @@
import { useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { useUserStore } from "@/state/user"; import { useUserStore } from "@/state/user";
import styles from "./home.module.scss"; import styles from "./home.module.scss";
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen"; import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
import { MaterialButton, MaterialIcon } from "@/utils/material"; import { MaterialButton, MaterialIcon } from "@/utils/material";
import generalChatScreenshot from "../../images/screenshots/general-chat.png"; import generalChatScreenshot from "../../images/screenshots/general-chat.png";
import dmScreenshot from "../../images/screenshots/dm.png"; import dmScreenshot from "../../images/screenshots/dm.png";
import telegramIcon from "../../images/telegram.svg";
import maxIcon from "../../images/max.svg";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
function GitHubLink({ children }: { children: React.ReactNode }) { const GITHUB_WEB = "https://github.com/fromchat-messenger/web";
const GITHUB_APP = "https://github.com/fromchat-messenger/app";
const GITHUB_LICENSE = `${GITHUB_WEB}/blob/main/LICENSE`;
function GitHubLink({ children, className }: { children: React.ReactNode; className?: string }) {
return ( return (
<a href="https://github.com/denis0001-dev/FromChat" target="_blank">{children}</a> <a href={`${GITHUB_WEB}/tree/main`} target="_blank" rel="noopener noreferrer" className={className}>{children}</a>
); );
} }
function SupportLink({ children }: { children: React.ReactNode }) { function SupportLink({ children, className }: { children: React.ReactNode; className?: string }) {
return ( return (
<a href="https://t.me/denis0001-dev" target="_blank">{children}</a> <a href="https://t.me/denis0001-dev" target="_blank" rel="noopener noreferrer" className={className}>{children}</a>
); );
} }
@@ -99,7 +105,9 @@ export default function HomePage() {
<section className={styles.title}> <section className={styles.title}>
<div className={styles.titleLogo} /> <div className={styles.titleLogo} />
<div className={styles.titleContent}>FromChat</div> <div className={styles.titleContent}>FromChat</div>
<div className={styles.titleDesc}>100% бесплатный и открытый мессенджер. Поддерживает self-hosted установку на своем сервере.</div> <div className={styles.titleDesc}>
100% бесплатный и открытый мессенджер. Поддерживает self-hosted установку на своём сервере.
</div>
<div className={styles.titleButtons}> <div className={styles.titleButtons}>
<MaterialButton variant="filled" onClick={() => navigate("/auth?mode=login")} icon="devices">Открыть веб-версию</MaterialButton> <MaterialButton variant="filled" onClick={() => navigate("/auth?mode=login")} icon="devices">Открыть веб-версию</MaterialButton>
<MaterialButton variant="outlined" onClick={() => navigate("/download-app")} icon="download">Скачать приложение</MaterialButton> <MaterialButton variant="outlined" onClick={() => navigate("/download-app")} icon="download">Скачать приложение</MaterialButton>
@@ -112,12 +120,12 @@ export default function HomePage() {
screenshot={generalChatScreenshot} screenshot={generalChatScreenshot}
right right
> >
Общайтесь со всеми пользователями этого сервера FromChat. Открытый форум для всех пользователей сервера. Пишите сообщения, делитесь файлами и общайтесь в реальном времени.
</FeatureSection> </FeatureSection>
<FeatureSection <FeatureSection
title={<>Личные сообщения</>} title={<>Личные сообщения</>}
screenshot={dmScreenshot}> screenshot={dmScreenshot}>
Общайтесь с одним человеком. Общайтесь с одним человеком в личной переписке.
</FeatureSection> </FeatureSection>
</section> </section>
@@ -126,31 +134,65 @@ export default function HomePage() {
<div className={styles.downloadContent}> <div className={styles.downloadContent}>
<h3>Скачайте приложение</h3> <h3>Скачайте приложение</h3>
<p> <p>
Для лучшего опыта используйте настольное приложение с поддержкой Для лучшего опыта используйте настольное приложение с уведомлениями
уведомлений и автономной работы. и автономной работой или мобильное приложение для Android.
</p> </p>
<div className={styles.downloadButtons}> <div className={styles.downloadPlatforms}>
{!isMobile ? ( {!isMobile ? (
<> <>
<a <a
href="https://github.com/Toolbox-io/FromChat/actions/workflows/build.yml" href={`${GITHUB_WEB}/actions/workflows/build.yml`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className={styles.downloadCard}
> >
<MaterialButton variant="filled"> <MaterialIcon name="computer" className={styles.downloadCardIcon} />
<span className={styles.downloadCardTitle}>Для ПК</span>
<span className={styles.downloadCardDesc}>Windows, macOS, Linux</span>
<MaterialButton variant="filled" className={styles.downloadCardBtn}>
<MaterialIcon name="download" slot="icon" /> <MaterialIcon name="download" slot="icon" />
Скачать для ПК Скачать
</MaterialButton> </MaterialButton>
</a> </a>
<MaterialButton variant="outlined" onClick={() => navigate("/login")}> <a
<MaterialIcon name="language" slot="icon" /> href={`${GITHUB_APP}/releases/latest`}
Веб-версия target="_blank"
rel="noopener noreferrer"
className={styles.downloadCard}
>
<MaterialIcon name="android" className={styles.downloadCardIcon} />
<span className={styles.downloadCardTitle}>Android</span>
<span className={styles.downloadCardDesc}>APK на GitHub</span>
<MaterialButton variant="outlined" className={styles.downloadCardBtn}>
<MaterialIcon name="download" slot="icon" />
Скачать
</MaterialButton> </MaterialButton>
</a>
<div className={styles.downloadCard}>
<MaterialIcon name="language" className={styles.downloadCardIcon} />
<span className={styles.downloadCardTitle}>Веб-версия</span>
<span className={styles.downloadCardDesc}>Без установки</span>
<MaterialButton variant="outlined" className={styles.downloadCardBtn} onClick={() => navigate("/login")}>
<MaterialIcon name="open_in_new" slot="icon" />
Открыть
</MaterialButton>
</div>
</> </>
) : ( ) : (
<MaterialButton variant="filled" onClick={() => navigate("/download-app")}> <a
href={`${GITHUB_APP}/releases/latest`}
target="_blank"
rel="noopener noreferrer"
className={styles.downloadCard}
>
<MaterialIcon name="android" className={styles.downloadCardIcon} />
<span className={styles.downloadCardTitle}>Android</span>
<span className={styles.downloadCardDesc}>Скачайте APK на GitHub</span>
<MaterialButton variant="filled" className={styles.downloadCardBtn}>
<MaterialIcon name="download" slot="icon" />
Скачать приложение Скачать приложение
</MaterialButton> </MaterialButton>
</a>
)} )}
</div> </div>
</div> </div>
@@ -162,7 +204,8 @@ export default function HomePage() {
<div className={styles.ctaContent}> <div className={styles.ctaContent}>
<h3>Готовы начать общение?</h3> <h3>Готовы начать общение?</h3>
<p> <p>
Присоединяйтесь к FromChat и общайтесь безопасно с друзьями и коллегами. Создайте аккаунт за минуту. Общайтесь в общем чате, ведите личную переписку
или звоните всё бесплатно и с открытым кодом.
</p> </p>
<div className={styles.ctaActions}> <div className={styles.ctaActions}>
{isMobile ? ( {isMobile ? (
@@ -190,20 +233,47 @@ export default function HomePage() {
</main> </main>
<footer className={styles.homepageFooter}> <footer className={styles.homepageFooter}>
<div className={styles.container}> <div className={styles.footerBrand}>
<div className={styles.footerContent}> <div className={styles.footerLogoRow}>
<div className={styles.footerLogo} />
<span className={styles.footerBrandName}>FromChat</span>
</div>
<p className={styles.footerCopyright}>FromChat © 2025</p>
</div>
<div className={styles.footerLinks}>
<div className={styles.footerSection}> <div className={styles.footerSection}>
<h4>Ссылки</h4> <Link to="/download-app" className={styles.footerLink}>
<GitHubLink>GitHub</GitHubLink> <MaterialIcon name="download" className={styles.footerLinkIcon} />
<SupportLink>Поддержка</SupportLink> Скачать приложение
</Link>
<Link to="/login" className={styles.footerLink}>
<MaterialIcon name="language" className={styles.footerLinkIcon} />
Веб-версия
</Link>
</div> </div>
<div className={styles.footerSection}> <div className={styles.footerSection}>
<h4>Лицензия</h4> <a href={`${GITHUB_APP}/tree/main`} target="_blank" rel="noopener noreferrer" className={styles.footerLink}>
<p>GPL-3.0</p> <MaterialIcon name="android" className={styles.footerLinkIcon} />
Исходный код приложения
</a>
<GitHubLink className={styles.footerLink}>
<MaterialIcon name="code" className={styles.footerLinkIcon} />
Исходный код веб-версии
</GitHubLink>
<a href={GITHUB_LICENSE} target="_blank" rel="noopener noreferrer" className={styles.footerLink}>
<MaterialIcon name="description" className={styles.footerLinkIcon} />
Лицензия
</a>
</div> </div>
</div> <div className={styles.footerSection}>
<div className={styles.footerBottom}> <a href="https://t.me/fromchat_ch" target="_blank" rel="noopener noreferrer" className={styles.footerLink}>
<p>&copy; 2025 FromChat. Сделано программистом denis0001-dev с для свободы общения.</p> <span className={`${styles.footerLinkIcon} ${styles.footerLinkIconSvg}`} style={{ maskImage: `url(${telegramIcon})`, WebkitMaskImage: `url(${telegramIcon})` }} />
Telegram
</a>
<a href="https://max.ru/join/c5t6LfnCCPetQSAOshmouEvq9vsjHZT_Lt63kw8YCg0" target="_blank" rel="noopener noreferrer" className={styles.footerLink}>
<span className={`${styles.footerLinkIcon} ${styles.footerLinkIconSvg}`} style={{ maskImage: `url(${maxIcon})`, WebkitMaskImage: `url(${maxIcon})` }} />
MAX
</a>
</div> </div>
</div> </div>
</footer> </footer>
+190 -135
View File
@@ -1,21 +1,8 @@
@use "../../css/material" as *; @use "../../css/material" as *;
.homepage { // Shared variables
min-height: 100vh; $gradient-rainbow: linear-gradient(45deg, #9333EA, #6366F1, #3B82F6, #A855F7, #D946EF, #EC4899, #7E22CE);
color: $color-dark-on-background; $gradient-conic: conic-gradient(
font-family: 'Montserrat', sans-serif;
position: relative;
background-color: $color-dark-surface;
// Container styles
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
}
.colorSphere {
background: conic-gradient(
from 0deg, from 0deg,
rgba(147, 51, 234, 0.5) 0%, rgba(147, 51, 234, 0.5) 0%,
rgba(99, 102, 241, 0.6) 12.5%, rgba(99, 102, 241, 0.6) 12.5%,
@@ -26,7 +13,54 @@
rgba(192, 132, 252, 0.5) 75%, rgba(192, 132, 252, 0.5) 75%,
rgba(126, 34, 206, 0.6) 87.5%, rgba(126, 34, 206, 0.6) 87.5%,
rgba(147, 51, 234, 0.5) 100% rgba(147, 51, 234, 0.5) 100%
); );
$glow-purple: rgba(147, 51, 234, 0.5);
$radius-card: 20px;
$radius-pill: 9999px;
// Mixins
@mixin gradient-text {
background: $gradient-rainbow;
background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 20px $glow-purple;
}
@mixin section-heading {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 1.5rem;
@include gradient-text;
}
@mixin section-content {
text-align: center;
max-width: 600px;
margin: 0 auto;
p {
font-size: 1.25rem;
line-height: 1.6;
margin-bottom: 2.5rem;
opacity: 0.9;
}
}
.homepage {
min-height: 100vh;
color: $color-dark-on-background;
font-family: 'Montserrat', sans-serif;
position: relative;
background-color: $color-dark-surface;
.container {
max-width: 960px;
margin: 0 auto;
padding: 0 2rem;
}
.colorSphere {
background: $gradient-conic;
position: absolute; position: absolute;
width: 100%; width: 100%;
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
@@ -37,7 +71,6 @@
filter: blur(80px); filter: blur(80px);
} }
// Header styles
.homepageHeader { .homepageHeader {
margin: 0 16px; margin: 0 16px;
padding: 16px 5px; padding: 16px 5px;
@@ -73,11 +106,7 @@
font-size: 2rem; font-size: 2rem;
font-weight: 700; font-weight: 700;
margin: 0; margin: 0;
background: linear-gradient(45deg, #9333EA, #6366F1, #3B82F6, #A855F7, #D946EF, #EC4899, #7E22CE); @include gradient-text;
background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 0 20px rgba(147, 51, 234, 0.5);
} }
} }
@@ -101,7 +130,6 @@
} }
} }
// Hero section
.title { .title {
padding: 60px 80px; padding: 60px 80px;
display: flex; display: flex;
@@ -122,12 +150,8 @@
.titleContent { .titleContent {
font-size: 30pt; font-size: 30pt;
font-weight: 700; font-weight: 700;
background: linear-gradient(45deg, #9333EA, #6366F1, #3B82F6, #A855F7, #D946EF, #EC4899, #7E22CE);
background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 0 20px rgba(147, 51, 234, 0.5);
margin-top: 10px; margin-top: 10px;
@include gradient-text;
} }
.titleDesc { .titleDesc {
@@ -144,11 +168,11 @@
} }
} }
// Features section
.features { .features {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 50px; gap: 50px;
overflow-x: clip;
.featureContainer { .featureContainer {
display: flex; display: flex;
@@ -170,12 +194,8 @@
.featureTitle { .featureTitle {
font-size: 30pt; font-size: 30pt;
font-weight: 700; font-weight: 700;
background: linear-gradient(45deg, #9333EA, #6366F1, #3B82F6, #A855F7, #D946EF, #EC4899, #7E22CE);
background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 0 20px rgba(147, 51, 234, 0.5);
line-height: normal; line-height: normal;
@include gradient-text;
} }
.featureDesc { .featureDesc {
@@ -191,26 +211,11 @@
$size: 400px; $size: 400px;
@keyframes rotateGradient { @keyframes rotateGradient {
from { from { transform: rotate(0deg); }
transform: rotate(0deg); to { transform: rotate(360deg); }
}
to {
transform: rotate(360deg);
}
} }
background: conic-gradient( background: $gradient-conic;
from 0deg,
rgba(147, 51, 234, 0.5) 0%,
rgba(99, 102, 241, 0.6) 12.5%,
rgba(59, 130, 246, 0.55) 25%,
rgba(168, 85, 247, 0.5) 37.5%,
rgba(217, 70, 239, 0.6) 50%,
rgba(236, 72, 153, 0.55) 62.5%,
rgba(192, 132, 252, 0.5) 75%,
rgba(126, 34, 206, 0.6) 87.5%,
rgba(147, 51, 234, 0.5) 100%
);
position: absolute; position: absolute;
width: $size; width: $size;
height: $size; height: $size;
@@ -233,65 +238,74 @@
} }
} }
// Download section
.download { .download {
padding: 6rem 0;
.downloadContent { .downloadContent {
text-align: center; @include section-content;
max-width: 600px;
margin: 0 auto;
h3 { h3 {
font-size: 2.5rem; @include section-heading;
font-weight: 700;
margin-bottom: 1.5rem;
background: linear-gradient(45deg, $color-dark-primary, $color-dark-tertiary);
background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 0 20px rgba($color-dark-primary, 0.5);
} }
p { .downloadPlatforms {
font-size: 1.25rem;
line-height: 1.6;
margin-bottom: 2.5rem;
opacity: 0.9;
}
.downloadButtons {
display: flex; display: flex;
gap: 1rem; gap: 1.5rem;
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
} }
.downloadCard {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
padding: 2rem 1.5rem;
min-width: 200px;
max-width: 260px;
background: rgba($color-dark-surface-container, 0.6);
border: 1px solid rgba($color-dark-outline, 0.3);
border-radius: $radius-card;
text-decoration: none;
color: inherit;
transition: all 0.3s ease;
&:hover {
background: rgba($color-dark-surface-container-high, 0.8);
border-color: rgba($color-dark-primary, 0.5);
box-shadow: 0 0 24px rgba($color-dark-primary, 0.2);
}
}
.downloadCardIcon {
width: 48px;
height: 48px;
color: $color-dark-primary;
}
.downloadCardTitle {
font-size: 1.25rem;
font-weight: 600;
@include gradient-text;
}
.downloadCardDesc {
font-size: 0.9rem;
opacity: 0.8;
}
.downloadCardBtn {
margin-top: 0.5rem;
}
} }
} }
// CTA section
.cta { .cta {
padding-bottom: 128px;
.ctaContent { .ctaContent {
text-align: center; @include section-content;
max-width: 600px;
margin: 0 auto;
h3 { h3 {
font-size: 2.5rem; @include section-heading;
font-weight: 700;
margin-bottom: 1.5rem;
background: linear-gradient(45deg, $color-dark-primary, $color-dark-tertiary);
background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 0 20px rgba($color-dark-primary, 0.5);
}
p {
font-size: 1.25rem;
line-height: 1.6;
margin-bottom: 2.5rem;
opacity: 0.9;
} }
.ctaActions { .ctaActions {
@@ -303,57 +317,98 @@
} }
} }
// Footer
.homepageFooter { .homepageFooter {
.footerContent { padding: 0 16px;
display: grid; background: $color-dark-surface;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); display: flex;
gap: 2rem; flex-direction: row;
margin-bottom: 2rem; gap: 32px;
max-width: 1000px;
margin-left: auto;
margin-right: auto;
align-items: center;
justify-content: center;
padding-bottom: 48px;
.footerBrand {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.75rem;
.footerLogoRow {
display: flex;
align-items: center;
gap: 10px;
}
.footerLogo {
width: 40px;
height: 40px;
background-image: url('../../images/logo_square.svg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
border-radius: 12px;
}
.footerBrandName {
font-size: 2rem;
font-weight: 700;
@include gradient-text;
}
.footerCopyright {
font-size: 0.875rem;
color: $color-dark-on-surface-variant;
opacity: 0.8;
margin: 0;
}
}
.footerLinks {
display: flex;
gap: 32px;
flex-wrap: wrap;
.footerSection { .footerSection {
h4 { display: flex;
font-size: 1.1rem; flex-direction: column;
font-weight: 600; align-items: flex-start;
margin-bottom: 1rem; gap: 0.75rem;
color: $color-dark-on-surface;
text-shadow: 0 0 10px rgba($color-dark-primary, 0.3);
}
p { .footerLink {
opacity: 0.8; display: flex;
line-height: 1.6; flex-direction: row;
align-items: center;
gap: 0.5rem;
color: $color-dark-on-surface-variant; color: $color-dark-on-surface-variant;
}
a {
color: $color-dark-on-surface;
text-decoration: none; text-decoration: none;
opacity: 0.8; font-size: 0.95rem;
display: block; transition: color 0.2s ease;
margin-bottom: 0.5rem;
transition: all 0.3s ease;
padding: 0.25rem 0;
border-radius: 4px;
&:hover { &:hover {
opacity: 1; color: $color-dark-on-surface;
color: $color-dark-primary;
text-shadow: 0 0 10px rgba($color-dark-primary, 0.5);
background: rgba($color-dark-primary, 0.1);
}
}
}
} }
.footerBottom { .footerLinkIcon {
text-align: center; width: 24px;
padding-top: 2rem; height: 24px;
min-width: 24px;
opacity: 0.9;
}
p { .footerLinkIconSvg {
opacity: 0.7; display: inline-block;
margin: 0; background-color: currentColor;
color: $color-dark-on-surface-variant; mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
}
}
} }
} }
} }