Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 57 additions & 20 deletions src/components/HeroCarousel.astro
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@
// at all — the dots still work.
//
// Reused by every locale homepage; only `imgCode` and the `shots` copy change.
import { ArrowUpRight } from '@lucide/astro';

interface Shot {
scene: string;
alt: string;
/** Short feature name, shown above the caption. */
label: string;
caption: string;
/** Where this feature is covered in full — a blog post, most often. When set,
* the shot and its label both link there. */
href?: string;
}

interface Props {
Expand Down Expand Up @@ -47,23 +52,39 @@ const { imgCode, shots, interval = 4500 } = Astro.props;
style="aspect-ratio: 1200 / 872;"
>
{
shots.map((shot, i) => (
<img
src={`/screenshots/${shot.scene}/${imgCode}.webp`}
alt={shot.alt}
width="1200"
height="872"
decoding="async"
loading={i === 0 ? 'eager' : 'lazy'}
fetchpriority={i === 0 ? 'high' : 'low'}
aria-hidden={i === 0 ? undefined : 'true'}
data-slide={i}
class:list={[
'hc-slide absolute inset-0 h-full w-full object-cover transition-opacity duration-700 ease-in-out',
i === 0 ? 'opacity-100' : 'opacity-0',
]}
/>
))
shots.map((shot, i) => {
const img = (
<img
src={`/screenshots/${shot.scene}/${imgCode}.webp`}
alt={shot.alt}
width="1200"
height="872"
decoding="async"
loading={i === 0 ? 'eager' : 'lazy'}
fetchpriority={i === 0 ? 'high' : 'low'}
class="h-full w-full object-cover"
/>
);
const slideClasses = [
'hc-slide absolute inset-0 transition-opacity duration-700 ease-in-out',
i === 0 ? 'opacity-100' : 'opacity-0',
];
return shot.href ? (
<a
href={shot.href}
aria-hidden={i === 0 ? undefined : 'true'}
tabindex={i === 0 ? undefined : -1}
data-slide={i}
class:list={['block', ...slideClasses]}
>
{img}
</a>
) : (
<div aria-hidden={i === 0 ? undefined : 'true'} data-slide={i} class:list={slideClasses}>
{img}
</div>
);
})
}
</div>

Expand All @@ -85,9 +106,20 @@ const { imgCode, shots, interval = 4500 } = Astro.props;
>
{/* 11px, not 10: `uppercase` does nothing for CJK labels, so they
lean on size alone to read as a label. */}
<span class="font-mono text-[11px] leading-4 font-bold tracking-wider text-wk-orange uppercase">
{shot.label}
</span>
{shot.href ? (
<a
href={shot.href}
tabindex={i === 0 ? undefined : -1}
class="group/hc-label inline-flex items-center justify-center gap-1 font-mono text-[11px] leading-4 font-bold tracking-wider text-wk-orange uppercase transition-opacity hover:opacity-70"
>
{shot.label}
<ArrowUpRight class="h-3 w-3 transition-transform group-hover/hc-label:translate-x-0.5 group-hover/hc-label:-translate-y-0.5" />
</a>
) : (
<span class="font-mono text-[11px] leading-4 font-bold tracking-wider text-wk-orange uppercase">
{shot.label}
</span>
)}
<span class="text-sm leading-relaxed text-gray-500 dark:text-gray-400">{shot.caption}</span>
</p>
))
Expand Down Expand Up @@ -201,12 +233,17 @@ const { imgCode, shots, interval = 4500 } = Astro.props;

// Only the current shot and caption are exposed — otherwise a screen
// reader reads every screenshot's alt text and every caption in a row.
// The CSS `pointer-events: none` on [aria-hidden] keeps hidden slides
// unclickable, but a keyboard user can still Tab to a hidden link, so
// its tabindex is toggled alongside aria-hidden.
for (const group of [slides, captions]) {
group.forEach((el, i) => {
const active = i === current;
el.classList.toggle('opacity-100', active);
el.classList.toggle('opacity-0', !active);
el.toggleAttribute('aria-hidden', !active);
const link = el.tagName === 'A' ? el : el.querySelector<HTMLAnchorElement>('a');
if (link) link.tabIndex = active ? 0 : -1;
});
}

Expand Down
3 changes: 3 additions & 0 deletions src/pages/de/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ const heroShots = [
alt: 'Ein Anrufablauf in WaveKat Voice als Karte — Begrüßung, Prüfung der Öffnungszeiten, Klingeln an Ihrem Platz und Anrufbeantworter',
label: 'Anrufabläufe',
caption: 'Begrüßung, Öffnungszeiten, Ihr Platz klingelt, dann der Anrufbeantworter — als Karte.',
href: '/de/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'flow-takeover',
alt: 'WaveKat Voice, während ein Anrufablauf eine Nachricht aufnimmt — mit Live-Transkript und einer Schaltfläche zum Übernehmen',
label: 'Nimmt für Sie ab',
caption: 'Der Ablauf geht ran — Sie lesen mit und übernehmen mitten in der Nachricht.',
href: '/de/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'share-sheet',
alt: 'WaveKat Voice teilt eine Anrufaufnahme — privat, auf Einladung oder für alle mit dem Link, mit maskierter Anruferkennung',
label: 'Aufgezeichnet und teilbar',
caption: 'Jeder Anruf wird aufgezeichnet und mitgeschrieben. Per Link teilen — Sie bestimmen, wer hört.',
href: '/de/blog/share-a-call-recording/',
},
];

Expand Down
3 changes: 3 additions & 0 deletions src/pages/es/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ const heroShots = [
alt: 'Un flujo de llamada de WaveKat Voice dibujado como un mapa: saludo, comprobación del horario, suena su equipo y buzón de voz',
label: 'Flujos de llamada',
caption: 'Saludo, su horario, suena su equipo y, si no, buzón de voz: dibujado como un mapa.',
href: '/es/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'flow-takeover',
alt: 'WaveKat Voice mientras un flujo de llamada toma un mensaje, con la transcripción en vivo y un botón para contestar',
label: 'Contesta por usted',
caption: 'El flujo contesta: usted lee lo que dicen y toma la llamada a mitad del mensaje.',
href: '/es/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'share-sheet',
alt: 'WaveKat Voice compartiendo la grabación de una llamada: privado, por invitación o cualquiera con el enlace, con la identidad enmascarada',
label: 'Grabada y compartible',
caption: 'Cada llamada se graba y se escribe. Compártala con un enlace y decida quién la oye.',
href: '/es/blog/share-a-call-recording/',
},
];

Expand Down
3 changes: 3 additions & 0 deletions src/pages/fr/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ const heroShots = [
alt: 'Un flux d’appel WaveKat Voice dessiné comme une carte : accueil, vérification des horaires, sonnerie à votre poste et messagerie',
label: 'Flux d’appel',
caption: 'Accueil, vos horaires, votre poste sonne, puis la messagerie — dessiné comme une carte.',
href: '/fr/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'flow-takeover',
alt: 'WaveKat Voice pendant qu’un flux d’appel prend un message, avec la transcription en direct et un bouton pour décrocher',
label: 'Décroche pour vous',
caption: 'Le flux décroche : vous lisez ce qui se dit et reprenez l’appel en plein message.',
href: '/fr/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'share-sheet',
alt: 'WaveKat Voice partage un enregistrement d’appel : privé, sur invitation ou toute personne ayant le lien, avec l’identité de l’appelant masquée',
label: 'Enregistré et partageable',
caption: 'Chaque appel est enregistré et retranscrit. Partagez-le par lien : vous choisissez qui l’écoute.',
href: '/fr/blog/share-a-call-recording/',
},
];

Expand Down
3 changes: 3 additions & 0 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,21 @@ const heroShots = [
alt: 'A WaveKat Voice call flow drawn as a map — a greeting, a business-hours check, ringing your desk, and voicemail',
label: 'Call flows',
caption: 'A greeting, your opening hours, ring your desk, then voicemail — drawn as a map.',
href: '/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'flow-takeover',
alt: 'WaveKat Voice while a call flow takes a message, showing the live transcript and a Pick up button to take the call over',
label: "Answers when you can't",
caption: 'It picks up for you — read what they say and take the call over mid-message.',
href: '/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'share-sheet',
alt: 'WaveKat Voice sharing a call recording, with private, specific-people and anyone-with-the-link options and the caller identity masked',
label: 'Recorded and shareable',
caption: 'Every call is recorded and written down. Send one by link, and choose who hears it.',
href: '/blog/share-a-call-recording/',
},
];

Expand Down
3 changes: 3 additions & 0 deletions src/pages/it/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ const heroShots = [
alt: 'Un flusso di chiamata di WaveKat Voice disegnato come una mappa: saluto, controllo degli orari, squillo alla tua postazione e segreteria',
label: 'Flussi di chiamata',
caption: 'Saluto, i tuoi orari, squilla la tua postazione, poi la segreteria — come una mappa.',
href: '/it/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'flow-takeover',
alt: 'WaveKat Voice mentre un flusso di chiamata prende un messaggio, con la trascrizione dal vivo e un pulsante per rispondere',
label: 'Risponde per te',
caption: 'Il flusso risponde: leggi quello che dicono e prendi la chiamata a metà messaggio.',
href: '/it/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'share-sheet',
alt: 'WaveKat Voice condivide la registrazione di una chiamata: privato, su invito o chiunque abbia il link, con l’identità del chiamante mascherata',
label: 'Registrata e condivisibile',
caption: 'Ogni chiamata è registrata e trascritta. Mandala con un link e decidi chi la ascolta.',
href: '/it/blog/share-a-call-recording/',
},
];

Expand Down
3 changes: 3 additions & 0 deletions src/pages/ja/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ const heroShots = [
alt: 'WaveKat Voice の通話フロー図——挨拶、営業時間の判定、手元を鳴らす、最後に留守番メッセージ',
label: '通話フロー',
caption: '挨拶、営業時間、手元を鳴らす、そして留守番メッセージ——一枚の地図に。',
href: '/ja/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'flow-takeover',
alt: '通話フローが伝言を預かっている WaveKat Voice——リアルタイム文字起こしと応答ボタン',
label: '手が離せないときに応答',
caption: '代わりに応答します——相手の話を読みながら、途中で代わることもできます。',
href: '/ja/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'share-sheet',
alt: 'WaveKat Voice の通話録音の共有——非公開・招待制・リンクを知っている全員から選べ、発信者情報は伏せられる',
label: '録音はリンクで共有',
caption: 'すべての通話を録音して文字に。リンクで送れて、誰が聞けるかも選べます。',
href: '/ja/blog/share-a-call-recording/',
},
];

Expand Down
3 changes: 3 additions & 0 deletions src/pages/ko/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ const heroShots = [
alt: 'WaveKat Voice의 통화 플로우 지도 — 인사말, 영업시간 확인, 내 자리 울리기, 마지막으로 음성 메시지',
label: '통화 플로우',
caption: '인사말, 영업시간, 내 자리 울리기, 그리고 음성 메시지 — 한 장의 지도로.',
href: '/ko/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'flow-takeover',
alt: '통화 플로우가 메시지를 받는 동안의 WaveKat Voice — 실시간 전사와 받기 버튼',
label: '손이 빌 때까지 대신 받기',
caption: '대신 받아 줍니다 — 상대의 말을 읽으면서 도중에 넘겨받을 수 있습니다.',
href: '/ko/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'share-sheet',
alt: 'WaveKat Voice의 통화 녹음 공유 — 비공개·초대 전용·링크를 아는 누구나 중에서 고르고 발신자 정보는 가려집니다',
label: '녹음은 링크로 공유',
caption: '모든 통화가 녹음되고 글로 남습니다. 링크로 보내고, 누가 들을지 정하세요.',
href: '/ko/blog/share-a-call-recording/',
},
];

Expand Down
3 changes: 3 additions & 0 deletions src/pages/zh-hant/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,21 @@ const heroShots = [
alt: 'WaveKat Voice 裡的通話流程圖——問候語、營業時間判斷、響你的桌面,最後是留言',
label: '通話流程',
caption: '問候語、營業時間、響你的桌面,再到留言——畫成一張流程圖。',
href: '/zh-hant/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'flow-takeover',
alt: 'WaveKat Voice 的通話流程正在錄留言,旁邊是即時文字稿和一個接聽按鈕',
label: '你忙不過來時它來接',
caption: '它替你接聽——一邊看對方在說什麼,一邊隨時把電話接過來。',
href: '/zh-hant/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'share-sheet',
alt: 'WaveKat Voice 分享通話錄音——可選私密、僅限受邀者或任何人可開啟,並對來電者身分做遮蔽',
label: '錄音隨時可分享',
caption: '每通電話都錄音並寫成文字。用一條連結發出去,還能決定誰能聽。',
href: '/zh-hant/blog/share-a-call-recording/',
},
];

Expand Down
3 changes: 3 additions & 0 deletions src/pages/zh/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ const heroShots = [
alt: 'WaveKat Voice 里的来电流程图——问候语、营业时间判断、响你的桌面,最后是留言',
label: '来电流程',
caption: '问候语、营业时间、响你的桌面,再到留言——画成一张流程图。',
href: '/zh/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'flow-takeover',
alt: 'WaveKat Voice 的来电流程正在录留言,旁边是实时文字稿和一个接听按钮',
label: '你忙不过来时它来接',
caption: '它替你接听——一边看对方在说什么,一边随时把电话接过来。',
href: '/zh/blog/answer-calls-with-a-call-flow/',
},
{
scene: 'share-sheet',
alt: 'WaveKat Voice 分享通话录音——可选私密、仅限受邀者或任何人可打开,并对来电者身份做脱敏',
label: '录音随时可分享',
caption: '每通电话都录音并写成文字。用一条链接发出去,还能决定谁能听。',
href: '/zh/blog/share-a-call-recording/',
},
];

Expand Down
Loading