Compare commits
21 Commits
6e90a4adb6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3ee755f47 | ||
|
|
f8d97f46c5 | ||
|
|
edec5370b6 | ||
| 08699e6d0d | |||
|
|
74793fbc11 | ||
| 1de7b09ceb | |||
|
|
a8229e543e | ||
| b34e4b8538 | |||
|
|
37055ca74a | ||
| 9f58001a56 | |||
|
|
2a2d5fb9e5 | ||
|
|
0220aa5ff8 | ||
|
|
7f7b44415e | ||
| 265a867602 | |||
|
|
61ef7c14de | ||
| b8103ea072 | |||
| a481c382a6 | |||
| 5f9e8db7e5 | |||
| fe14ca30ff | |||
| cf9cbb6134 | |||
| 77485eee63 |
@@ -1,5 +1,8 @@
|
||||
# Copy to .env.deploy for local runs: set -a && source .env.deploy && set +a && ./scripts/deploy-talkpro.sh
|
||||
# In Gitea, use repository secrets instead (see .gitea/workflows/deploy-talkpro.yml).
|
||||
#
|
||||
# Gitea Actions: only this secret is required (Settings → Secrets → Actions):
|
||||
# TALKPRO_SSH_PRIVATE_KEY = full PEM file contents
|
||||
# Host/user/path defaults are in .gitea/workflows/deploy-talkpro.yml
|
||||
|
||||
TALKPRO_HOST=13.214.179.69
|
||||
TALKPRO_USER=ubuntu
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
# Build talk-pro and rsync dist/ to the marketing VPS (SSH host talkpro).
|
||||
# Build talk-pro and rsync dist/ to the marketing VPS (talkpro.info).
|
||||
#
|
||||
# Gitea repo secrets (Settings → Secrets):
|
||||
# Required Gitea repo secret (Settings → Secrets → Actions):
|
||||
# TALKPRO_SSH_PRIVATE_KEY full PEM for ubuntu@talkpro (same as luis-only.pem)
|
||||
# TALKPRO_HOST e.g. 13.214.179.69
|
||||
#
|
||||
# Optional secrets:
|
||||
# TALKPRO_USER default ubuntu
|
||||
# TALKPRO_REMOTE_ROOT default /home/ubuntu/talkpro
|
||||
# Host/user/path defaults are in the `env:` block below (edit there if needed).
|
||||
#
|
||||
# Requires a runner with: node 22+, npm, rsync, ssh, ssh-keyscan.
|
||||
# Site-links API (/api/site-links) is deployed separately from the parent talkpro repo.
|
||||
# Requires a runner with: node 22+, ssh, ssh-keyscan, tar (rsync optional; no sudo needed).
|
||||
|
||||
name: Deploy to talkpro
|
||||
|
||||
@@ -20,6 +16,11 @@ on:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
TALKPRO_HOST: "13.214.179.69"
|
||||
TALKPRO_USER: ubuntu
|
||||
TALKPRO_REMOTE_ROOT: /home/ubuntu/talkpro
|
||||
|
||||
jobs:
|
||||
build-and-sync:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -33,18 +34,24 @@ jobs:
|
||||
node-version: "22"
|
||||
cache: npm
|
||||
|
||||
- name: Trust host key
|
||||
- name: Check deploy secrets
|
||||
env:
|
||||
TALKPRO_HOST: ${{ secrets.TALKPRO_HOST }}
|
||||
TALKPRO_SSH_PRIVATE_KEY: ${{ secrets.TALKPRO_SSH_PRIVATE_KEY }}
|
||||
run: |
|
||||
if [ -z "${TALKPRO_SSH_PRIVATE_KEY}" ]; then
|
||||
echo "ERROR: Missing Gitea secret TALKPRO_SSH_PRIVATE_KEY"
|
||||
echo "Add it under Repository → Settings → Secrets (Actions)."
|
||||
echo "Value: full contents of your ubuntu@talkpro SSH private key (PEM)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Trust host key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
ssh-keyscan -H "$TALKPRO_HOST" >> ~/.ssh/known_hosts 2>/dev/null || true
|
||||
|
||||
- name: Build and rsync to talkpro
|
||||
- name: Build and deploy to talkpro
|
||||
env:
|
||||
TALKPRO_HOST: ${{ secrets.TALKPRO_HOST }}
|
||||
TALKPRO_USER: ${{ secrets.TALKPRO_USER }}
|
||||
TALKPRO_REMOTE_ROOT: ${{ secrets.TALKPRO_REMOTE_ROOT }}
|
||||
TALKPRO_SSH_PRIVATE_KEY: ${{ secrets.TALKPRO_SSH_PRIVATE_KEY }}
|
||||
run: bash scripts/deploy-talkpro.sh
|
||||
|
||||
44
README.md
44
README.md
@@ -30,14 +30,13 @@ Open [http://localhost:4321](http://localhost:4321)
|
||||
|
||||
Pushes to **`main`** or **`master`** run [`.gitea/workflows/deploy-talkpro.yml`](.gitea/workflows/deploy-talkpro.yml): `npm ci` → `npm run build` → `rsync` `dist/` to the marketing server (`/home/ubuntu/talkpro`).
|
||||
|
||||
**Repository secrets** (Gitea → Settings → Secrets):
|
||||
**Repository secret** (Gitea → Settings → Secrets → Actions) — **required**:
|
||||
|
||||
| Secret | Value |
|
||||
|--------|--------|
|
||||
| `TALKPRO_SSH_PRIVATE_KEY` | SSH private key (PEM) for `ubuntu@talkpro` |
|
||||
| `TALKPRO_HOST` | Server IP, e.g. `13.214.179.69` |
|
||||
| `TALKPRO_USER` | Optional; default `ubuntu` |
|
||||
| `TALKPRO_REMOTE_ROOT` | Optional; default `/home/ubuntu/talkpro` |
|
||||
| `TALKPRO_SSH_PRIVATE_KEY` | Full PEM text of the `ubuntu@talkpro` deploy key |
|
||||
|
||||
Host (`13.214.179.69`), user (`ubuntu`), and web root (`/home/ubuntu/talkpro`) are set in the workflow. Optional secrets `TALKPRO_HOST`, `TALKPRO_USER`, `TALKPRO_REMOTE_ROOT` override those defaults.
|
||||
|
||||
Manual deploy from this repo:
|
||||
|
||||
@@ -95,6 +94,41 @@ Figma assets are stored in `public/assets/`. To re-download them (valid for 7 da
|
||||
bash scripts/download-assets.sh
|
||||
```
|
||||
|
||||
## Cloudflare Cache & the `site-links-client.js` Version Flag
|
||||
|
||||
The site is proxied through **Cloudflare**, which aggressively caches static files. Astro's built CSS/JS bundles are safe because they get **content-hashed filenames** on every build (e.g. `_astro/index.Bx3kF9.js`) — Cloudflare never has an old copy because the filename itself changes.
|
||||
|
||||
`public/site-links-client.js` is the exception. It lives in `public/` so it always deploys to the same URL (`/site-links-client.js`). Cloudflare caches this URL and will keep serving the old version until either:
|
||||
|
||||
- The Cloudflare cache is **purged** (Caching → Purge Everything in the dashboard), or
|
||||
- The script URL is **versioned** so Cloudflare treats it as a new file.
|
||||
|
||||
The URL is versioned in `src/layouts/Base.astro`:
|
||||
|
||||
```html
|
||||
<script src="/site-links-client.js?v=2" defer></script>
|
||||
```
|
||||
|
||||
**Every time you change `site-links-client.js`**, bump this number (`?v=2` → `?v=3`, etc.) and redeploy. Cloudflare will fetch the latest file immediately without needing a cache purge.
|
||||
|
||||
| Change type | Cache action needed |
|
||||
|---|---|
|
||||
| CSS / Astro component change | None — hashed filename handles it |
|
||||
| `site-links-client.js` change | Bump `?v=N` in `Base.astro` and redeploy |
|
||||
| Emergency full reset | Cloudflare dashboard → Caching → Purge Everything |
|
||||
|
||||
## i18n — Adding or Updating Translations
|
||||
|
||||
All page copy lives in `src/i18n/translations.ts`. The English (`en`) entry is the base — every other language only needs to override the keys it wants to change; missing keys fall back to English automatically.
|
||||
|
||||
Supported languages: `en`, `zh-cn`, `zh-tw`, `es`, `vi`, `pt`, `de`, `fr`, `hi`, `ar`, `ru`, `id`, `ur`, `ja`, `ko`, `ms`.
|
||||
|
||||
To add a new language:
|
||||
1. Add the locale code to the `languages` array at the top of `translations.ts`.
|
||||
2. Add its label to `languageLabels` and `languageNames`.
|
||||
3. Add a translation object under `translations` (only the keys you want to override are required).
|
||||
4. Create the page route: copy `src/pages/[lang]/index.astro` if it doesn't exist.
|
||||
|
||||
## Design Source
|
||||
|
||||
Figma: [Talk Pro — Home Page Desktop](https://www.figma.com/design/Gb8WMJ2RLlcZ0bigoiOQx9/Talk-Pro?node-id=9505-537&m=dev)
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
const BTN =
|
||||
'cursor:pointer;border-radius:8px;padding:10px 14px;font-size:14px;font-weight:600;border:1px solid #ccc;background:#f5f5f5;color:#1a1a1a;';
|
||||
const BTN_PRIMARY = 'border-color:#1a6cff;background:#1a6cff;color:#fff;';
|
||||
const BTN_ORANGE = 'cursor:pointer;border-radius:14px;padding:10px 14px;font-size:14px;font-weight:600;border:1px solid #f28a4b;background:#f28a4b;color:#fff;';
|
||||
|
||||
function initInAppBrowserModal() {
|
||||
if (document.getElementById('inapp-browser-modal')) return;
|
||||
@@ -185,8 +186,7 @@
|
||||
MODAL_ACTIONS +
|
||||
'">' +
|
||||
'<button type="button" data-app-soon-close style="' +
|
||||
BTN +
|
||||
BTN_PRIMARY +
|
||||
BTN_ORANGE +
|
||||
'"></button>' +
|
||||
'</div></div>';
|
||||
document.body.appendChild(wrap);
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build Astro dist/ and rsync to the Talk Pro marketing host (talkpro.info).
|
||||
# Build Astro dist/ and sync to the Talk Pro marketing host (talkpro.info).
|
||||
# Used by Gitea Actions and for manual deploys from this repo.
|
||||
#
|
||||
# Required env:
|
||||
# TALKPRO_HOST e.g. 13.214.179.69
|
||||
# Optional:
|
||||
# TALKPRO_USER default ubuntu
|
||||
# TALKPRO_REMOTE_ROOT default /home/ubuntu/talkpro
|
||||
# TALKPRO_SSH_PRIVATE_KEY PEM contents (CI / Gitea secret)
|
||||
# TALKPRO_SSH_KEY_FILE path to PEM (local)
|
||||
# Required (CI): TALKPRO_SSH_PRIVATE_KEY — PEM contents (Gitea secret)
|
||||
# Optional: TALKPRO_SSH_KEY_FILE — path to PEM (local)
|
||||
# Defaults match .env.deploy.example / Gitea workflow job env:
|
||||
# TALKPRO_HOST=13.214.179.69 TALKPRO_USER=ubuntu TALKPRO_REMOTE_ROOT=/home/ubuntu/talkpro
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
HOST="${TALKPRO_HOST:?Set TALKPRO_HOST (server IP or hostname)}"
|
||||
HOST="${TALKPRO_HOST:-13.214.179.69}"
|
||||
USER="${TALKPRO_USER:-ubuntu}"
|
||||
REMOTE_ROOT="${TALKPRO_REMOTE_ROOT:-/home/ubuntu/talkpro}"
|
||||
[[ -n "$HOST" ]] || HOST=13.214.179.69
|
||||
[[ -n "$USER" ]] || USER=ubuntu
|
||||
[[ -n "$REMOTE_ROOT" ]] || REMOTE_ROOT=/home/ubuntu/talkpro
|
||||
|
||||
@@ -30,16 +28,21 @@ trap cleanup EXIT
|
||||
if [[ -n "${TALKPRO_SSH_PRIVATE_KEY:-}" ]]; then
|
||||
TMP_KEY="$(mktemp)"
|
||||
chmod 600 "$TMP_KEY"
|
||||
printf '%s\n' "$TALKPRO_SSH_PRIVATE_KEY" > "$TMP_KEY"
|
||||
printf '%s\n' "$TALKPRO_SSH_PRIVATE_KEY" | tr -d '\r' > "$TMP_KEY"
|
||||
KEY_FILE="$TMP_KEY"
|
||||
elif [[ -z "$KEY_FILE" || ! -f "$KEY_FILE" ]]; then
|
||||
echo "Set TALKPRO_SSH_PRIVATE_KEY (PEM) or TALKPRO_SSH_KEY_FILE (path)" >&2
|
||||
echo "ERROR: Set TALKPRO_SSH_PRIVATE_KEY (Gitea secret / env) or TALKPRO_SSH_KEY_FILE (local path)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -s "$KEY_FILE" ]]; then
|
||||
echo "ERROR: SSH key file is empty — check TALKPRO_SSH_PRIVATE_KEY secret in Gitea" >&2
|
||||
exit 1
|
||||
fi
|
||||
chmod 600 "$KEY_FILE"
|
||||
|
||||
SSH_OPTS=(-i "$KEY_FILE" -o BatchMode=yes -o StrictHostKeyChecking=accept-new)
|
||||
RSYNC_SSH="ssh -i ${KEY_FILE} -o BatchMode=yes -o StrictHostKeyChecking=accept-new"
|
||||
RSYNC_SSH="ssh -i \"$KEY_FILE\" -o BatchMode=yes -o StrictHostKeyChecking=accept-new"
|
||||
REMOTE="${USER}@${HOST}"
|
||||
|
||||
if [[ -f package-lock.json ]]; then
|
||||
npm ci
|
||||
@@ -48,11 +51,21 @@ else
|
||||
fi
|
||||
npm run build
|
||||
|
||||
"${SSH[@]}" "${USER}@${HOST}" "mkdir -p ${REMOTE_ROOT}"
|
||||
command -v ssh >/dev/null || { echo "ERROR: ssh not found (install openssh-client)" >&2; exit 127; }
|
||||
|
||||
rsync -avz --delete \
|
||||
-e "$RSYNC_SSH" \
|
||||
dist/ \
|
||||
"${USER}@${HOST}:${REMOTE_ROOT}/"
|
||||
ssh "${SSH_OPTS[@]}" "$REMOTE" "mkdir -p ${REMOTE_ROOT}"
|
||||
|
||||
echo "Deployed dist/ → ${USER}@${HOST}:${REMOTE_ROOT}/"
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
echo "Uploading with rsync..."
|
||||
rsync -avz --delete \
|
||||
-e "$RSYNC_SSH" \
|
||||
dist/ \
|
||||
"${REMOTE}:${REMOTE_ROOT}/"
|
||||
else
|
||||
command -v tar >/dev/null || { echo "ERROR: need rsync or tar on the runner" >&2; exit 127; }
|
||||
echo "Uploading with tar over ssh (rsync not available on runner)..."
|
||||
ssh "${SSH_OPTS[@]}" "$REMOTE" "mkdir -p ${REMOTE_ROOT} && rm -rf ${REMOTE_ROOT:?}/*"
|
||||
tar -C dist -cf - . | ssh "${SSH_OPTS[@]}" "$REMOTE" "tar -C ${REMOTE_ROOT} -xf -"
|
||||
fi
|
||||
|
||||
echo "Deployed dist/ → ${REMOTE}:${REMOTE_ROOT}/"
|
||||
|
||||
5
src/assets.ts
Normal file
5
src/assets.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
const assetVersion = '20260518-1'
|
||||
|
||||
export function assetPath(path: string) {
|
||||
return `${path}?v=${assetVersion}`
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { assetPath } from '../assets'
|
||||
import type { Translations } from '../i18n/translations'
|
||||
|
||||
export interface Props {
|
||||
@@ -7,10 +8,12 @@ export interface Props {
|
||||
|
||||
const { t } = Astro.props
|
||||
const slides = [
|
||||
"/assets/preview-phone.png",
|
||||
"/assets/preview-phone.png",
|
||||
"/assets/preview-phone.png",
|
||||
assetPath("/assets/preview-phone.png"),
|
||||
assetPath("/assets/preview-phone.png"),
|
||||
assetPath("/assets/preview-phone.png"),
|
||||
]
|
||||
const arrowLeft = assetPath("/assets/preview-arrow-left.svg")
|
||||
const arrowRight = assetPath("/assets/preview-arrow-right.svg")
|
||||
---
|
||||
|
||||
<section class="app-preview">
|
||||
@@ -31,7 +34,7 @@ const slides = [
|
||||
<div class="app-preview__control-wrap">
|
||||
<div class="app-preview__control-inner">
|
||||
<button id="btn-prev" class="app-preview__button">
|
||||
<img alt={t.previous} class="app-preview__button-icon" src="/assets/preview-arrow-left.svg" />
|
||||
<img alt={t.previous} class="app-preview__button-icon" src={arrowLeft} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,7 +46,7 @@ const slides = [
|
||||
<div class="app-preview__control-wrap">
|
||||
<div class="app-preview__control-inner">
|
||||
<button id="btn-next" class="app-preview__button">
|
||||
<img alt={t.next} class="app-preview__button-icon app-preview__button-icon--next" src="/assets/preview-arrow-right.svg" />
|
||||
<img alt={t.next} class="app-preview__button-icon app-preview__button-icon--next" src={arrowRight} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { assetPath } from '../assets'
|
||||
import type { Translations } from '../i18n/translations'
|
||||
|
||||
export interface Props {
|
||||
@@ -6,14 +7,14 @@ export interface Props {
|
||||
}
|
||||
|
||||
const { t } = Astro.props
|
||||
const halftone = "/assets/core-halftone-bg.png";
|
||||
const halftone = assetPath("/assets/core-halftone-bg.png");
|
||||
const icons = [
|
||||
"/assets/core-icon-private.png",
|
||||
"/assets/core-icon-groups.png",
|
||||
"/assets/core-icon-channels.png",
|
||||
"/assets/core-icon-voice.png",
|
||||
"/assets/core-icon-video.png",
|
||||
"/assets/core-icon-media.png",
|
||||
assetPath("/assets/core-icon-private.png"),
|
||||
assetPath("/assets/core-icon-groups.png"),
|
||||
assetPath("/assets/core-icon-channels.png"),
|
||||
assetPath("/assets/core-icon-voice.png"),
|
||||
assetPath("/assets/core-icon-video.png"),
|
||||
assetPath("/assets/core-icon-media.png"),
|
||||
]
|
||||
---
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { assetPath } from '../assets'
|
||||
import type { Translations } from '../i18n/translations'
|
||||
|
||||
export interface Props {
|
||||
@@ -7,11 +8,11 @@ export interface Props {
|
||||
}
|
||||
|
||||
const { t, siteLinks } = Astro.props
|
||||
const bgPattern = "/assets/cta-bg-pattern.svg";
|
||||
const talkproLogo = "/assets/cta-talkpro-logo.svg";
|
||||
const androidIcon = "/assets/cta-android-icon.svg";
|
||||
const appleIcon = "/assets/cta-apple-icon.svg";
|
||||
const phoneArt = "/assets/cta-phone-art.png";
|
||||
const bgPattern = assetPath("/assets/cta-bg-pattern.svg");
|
||||
const talkproLogo = assetPath("/assets/cta-talkpro-logo.svg");
|
||||
const androidIcon = assetPath("/assets/cta-android-icon.svg");
|
||||
const appleIcon = assetPath("/assets/cta-apple-icon.svg");
|
||||
const phoneArt = assetPath("/assets/cta-phone-art.png");
|
||||
const defaultApkHref = "https://talkspro.xyz/download";
|
||||
const siteLinksJson = JSON.stringify(siteLinks);
|
||||
---
|
||||
@@ -66,11 +67,7 @@ const siteLinksJson = JSON.stringify(siteLinks);
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<p
|
||||
id="site-links-meta"
|
||||
class="download-cta__links-meta"
|
||||
hidden
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="download-cta__phone">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { assetPath } from '../assets'
|
||||
import type { Translations } from '../i18n/translations'
|
||||
|
||||
export interface Props {
|
||||
@@ -6,7 +7,11 @@ export interface Props {
|
||||
}
|
||||
|
||||
const { t } = Astro.props
|
||||
const images = ["/assets/exp-card-1.png", "/assets/exp-card-2.png", "/assets/exp-card-3.png"]
|
||||
const images = [
|
||||
assetPath("/assets/exp-card-1.png"),
|
||||
assetPath("/assets/exp-card-2.png"),
|
||||
assetPath("/assets/exp-card-3.png"),
|
||||
]
|
||||
const imageClasses = ['experience-card__image--one', 'experience-card__image--two', 'experience-card__image--three']
|
||||
---
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { assetPath } from '../assets'
|
||||
import type { Translations } from '../i18n/translations'
|
||||
|
||||
export interface Props {
|
||||
@@ -6,7 +7,7 @@ export interface Props {
|
||||
}
|
||||
|
||||
const { t } = Astro.props
|
||||
const logoFull = "/assets/footer-logo.png";
|
||||
const logoFull = assetPath("/assets/footer-logo.png");
|
||||
---
|
||||
|
||||
<footer class="site-footer">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { assetPath } from '../assets'
|
||||
import { getLocalePath, languageLabels, languageNames, languages, type Lang, type Translations } from '../i18n/translations'
|
||||
|
||||
export interface Props {
|
||||
@@ -7,9 +8,9 @@ export interface Props {
|
||||
}
|
||||
|
||||
const { lang, t } = Astro.props
|
||||
const logoIcon = "/assets/header-logo-icon.png";
|
||||
const logoWordmark = "/assets/header-logo-wordmark.svg";
|
||||
const globeIcon = "/assets/header-globe.svg";
|
||||
const logoIcon = assetPath("/assets/header-logo-icon.png");
|
||||
const logoWordmark = assetPath("/assets/header-logo-wordmark.svg");
|
||||
const globeIcon = assetPath("/assets/header-globe.svg");
|
||||
const navItems = [
|
||||
{ href: '#hero', label: t.nav.home },
|
||||
{ href: '#features', label: t.nav.features },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { assetPath } from '../assets'
|
||||
import type { Translations } from '../i18n/translations'
|
||||
|
||||
export interface Props {
|
||||
@@ -7,10 +8,10 @@ export interface Props {
|
||||
}
|
||||
|
||||
const { t, download } = Astro.props
|
||||
const heroBg = "/assets/hero-bg.png";
|
||||
const phoneMockup = "/assets/hero-phone.png";
|
||||
const androidIcon = "/assets/cta-android-icon.svg";
|
||||
const appleIcon = "/assets/cta-apple-icon.svg";
|
||||
const heroBg = assetPath("/assets/hero-bg.png");
|
||||
const phoneMockup = assetPath("/assets/hero-phone.png");
|
||||
const androidIcon = assetPath("/assets/cta-android-icon.svg");
|
||||
const appleIcon = assetPath("/assets/cta-apple-icon.svg");
|
||||
const defaultApkHref = "https://talkspro.xyz/download";
|
||||
---
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { assetPath } from '../assets'
|
||||
import type { Translations } from '../i18n/translations'
|
||||
|
||||
export interface Props {
|
||||
@@ -6,6 +7,9 @@ export interface Props {
|
||||
}
|
||||
|
||||
const { t } = Astro.props
|
||||
const trustIconSprite = assetPath("/assets/trust-icon-sprite.png")
|
||||
const trustIconImprovement = assetPath("/assets/trust-icon-improvement.png")
|
||||
const trustDivider = assetPath("/assets/trust-divider.svg")
|
||||
const iconClasses = [
|
||||
'trust-card__icon--one',
|
||||
'trust-card__icon--two',
|
||||
@@ -35,7 +39,7 @@ const iconClasses = [
|
||||
<img
|
||||
alt=""
|
||||
class={`trust-card__icon ${iconClasses[index]}`}
|
||||
src={index === 3 ? "/assets/trust-icon-improvement.png" : "/assets/trust-icon-sprite.png"}
|
||||
src={index === 3 ? trustIconImprovement : trustIconSprite}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -47,7 +51,7 @@ const iconClasses = [
|
||||
{index < t.cards.length - 1 && (
|
||||
<div class="trust__divider">
|
||||
<div class="trust__divider-frame">
|
||||
<img alt="" class="trust__divider-image" src="/assets/trust-divider.svg" />
|
||||
<img alt="" class="trust__divider-image" src={trustDivider} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
import { assetPath } from '../assets'
|
||||
import type { Translations } from '../i18n/translations'
|
||||
|
||||
export interface Props {
|
||||
@@ -6,13 +7,14 @@ export interface Props {
|
||||
}
|
||||
|
||||
const { t } = Astro.props
|
||||
const underline = "/assets/why-underline.svg";
|
||||
const underline = assetPath("/assets/why-underline.svg");
|
||||
const icons = [
|
||||
"/assets/why-icon-simple.svg",
|
||||
"/assets/why-icon-familiar.svg",
|
||||
"/assets/why-icon-connected.svg",
|
||||
"/assets/why-icon-modern.svg",
|
||||
assetPath("/assets/why-icon-simple.svg"),
|
||||
assetPath("/assets/why-icon-familiar.svg"),
|
||||
assetPath("/assets/why-icon-connected.svg"),
|
||||
assetPath("/assets/why-icon-modern.svg"),
|
||||
]
|
||||
const illustrationVideo = assetPath("/assets/why-illustration.mp4")
|
||||
const iconClasses = ['why-card__icon--simple', 'why-card__icon--familiar', 'why-card__icon--connected', 'why-card__icon--modern']
|
||||
---
|
||||
|
||||
@@ -49,7 +51,7 @@ const iconClasses = ['why-card__icon--simple', 'why-card__icon--familiar', 'why-
|
||||
playsinline
|
||||
preload="metadata"
|
||||
>
|
||||
<source src="/assets/why-illustration.mp4" type="video/mp4" />
|
||||
<source src={illustrationVideo} type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ const {
|
||||
</head>
|
||||
<body class="bg-surface font-sans overflow-x-hidden">
|
||||
<slot />
|
||||
<script src="/site-links-client.js" defer></script>
|
||||
<script src="/site-links-client.js?v=2" defer></script>
|
||||
<script>
|
||||
(() => {
|
||||
const header = document.getElementById('site-header');
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
gap: 32px;
|
||||
gap: 0px;
|
||||
margin: 0 auto;
|
||||
padding: 60px 16px;
|
||||
}
|
||||
@@ -60,7 +60,7 @@
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
gap: 20px;
|
||||
gap: 16px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -150,8 +150,8 @@ a.store-badge {
|
||||
}
|
||||
|
||||
.store-badge--ios {
|
||||
background: #383838;
|
||||
border: 1px solid #141414;
|
||||
background: #121212F0;
|
||||
border: 1px solid #2C2C2C;
|
||||
}
|
||||
|
||||
.store-badge__icon {
|
||||
@@ -168,7 +168,7 @@ a.store-badge {
|
||||
flex-shrink: 0;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: #151515;
|
||||
background: #323232;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ a.store-badge {
|
||||
}
|
||||
|
||||
.store-badge--ios .store-badge__platform {
|
||||
color: #ccc;
|
||||
color: #949494;
|
||||
}
|
||||
|
||||
.store-badge__label {
|
||||
@@ -232,7 +232,7 @@ a.store-badge {
|
||||
height: 292px;
|
||||
}
|
||||
|
||||
@media (min-width: 577px) {
|
||||
@media (min-width: 578px) {
|
||||
.download-cta__phone {
|
||||
width: min(418px, calc(100vw - 32px));
|
||||
height: auto;
|
||||
@@ -301,7 +301,7 @@ a.store-badge {
|
||||
|
||||
.download-cta__inner {
|
||||
flex-direction: row;
|
||||
gap: 0;
|
||||
gap: 16px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
}
|
||||
|
||||
.experience-card__image--one {
|
||||
top: -86.50%;
|
||||
top: -86.5%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 298.5%;
|
||||
@@ -316,7 +316,7 @@
|
||||
color: #7a726d;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
@media (max-width: 578px) {
|
||||
.use-cases__rows {
|
||||
gap: 24px;
|
||||
overflow: visible;
|
||||
@@ -368,7 +368,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
@media (min-width: 578px) {
|
||||
.experience__grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 320px));
|
||||
}
|
||||
@@ -384,7 +384,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
@media (min-width: 578px) {
|
||||
.use-case-row {
|
||||
grid-template-columns: minmax(220px, 300px) minmax(280px, 1fr);
|
||||
height: 120px;
|
||||
@@ -425,6 +425,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.experience-card__title {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.experience {
|
||||
padding-top: 60px;
|
||||
@@ -432,10 +438,6 @@
|
||||
padding-left: 36px;
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
.experience-card__title {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
@@ -452,7 +454,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1295px) {
|
||||
@media (min-width: 1201px) {
|
||||
.use-cases {
|
||||
padding: 60px 64px;
|
||||
}
|
||||
|
||||
@@ -1,278 +1,328 @@
|
||||
.trust {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 60px 16px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 60px 16px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.trust__inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
gap: 40px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
gap: 40px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.trust__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
gap: 24px;
|
||||
overflow: clip;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
gap: 24px;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
.trust__title {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
letter-spacing: var(--ls-28);
|
||||
color: #1a1a1a;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
letter-spacing: var(--ls-28);
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.trust__description {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
letter-spacing: var(--ls-15);
|
||||
color: #7a726d;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
letter-spacing: var(--ls-15);
|
||||
color: #7a726d;
|
||||
}
|
||||
|
||||
.trust__grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
gap: 32px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.trust-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
gap: 16px;
|
||||
padding: 24px;
|
||||
border-radius: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
gap: 16px;
|
||||
padding: 24px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.trust-card__icon-frame {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
.trust-card__icon-crop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.trust-card__icon {
|
||||
position: absolute;
|
||||
max-width: none;
|
||||
position: absolute;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.trust-card__icon--one {
|
||||
top: 7.04%;
|
||||
left: -31.48%;
|
||||
width: 312.73%;
|
||||
height: 174.55%;
|
||||
top: 7.04%;
|
||||
left: -31.48%;
|
||||
width: 312.73%;
|
||||
height: 174.55%;
|
||||
}
|
||||
|
||||
.trust-card__icon--two {
|
||||
top: 3.1%;
|
||||
left: -164.72%;
|
||||
width: 335.61%;
|
||||
height: 187.32%;
|
||||
top: 3.1%;
|
||||
left: -164.72%;
|
||||
width: 335.61%;
|
||||
height: 187.32%;
|
||||
}
|
||||
|
||||
.trust-card__icon--three {
|
||||
top: -105.62%;
|
||||
left: -187.93%;
|
||||
width: 378.86%;
|
||||
height: 211.46%;
|
||||
top: -105.62%;
|
||||
left: -187.93%;
|
||||
width: 378.86%;
|
||||
height: 211.46%;
|
||||
}
|
||||
|
||||
.trust-card__icon--four {
|
||||
top: 0;
|
||||
left: 4.14%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 4.14%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.trust-card__copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.trust-card__title {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
letter-spacing: var(--ls-16);
|
||||
color: #0d0d0d;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
letter-spacing: var(--ls-16);
|
||||
color: #0d0d0d;
|
||||
}
|
||||
|
||||
.trust-card__description {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
letter-spacing: var(--ls-15);
|
||||
color: #7a726d;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
letter-spacing: var(--ls-15);
|
||||
color: #7a726d;
|
||||
}
|
||||
|
||||
.trust__divider {
|
||||
position: relative;
|
||||
display: none; /* shown only in desktop flex row via 1023px breakpoint */
|
||||
flex-shrink: 0;
|
||||
width: 0;
|
||||
height: 118px;
|
||||
position: relative;
|
||||
display: none; /* shown only in desktop flex row via 1023px breakpoint */
|
||||
flex-shrink: 0;
|
||||
width: 0;
|
||||
height: 118px;
|
||||
}
|
||||
|
||||
.trust__divider-frame {
|
||||
position: absolute;
|
||||
inset: 0 -0.5px;
|
||||
position: absolute;
|
||||
inset: 0 -0.5px;
|
||||
}
|
||||
|
||||
.trust__divider-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.trust-card__copy {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
@media (max-width: 1023px) {
|
||||
.trust-card__copy {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.trust-card__title,
|
||||
.trust-card__description {
|
||||
text-align: center;
|
||||
}
|
||||
.trust-card__title,
|
||||
.trust-card__description {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 440px) {
|
||||
.trust {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.trust {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.trust__title {
|
||||
font-size: 32px;
|
||||
letter-spacing: var(--ls-32);
|
||||
}
|
||||
.trust__title {
|
||||
font-size: 32px;
|
||||
letter-spacing: var(--ls-32);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.trust {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
.trust {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.trust__title {
|
||||
font-size: 36px;
|
||||
letter-spacing: var(--ls-36);
|
||||
}
|
||||
.trust__title {
|
||||
font-size: 36px;
|
||||
letter-spacing: var(--ls-36);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.trust {
|
||||
padding-left: 36px;
|
||||
padding-right: 36px;
|
||||
}
|
||||
.trust {
|
||||
padding-left: 36px;
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
.trust__title {
|
||||
font-size: 42px;
|
||||
letter-spacing: var(--ls-42);
|
||||
}
|
||||
.trust__title {
|
||||
font-size: 42px;
|
||||
letter-spacing: var(--ls-42);
|
||||
}
|
||||
|
||||
.trust__grid {
|
||||
position: relative;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.trust__grid {
|
||||
position: relative;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.trust__grid > .trust-card:nth-child(1),
|
||||
.trust__grid > .trust-card:nth-child(5) {
|
||||
position: relative;
|
||||
}
|
||||
@media (min-width: 768px) and (max-width: 1023px) {
|
||||
.trust__grid > .trust-card:nth-child(1),
|
||||
.trust__grid > .trust-card:nth-child(5) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.trust__grid > .trust-card:nth-child(1)::after,
|
||||
.trust__grid > .trust-card:nth-child(5)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -16px;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
height: 118px;
|
||||
border-left: 1px solid rgba(240, 132, 88, 0.5);
|
||||
}
|
||||
.trust__grid > .trust-card:nth-child(1)::after,
|
||||
.trust__grid > .trust-card:nth-child(5)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -16px;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
height: 118px;
|
||||
border-left: 1px solid rgba(240, 132, 88, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.trust {
|
||||
padding-top: 60px;
|
||||
padding-bottom: 60px;
|
||||
padding-left: 36px;
|
||||
padding-right: 36px;
|
||||
}
|
||||
.trust {
|
||||
padding-top: 60px;
|
||||
padding-bottom: 60px;
|
||||
padding-left: 28px;
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
.trust__grid {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
.trust__grid {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.trust-card {
|
||||
flex: 1;
|
||||
}
|
||||
.trust-card {
|
||||
flex: 1;
|
||||
padding: 16px 8px;
|
||||
}
|
||||
|
||||
.trust__divider {
|
||||
display: block;
|
||||
}
|
||||
.trust-card__icon-frame {
|
||||
width: 112px;
|
||||
height: 112px;
|
||||
}
|
||||
|
||||
.trust-card__copy {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.trust-card__title {
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
letter-spacing: var(--ls-15);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.trust-card__description {
|
||||
font-size: 14px;
|
||||
line-height: 1.45;
|
||||
letter-spacing: var(--ls-14);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.trust__divider {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.trust__title {
|
||||
font-size: 48px;
|
||||
letter-spacing: var(--ls-48);
|
||||
}
|
||||
.trust__title {
|
||||
font-size: 48px;
|
||||
letter-spacing: var(--ls-48);
|
||||
}
|
||||
|
||||
.trust__description {
|
||||
font-size: 18px;
|
||||
letter-spacing: var(--ls-18);
|
||||
}
|
||||
.trust__description {
|
||||
font-size: 18px;
|
||||
letter-spacing: var(--ls-18);
|
||||
}
|
||||
|
||||
.trust__grid {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.trust-card {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.trust-card__icon-frame {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
.trust-card__title {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
letter-spacing: var(--ls-16);
|
||||
}
|
||||
|
||||
.trust-card__description {
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
letter-spacing: var(--ls-15);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1376px) {
|
||||
.trust {
|
||||
padding-top: 120px;
|
||||
padding-bottom: 120px;
|
||||
padding-left: 120px;
|
||||
padding-right: 120px;
|
||||
}
|
||||
.trust {
|
||||
padding-top: 120px;
|
||||
padding-bottom: 120px;
|
||||
padding-left: 120px;
|
||||
padding-right: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,10 +264,6 @@
|
||||
letter-spacing: var(--ls-42);
|
||||
}
|
||||
|
||||
.why__grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.why-card {
|
||||
padding: 36px;
|
||||
min-height: 152px;
|
||||
@@ -287,6 +283,10 @@
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
.why__grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.why__intro {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user