wa-ui
componenttemplate-bubblesource: ok · refreshed
Template Bubble
WhatsApp Business template message bubble supporting text/image/video/document/location headers and url/phone/quick_reply/copy_code/flow/call_permission buttons.
npx shadcn add "https://ui.meta-cloud-api.site/r/template-bubble.json"free
Where it goes
navbar
Visual style
terminal-mono
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Files10 · source cached 10/8/2026, 1:45:59 AM
components/ui/whatsapp/template-bubble.tsx
"use client";
import * as React from "react";
import { Button } from "@base-ui/react/button";
import { cn } from "@/lib/utils";
import "@/components/ui/whatsapp/styles/whatsapp.css";
import { type MessageStatus, MessageStatusIcon } from "./message-status";
// ─── Types ────────────────────────────────────────────────────────────────────
type HeaderType = "text" | "image" | "video" | "document" | "location";
interface LocationHeader {
name: string;
address: string;
imageUrl?: string;
}
interface TemplateHeader {
type: HeaderType;
text?: string;
imageUrl?: string;
videoUrl?: string;
documentName?: string;
location?: LocationHeader;
}
export type TemplateButton =
// CTA — opens URL in browser
| { type: "url"; label: string; url?: string }
// CTA — initiates phone call
| { type: "phone_number"; label: string; phone_number?: string }
// User reply with pre-set text
| { type: "quick_reply"; label: string }
// Copies a coupon/promo code to clipboard
| { type: "copy_code"; label?: string; code?: string }
// Opens a WhatsApp Flow
| { type: "flow"; label: string }
// Opens a WhatsApp Catalog
| { type: "catalog"; label?: string }
// One-tap autofill OTP (authentication templates)
| { type: "otp"; label?: string }
// Calling permission request (custom display type)
| { type: "call_permission"; bizName: string };
export interface TemplateBubbleProps
extends React.HTMLAttributes<HTMLDivElement> {
variant?: "incoming" | "outgoing";
header?: TemplateHeader;
body: string;
footer?: string;
buttons?: TemplateButton[];
timestamp?: string;
/** Delivery status ticks, shown for outgoing templates */
status?: MessageStatus;
/** Bubble tail on the last message of a group (templates without buttons) */
showTail?: boolean;
/** Override the default `<a>` element for URL/phone buttons (e.g. Next.js Link) */
renderAction?: (props: { href: string; children: React.ReactNode; className: string; target?: string; rel?: string }) => React.ReactElement;
}
// ─── Variable highlighting ─────────────────────────────────────────────────
function BodyText({ text }: { text: string }) {
const parts = text.split(/(\{\{[^}]+\}\}|\{[^}]+\})/g);
return (
<>
{parts.map((part, i) =>
/^\{/.test(part) ? (
<span key={i} className="text-wa-emerald-600">
{part}
</span>
) : (
<React.Fragment key={i}>{part}</React.Fragment>
)
)}
</>
);
}
// ─── Header variants ──────────────────────────────────────────────────────────
function ImageHeader({ imageUrl }: { imageUrl?: string }) {
return (
<div className="h-[180px] w-full overflow-hidden rounded-t-lg" style={{ backgroundColor: "var(--wa-placeholder-bg)" }}>
{imageUrl ? (
<img src={imageUrl} alt="" className="h-full w-full object-cover" />
) : (
<div className="flex h-full items-center justify-center">
<img src="/wa-header-image.png" width={60} height={60} alt="" style={{ filter: "var(--wa-placeholder-icon)" }} />
</div>
)}
</div>
);
}
function VideoHeader({ videoUrl }: { videoUrl?: string }) {
return (
<div className="h-[180px] w-full overflow-hidden rounded-t-lg" style={{ backgroundColor: "var(--wa-placeholder-bg)" }}>
{videoUrl ? (
<video src={videoUrl} className="h-full w-full object-cover" />
) : (
<div className="flex h-full items-center justify-center">
<img src="/wa-header-video.png" width={60} height={60} alt="" />
</div>
)}
</div>
);
}
function DocumentHeader({ documentName }: { documentName?: string }) {
return (
<div className="flex h-[130px] w-full flex-col items-center justify-center gap-2 rounded-t-lg" style={{ backgroundColor: "var(--wa-placeholder-bg)" }}>
<img src="/wa-header-document.png" width={52} height={52} alt="" style={{ filter: "var(--wa-placeholder-icon)" }} />
{documentName && (
<span className="text-[12px]" style={{ color: "var(--wa-icon-lighter)" }}>{documentName}</span>
)}
</div>
);
}
function LocationHeader({ location }: { location?: LocationHeader }) {
return (
<div className="relative h-[160px] w-full overflow-hidden rounded-t-lg" style={{ backgroundColor: "var(--wa-placeholder-bg)" }}>
{location?.imageUrl ? (
<img src={location.imageUrl} alt="" className="h-full w-full object-cover" />
) : (
<div className="flex h-full items-center justify-center">
<img src="/wa-header-location.png" width={60} height={60} alt="" style={{ filter: "var(--wa-placeholder-icon)" }} />
</div>
)}
{(location?.name || location?.address) && (
<div className="absolute bottom-0 left-0 right-0 bg-black/30 px-3 py-2">
{location.name && (
<p className="text-[13px] font-medium text-white"><BodyText text={location.name} /></p>
)}
{location.address && (
<p className="text-[12px] text-white/80"><BodyText text={location.address} /></p>
)}
</div>
)}
</div>
);
}
// ─── Icons ────────────────────────────────────────────────────────────────────
const ICONS: Partial<Record<TemplateButton["type"], React.ReactNode>> = {
url: <img src="/wa-icon-url.png" width={16} height={16} alt="" style={{ filter: "var(--wa-btn-icon-filter)" }} />,
phone_number: <img src="/wa-icon-phone.png" width={16} height={16} alt="" style={{ filter: "var(--wa-btn-icon-filter)" }} />,
quick_reply: <img src="/wa-icon-phone.png" width={16} height={16} alt="" style={{ filter: "var(--wa-btn-icon-filter)" }} />,
flow: <img src="/wa-icon-flow.png" width={16} height={16} alt="" style={{ filter: "var(--wa-btn-icon-filter)" }} />,
catalog: <img src="/wa-icon-url.png" width={16} height={16} alt="" style={{ filter: "var(--wa-btn-icon-filter)" }} />,
// copy_code / otp — inline SVG clipboard icon
copy_code: (
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" style={{ filter: "var(--wa-btn-icon-filter)" }}>
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
</svg>
),
otp: (
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" style={{ filter: "var(--wa-btn-icon-filter)" }}>
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
</svg>
),
};
// ─── Buttons ──────────────────────────────────────────────────────────────────
function TemplateButtonRow({ button, renderAction }: { button: TemplateButton; renderAction?: TemplateBubbleProps["renderAction"] }) {
const [copied, setCopied] = React.useState(false);
const base = "flex w-full items-center justify-center gap-1.5 py-[10px] text-[14px] font-medium text-wa-emerald-500";
if (button.type === "call_permission") {
return (
<Button className={cn(base, "flex-col gap-0.5 py-3 text-wa-text-primary")}>
<div className="flex items-center gap-2">
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-wa-emerald-500 text-white">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" />
</svg>
</div>
<span className="text-[14px] font-medium">
Can <span className="font-semibold">{button.bizName}</span> call you?
</span>
</div>
</Button>
);
}
const label =
button.type === "copy_code" ? (button.label ?? "Copy offer code") :
button.type === "otp" ? (button.label ?? "Copy code") :
button.type === "catalog" ? (button.label ?? "View catalog") :
"label" in button ? button.label : "";
const isCopyType = button.type === "copy_code" || button.type === "otp";
const copyCode = isCopyType && "code" in button ? button.code : undefined;
const handleClick = copyCode
? () => {
navigator.clipboard.writeText(copyCode);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
: undefined;
const buttonContent = (
<>
{copied ? (
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" style={{ filter: "var(--wa-btn-icon-filter)" }}>
<path d="M9 16.2l-3.5-3.5-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
</svg>
) : (
ICONS[button.type] ?? null
)}
{copied ? "Copied!" : label}
</>
);
// Use renderAction if provided for link-type buttons
const href = button.type === "url" && button.url ? button.url
: button.type === "phone_number" && button.phone_number ? `tel:${button.phone_number}`
: undefined;
if (renderAction && href) {
return renderAction({
href,
children: buttonContent,
className: base,
...(button.type === "url" ? { target: "_blank", rel: "noopener noreferrer" } : {}),
});
}
const renderEl = (() => {
if (button.type === "url" && button.url)
return <a href={button.url} target="_blank" rel="noopener noreferrer" />;
if (button.type === "phone_number" && button.phone_number)
return <a href={`tel:${button.phone_number}`} />;
return undefined;
})();
return (
<Button
render={renderEl}
nativeButton={renderEl == null}
className={base}
onClick={handleClick}
>
{buttonContent}
</Button>
);
}
// ─── Main component ───────────────────────────────────────────────────────────
const TemplateBubble = React.forwardRef<HTMLDivElement, TemplateBubbleProps>(
({ className, variant = "incoming", header, body, footer, buttons = [], timestamp, status, showTail = false, renderAction, ...props }, ref) => {
const hasButtons = buttons.length > 0;
const isOutgoing = variant === "outgoing";
// Buttons hang below the body, so a tail would point at nothing there.
const tail = showTail && !hasButtons;
return (
<div
ref={ref}
className={cn("font-wa w-full min-w-[200px] max-w-[320px]", className)}
{...props}
>
{/* Bubble */}
<div className="relative">
{tail && (
<svg viewBox="0 0 8 13" width="8" height="13" className={cn("absolute bottom-0", isOutgoing ? "-right-[8px]" : "-left-[8px]")}>
{isOutgoing
? <path d="M5.188 13H0V1.807l6.467 8.625C7.526 11.844 6.958 13 5.188 13z" className="fill-wa-bubble-outgoing" />
: <path d="M1.533 10.432L8 1.807V13H2.812C1.042 13 .474 11.844 1.533 10.432z" className="fill-wa-bubble-incoming" />}
</svg>
)}
<div className={cn("overflow-hidden rounded-lg shadow-sm", isOutgoing ? "bg-wa-bubble-outgoing" : "bg-wa-bubble-incoming", tail && (isOutgoing ? "rounded-br-[3px]" : "rounded-bl-[3px]"))}>
{/* Header */}
{header?.type === "image" && <ImageHeader imageUrl={header.imageUrl} />}
{header?.type === "video" && <VideoHeader videoUrl={header.videoUrl} />}
{header?.type === "document" && <DocumentHeader documentName={header.documentName} />}
{header?.type === "location" && <LocationHeader location={header.location} />}
{/* Body */}
<div className="px-[9px] pb-[7px] pt-[6px]">
{header?.type === "text" && header.text && (
<p className="mb-1 text-[14.2px] font-bold leading-[19px] text-wa-text-primary">
<BodyText text={header.text} />
</p>
)}
<p className="text-[14.2px] leading-[19px] text-wa-text-primary">
<BodyText text={body} />
</p>
{footer && (
<p className="mt-1 text-[12px] leading-[16px] text-wa-text-secondary">
{footer}
</p>
)}
{timestamp && (
<div className="mt-[2px] flex items-center justify-end gap-[3px]">
<span className="text-[11px] leading-[15px] text-wa-bubble-meta">{timestamp}</span>
{variant === "outgoing" && status && <MessageStatusIcon status={status} />}
</div>
)}
</div>
{/* Buttons */}
{hasButtons && (
<div className="border-t border-wa-border">
{buttons.map((btn, i) => (
<div key={i} className={cn(i > 0 && "border-t border-wa-border")}>
<TemplateButtonRow button={btn} renderAction={renderAction} />
</div>
))}
</div>
)}
</div>
</div>
</div>
);
}
);
TemplateBubble.displayName = "TemplateBubble";
export { TemplateBubble };
components/ui/whatsapp/message-status.tsx
"use client";
import * as React from "react";
import "@/components/ui/whatsapp/styles/whatsapp.css";
export type MessageStatus = "sending" | "sent" | "delivered" | "read" | "failed";
function CheckIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 16 11" height="11" width="16" className={className}>
<path d="M11.071.653a.457.457 0 0 0-.304-.102.493.493 0 0 0-.381.178l-6.19 7.636-2.011-2.175a.463.463 0 0 0-.349-.158.467.467 0 0 0-.338.131.537.537 0 0 0-.14.353.54.54 0 0 0 .13.363l2.39 2.576a.465.465 0 0 0 .349.158.457.457 0 0 0 .368-.189l6.596-8.14a.504.504 0 0 0 .103-.36.516.516 0 0 0-.223-.271z" fill="currentColor" />
</svg>
);
}
function DoubleCheckIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 16 11" height="11" width="16" className={className}>
<path d="M11.071.653a.457.457 0 0 0-.304-.102.493.493 0 0 0-.381.178l-6.19 7.636-2.011-2.175a.463.463 0 0 0-.349-.158.467.467 0 0 0-.338.131.537.537 0 0 0-.14.353.54.54 0 0 0 .13.363l2.39 2.576a.465.465 0 0 0 .349.158.457.457 0 0 0 .368-.189l6.596-8.14a.504.504 0 0 0 .103-.36.516.516 0 0 0-.223-.271z" fill="currentColor" />
<path d="M15.071.653a.457.457 0 0 0-.304-.102.493.493 0 0 0-.381.178l-6.19 7.636-1.2-1.298-.728.897 1.58 1.704a.465.465 0 0 0 .349.158.457.457 0 0 0 .368-.189l6.596-8.14a.504.504 0 0 0 .103-.36.516.516 0 0 0-.193-.484z" fill="currentColor" />
</svg>
);
}
/** WhatsApp's pending-message clock, shown until the server accepts the message. */
function ClockIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 16 15" height="15" width="16" className={className} aria-label="Sending">
<path d="M9.75 7.713H8.244V5.359a.5.5 0 0 0-.5-.5H7.65a.5.5 0 0 0-.5.5v2.947a.5.5 0 0 0 .5.5h.094l.003-.001.003.002h2a.5.5 0 0 0 .5-.5v-.094a.5.5 0 0 0-.5-.5zm0-5.263h-3.5c-1.82 0-3.3 1.48-3.3 3.3v3.5c0 1.82 1.48 3.3 3.3 3.3h3.5c1.82 0 3.3-1.48 3.3-3.3v-3.5c0-1.82-1.48-3.3-3.3-3.3zm2 6.8a2 2 0 0 1-2 2h-3.5a2 2 0 0 1-2-2v-3.5a2 2 0 0 1 2-2h3.5a2 2 0 0 1 2 2v3.5z" fill="currentColor" />
</svg>
);
}
/**
* Upload indicator for outgoing media that is still sending: dims the media and
* shows WhatsApp's spinning ring. Place inside a `relative` media container.
*/
export function MediaSendingOverlay({ className }: { className?: string }) {
return (
<div
role="status"
aria-label="Sending"
className={"absolute inset-0 z-[5] flex items-center justify-center bg-black/30 " + (className ?? "")}
>
<span className="flex h-[52px] w-[52px] items-center justify-center rounded-full bg-black/50">
<svg viewBox="0 0 44 44" width="40" height="40" className="animate-spin" aria-hidden="true">
<circle cx="22" cy="22" r="18" fill="none" stroke="currentColor" strokeWidth="3" className="text-white/25" />
<circle cx="22" cy="22" r="18" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeDasharray="28 200" className="text-white" />
</svg>
</span>
</div>
);
}
/** Renders the appropriate status icon for a given MessageStatus. */
export function MessageStatusIcon({ status, className }: { status: MessageStatus; className?: string }) {
switch (status) {
case "sending":
return <span className={className ?? "text-wa-bubble-meta"}><ClockIcon /></span>;
case "sent":
return <span className="text-wa-delivered opacity-75"><CheckIcon /></span>;
case "delivered":
return <span className="text-wa-delivered"><DoubleCheckIcon /></span>;
case "read":
return <span className="text-wa-read"><DoubleCheckIcon /></span>;
case "failed":
return (
<span className="text-wa-text-critical">
<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
</svg>
</span>
);
}
}
public/wa-header-image.png
�PNG
IHDR d d p�T sRGB ��� sIDATx�}�VU������(EM�Rh��%�V9���s+�������
uE���fC
3���\JB4SWjZ��oi�ɋB��K��ܗ�y���p�ۗ��s�9������{��14ԠQ�Q�Q�Q�Q �#�e��}�����,x� ��qp4Dl�Q��F����1b����mF���s��H8
5�0w���e�r�@�7| n�O���^��B�5�#��p><j�H�`hh"ḥp\�1���ѱ!�?^��%l�@�h�N� \Wa�=�-Z�3>�WL�c`�t�Q���p
.�M�D����R>'B��+���� olgʫ��
�){�fL��Y�%�S3�S�����)��4��ӡÔ=�]Z�4HQ@��P-�4�CNZ��p�dΰk>���,^��� ��4JM\q� O���j�[xC��XY��E�~lg��<
}0ZCC�qQWnV'l���!���'�#`�RM���D�x.x�
�������xl��C$¶���dAM�����!����0;8i��u\��'T�@�ɂ�^ܑ$<��/�4l��ؘѢ��ڨQ�V�cS��+�!½)��48g,g��?-��h����Q�V��:�$C���&rWSN�wDJj�+�6j���Ȗ�j��$2�YK8��k�6EJj(h�S�Ҡ�jA�!.�bc۰\.ke�l��oI�X�#H2d<)��M�N��
��ƌ$j��$�����2��~��.|���2�)��[�w�����h���uI������9���,�fS΅�]�x�)�a{>����~6E�hC�Nj��٠��0�f��~�Bgç�ެp�:�
ߛ^Z���,lmmE8\�Ɨ~L�|svt�'�z���K�R�}وCICV�m��J���#�5�bX�#�e��� ��ŗ
(3���(�ס�J
ADW7��P����p!��ңԵp��A<��@猢�#�~f-���Wf
�n�0l7�w��h~������jC��(���i%�דEUi�=#�WS@{��@�jC�G�D��-���˪���E=��f��B<z�b�6��%��Г%�דEUi�Z��[�۷2
�U���8�g�3��8����ݴb9�
m�[��}� ��:����w@���y�4�V�f�fa�����i�_��]Ѝ�"�L�_��7p�˧�n��?}��'���
o�+�Ȫ�*�,7����,� ����σ�
ϊ~�n§�8��_�I_ �d#�D��XqD\C�m�)�vy��#��<BX*�����U�!Yz8�ݠ)0+��!kx�s1�0jt���:.����\T*3>@�sa;3�kk�����S��_3�b�-LQ��ЍB��C�:��α�[�eЏ��!�1�� /�G����r5嬧~��z��##=E�g��4�C9N���pE�W�t5��K����ӯկ��%��=Jhʆ2�Y�!����\֊���q����f�uM�=ͺ�L�ݐ�5u����,�9��d� 4���3R�~:�����#�՞K�2̠��Д���\�����_�z���WR�UT64�?.��0d����>�/�M�]��B!7s]��Lܭ��r?�U��*|x<1��6���F��a]f��k�-��&HC3~�>|hJ���gf\����eFx���&�9$����a*Is?�]���s52z?�O��̇��U�%W� ܙ��x
��~���Ͼ��k���T/S]5C�� �*�2�mЧg1~���0�Y#;���ֹ���j���6�w���ԭ��+n�o&�+Hwa[f|��c�����S���[2�����?��|��E�x��v|�rC�j�M��n�������S_ e���g%��s��!z�\�M]���7�����p�J�[9߷��3������\�[7f��:|���&�{S"o5 ���?�'�ٰ�Q�!�4U|#w�\@� �] �D�`�o 8^˹�Ⱦ7�4Cŗ>c�f8�����A����j�`�sG�i�7�ș���+r�J�x`�Ìb\
�0#l�s�T���Zj�)&$���[��㠟U�`���p��2��>-��
�A��<�k&�H��"��pLZ��(d¸��?o��ª�5��q�����)�Ҹ
�;�6$��J�z�r�j�3�
5���H��]D�J:��q�A��㡓�B�R4�t�3xuI�vs�%�w�c�[��U�j�8t_%��O�?�_�7�L����)t�
�J�+�4��8�0�?-SE�N��_�zM����d��#[mx�9��ï�C�����2S'¤a����%fH�T<l@V8)�d����.�/:�V�ƐҤ,��Ɛrt,��ƐҤ,��$C^���*G��JQc�� �������AY
�q��$C6��1�,���Qc�� ɐ���I�T���uI��&E_n�EZ��j�Pk5��cc�Mk�渰j���R�q���|����=V[5n?�����v7�T����h�BR1���#�=
�U@M�V�cH4�'�J�#���(����4���hH�j)��?�\MD^�RM�6����r,�k`�N���H5T�E����S
R�"\}��C��P;5TK5ME�!8���x'_��֜HT���vj�$�41���w�I)y�������p*��.p�p��gh��v-�������+�P��'@\��ჟ�;�;�j�3H�f�� ���-�E��?5� ����
����Q��ڐ�d����9PsN��k�$�f1�]>y{�\r~uW��yB#�qI{/Fl!����?~�h���p��Fm��=�][7e�ň��B4hhhh�_����t��o�� IEND�B`�public/wa-header-video.png
�PNG
IHDR d d p�T sRGB ��� 3IDATx��Uu�yL R���=(�+��HҤ�X�h*Ć)9`XV�U��Zm�����M'ئ1�e���?\K٠�Xiʏx?( O���ܳs�s�s�=����^~�=?����~ރϰaYd
d
�W�������\���0��Ǔ��K"�9�����;�5�p����i*#U�`������a&L��ah�{���|�M�ۃl�a���n��"}����0�,i*"_��e&�S��l�&�H�
�8CsDN�^x���vמ��c`.h�$C�pˊ�9����� M<7#��]0��
ik�}�~X+0f�4�H���T_�9�!�CN��<����q�p��r��C��h� 1�R�
�Z�Z�\�Gh�A�+a5Ɯ&mY�L��/�KaXCub8R��l�e�c�{b����#���`�p��
����<�)'�.T��)FX.��y�p���/�e�l�)�j�71�K�VCȜ���`�Aus8[����#��#C0�"rr|
ngֽ�����a��i��M7Y��^2q
�L9�Hzb�#�~�,�!x-vb����MS5����a1�Mݏ�;5?
���0Ś�P4,`P3����ì���}=O���4ZSj�0�>�fjdf Bjq���F���
̰�ϰ�ʢP59��j#��5����<��(Mٙ5��qm���ا^���eL�y�R�!<�I�|ph�K�)�[W��
�^p����_S��!N�>�2ϨI�2'�����@�2���n��K�>���٧
�㘚- 4�p��C�6Y��zc�F��O�9}8l���]Z���7[
��fјj�4в���ն�3�[��+
Ң�j��jY1*���\�L�hN�����e�T��r�au�v^�dB�P-մl�����D��\
e�+{��@)���4�E�(C��7g�vHf��ޡ�j��%���Ԏ�����G��i%o�NSx^���zo�n�s�U�E-9P\�r��q��ض���G�)��į�v�&�
��PS�U�d�����_dkW�
�'ߠ�����Q���߂mq'��� �.(�C8�l|��=7h��,�����̎�wa�`g�Gm�X���!��<���?b��Z�>M�
+�>X-ya���:�X������y�+�����!skʗatZ���@�0��͒��R�?� -s��l'?�1��Nrf�M��0�y���s[,��-�f4k�Ř�ȷM��8�}.9�g�"��R�d��[�ؤ4�d^�f����G�;���[�w�����ϷLsn ����_V[E��_��(��v��]���A��R��hF/�3�5�ٌ��ټ���p������Pk5W�\D
q%�߁�"0����|�L�3��Ps��EԐ���|���/�l��3��������U��������UP�! ��?��xv�iHC�y��ΨAi�l�y��p���+�j�Qh�L_ͭ���
d�spJw%�r�@�l�g���-j�����
d'L���7�`���C������E�GA.����g���r^g�ל��!鑘��?uD
wrBO��C���bߒt���S5$�:�8>?��O`�l
I���#�)�!vji�0U�{��YO����b�q������ؐ���J1�b
�ҽ��Ym(|hȅ��g[�z���? �
�`$$�Z��j���!=�daF?
,��p5�vDA�5d/�qM>�oH�"`��e�O�C�v�Y��A.�������
��8����]��G�����
i��A>/-K1���~X ��_��W���!QCv���jf�J�\ݽ��"HS���|���Xm��&���O��#�S,���KK� +a�y�&�^_jg'���P���N;�/9�\����"���c��
ۏ`���|X6Uc!,#�i��h��[��rNj� �0� �FX��ς�46Od� ��I��(6dGN�c��G�<]IFg�f̄�2�+���jFq��sDFC��4l`�����o�5�Ia����@t�2_`^�щ�����
�w�x?L�%����!'��:5'
����)ޙ�羠9*x,���M��O�C�N4�l�4V�(�!��-��`�:���D��1���V�D>�vܝ�i�͒a�Pc�.�!5$���,W����1�%l;����:����vmt��N�d�����0CLc_����l ������n�{Nk�g�8w S�sQ?�
M:l�>���[�<�pY�ƥT�ۿ���f����j[2���\�(�Wc��G���ơ�ږ���g?L�JdVKJ�W�N5TK5-
��=\�L�hN�����e�Tѐ�դ�u�,S@��P-+FUCp�W����-��P�e���PՐ��ͤO��+�-;XJ5S;5�5-����K�n֖���w�p5W3~���O�c�
]�\���S-��s��>cM�]MR��d�o�&��9�����=����Ԭ�����D�._���%P�=����ٸ�{ntJ�pe�権�ލ���M�{.�/D�g۹?}�:�]�2C��m�rzk
;a�*��y�BM��`jTw4��PSF�ěa1�Mݏ�;5l���ǡ�?
Vw�U�o�)L�+�l����t�O�d��������l^CMV4ۘ�I��N��PziHlY-�e��P�������%5e#��%�lts�p��v�݁�'�6S�q���|�w�/Q(�АX�8q,5$�8����~O���FB7���6ϮX<Ay]��5b�!ќa���4X
3��G;�/��Yv廳��=c�)6a��r��������Z�VS+N��,c�!��.�Ğϳ� ��4�'��a
iy$.�L�T�
z8�5�X�2��h8R:��ή����"qC�%Ø�l��)�$�.���҉��J�]`�x#�&m3$_R����
s'L Ge�͉�
k�����+��#���m7$Zr�ф�0f��F��i����n�߶��TqELO�+�
6����0AsR���"3řj�h�O���mM��q`���\Glg,��`����v��E�@�@C
�3��w� IEND�B`�public/wa-header-document.png
�PNG
IHDR d d p�T sRGB ��� �IDATx�ɏU��QTT�n�qKHL4��G��x3����A�����Q8��E=p�QCd_�UF��gR/��]ճ�ׯg~��jz����~����*Ɩ�2���6�UY;�.xV��09���E���G`|lll���A��
,��Ͱ�aۛ�FH�Y�fP���պ��Zh�j�R�Kp�0H;c`�8@}{}�h�T
Z
�`_/���Tr������v���b������ X6Ew�Z�<s��f��5l��iּ�<;����`��[�x�W�6��6�љ!4U~Ʀ�~���诀'�l ��qt���/�I���4fl��w��
"3a�H�2���94��fJ�!��̰���0C"3a�2E�փY��}#uJ�>���Wac����
l�;�~XM�F�!�ݬp��^�]
���e���˦�C7S�6���j��j��d
҇���4��e�p.�:
$CKkF�y|$��j��j9�q��_G�N
�l��i�r�q�C�_�9��D����p&���0�r4�V����8opR���'q C����
�94�Q�s�w�n'�YG�%1#~��p��y�s|��h���'�O��bs� M�Čڛ�%�0�r��z]�#x\B��E���)��_n�>
f$����+��!��TT���@ C<��e9̀K#MYe��gD�~z&�}8CE��8��6(�C^GY�8��.Y��!f�p\� ��
�°�+��?Wk�gw C�;\t���4��e��6Sf���K�(a�����.CG�(���j9v���aHQ��V2L�{;�!�0�
C��~�c�ey�Ip�J�;&�q�#!�Q���ze? �pF>'�3p��O�z�N��������6��g��8!<'�{��)�N������$ЋS�h�u�%q�z7�����L�y�\��
ޜ�-Jt�(�Д�d���L��b�ѲZ�����d��d��B�yR��%����!{�,�C.��lQNWw]�r���.Y��!v�;`���yGX��#j��Q�+��v��?�D�cx�y=�ydz0=��yȽM�K�(�!�ߟ��ۚm���W�0�����ի����eU�x�T�@eʼn C*S����O�b9�MA��K�.�9��P'w��\N��E(a�f� ���f�s���B��աҷ���Wu�0�%��a��d�m/� f�?Ր����T%������{a���ɔY�������n�Pu�0�>��+�nFR[�1�c{B�|3
W)�����e[b�a{,m����6DZ~��{���:(�d���o���p��ޅۘa���u�C_�G C����x��Nu�)������z��!��>�1�(aH��(��ޟ�ũ��D<��O���(aH��)F���a;V�SvG��aHev�!aHe
TV�Ȑ0�2*+NdHR��'2$�L�ʊ�T�@eʼn C*S���D��!�)PYq"C���8�!aHe
TV�Ȑ0�2*+NdHR��'2$�L�ʊ2����h�:w U�o*�کa���2�_��!I��o�mH��1Ļf���m\��P;5T�y1�{�}4_��P;5��!��ꃏ���G�N�SC�TӾ�uÎw;}��[�|2\�������-5�]��ޝwb��ƌ��z�s�თE�&����!��p��$7g�e'�ڼ��c����
x����
|
Sjʶo���0F������)��'Rf�����:�6Ui?�������k�-�f���Rf|Οć�31D�O������f��HOJH����i�d3����k;o5r��fe����vF�t��*
������`=� �_�خdL���
4c���K��;Z;s>3�N��mv�N/�`��:�͘Oh�B�TO��]
4�Q�I�r;��2i����� IEND�B`�public/wa-header-location.png
�PNG
IHDR d d p�T sRGB ��� �IDATx��]U�������^�^)�/Q��%B�c��H���Ā��F�`� B�Z��Ph��mgz��;-���<ӵj���^��Ϟ��q�9g���z�}�e�[d�h��,��;` L����Fy+L��]��pN�Y8�ݔg`g`?��7n��f��f��?���'�mpw`�(�$P�:E�����}O{�v��z��u������k�c��@�n�Ŏ�~s���惎z����w�dP��:���i:S�啿�D����2(�N��P�>��`?���D�{����͂��ѫ�c&sA��#e,�)�7��h2�)�m�N�V�7rn�SG\-FɦS�#��wLE~&^#��Y�R�����ޢ���Ϗ J�8��gÍ�J�1"���{a)<
�������b�N �:HEq����N1�21��ͲG�c� ��3��������tH��TuLg���p�@��f�"!�����
F�"x�상{�1�Q��9Mq��)u�2͝C?��*wJ�T`x��P��d1h���κAa�B�N�����!
����`��4�F���
��~��Un�
Bt��'���t����|r^�����V`� R���9�P�/��w4���W`�MAv@/}ww_ɜR� 4l|V�2py[e�T��O.P���͠@-[�S�Ga)�\�rXA�=�7�� ��pu�vһ��۹d��}487�Ss��3��VE�(���jP�>ƂEA\E>��CK��>�`D�7~S�4��t}|w��}���`�>�7e;�J���b
��~Nr�ayK7��>�7���8�H�VF�gR�f�2�f�,Qo��>�w}�/:@ߔ�V"dw�.DA̡c�컂<+�e�ɖ,����6:�
`+���G��O)g~2j.���,��%B��N䮪R��6�L���4��?�d�����*o�T'��������D��#}�de�<w�!����FN�/��G�*��8���\iY�yxg>������@�p����1b�#��ɤ�t5T���QA|������T��A�
g�ĉ��x�g�x 8�{�Pz��wSL9���%�-,H�}����dp�6-:��a(���=q�j���Dz�o���k˘��G����jXKi���p��(�#:t<G�ߦ���,#I���
�D� ,G��4'u#dt�18
�Z� vΛ�~��w�(Cؑ��O�aP���yF���v+�u�Ae��e5|mX�����g}�~#\�"��ؼ<�h�-\�`t���@ač�駐y-��:�W��}ր���}��
��a-E;3T<��������R���h|ψ1Z:�{�(�ەPnA��,Y� ����\�� ��UV�;�b=��L�π9ޔ&9,�샢���
�l����3���$Tm����xS��K�d��ɇ6
kV���z.s$9g�
�#:�j�N��ޫ����T�>+<�Sqe5r����}p���5]
Uj�i�T�R�{����d��bX;9)J.3�;b�o�x/�\}V8%��
R���T��جx���G��)�D�W{�P��6���v�W�/��^�g�q�*K!�:���d/'|AA<_�)~��R"$y�Jp�h�4�g)�\yý�9W$WS����,d)�a��P�\f��)�l[�G�{%�,E�gL��s�s�r���{́���>+<'��hŖ��e��,s[�J�Y� F�RX���w���x/�\}V8���)��@9�N����������n���=sY��e!K�? �Ʌj.w���!�y�T�Y���C҇��U9���n�,d���xa��`�Eq���fC���:��{��*����T�WI>K��#T�\�2�#������)K?1����о����j��a^�3�B�"���Wx�*Ԃ�E�M�m'�y���G���l,i������:=ʰN�6B��x�Œ}�"H/M�fi��J��i���k7<��I��<�~��b��I�Oa ������2}�.X��FE����Q��J��
5(\d�d&��,�����1R�����J��F�+*�o]�.��k˘;t�YH�*)���G�{B�sr��)�����
��A�5v���i���� ��)�9��S���̉�-��zb�x��-�qVjd82uD�cx�4�'�t�G��4z�V�;쵦(WO
�F��:�
�qm+f{��ؾB)�늏����{��F�81�a:R��Gq�tL
sm�M��*Si���ȨKn5��c~���-%Bb�}��E�Jj�̝&�莃�,��̤��;���ڍ=�����2�Z��4̕��:�ARk��9����E�&IVFWZ�a�'}��ƺ(�� ��o��$K�Iʛĝ�#AsT�u��qN�'���Vf�7�ˋ
�
c�L��B��R֊ ���77m٘�nJ}�&�$9e]�u�s��0�Y�������˴�<
��>M[��� �H9�F1
�͝�X�laT�߳8���*��0�¿����4��W�o��k�/Z��YG+6�Qb�S��8(!�بx\��� �}<�+�����s
4r�9�����_a<�f�9��a�_�<[D��D9�KW[
a^�F�(n ���cp"�<S��S�����|���a�Lb<�&3
�K['���X�U!�%6�Ȱ�F�L��MS�.�����TE�V� !dϒ�>����<X>-f�r��<� ��o�WeV� ״�"�]�)�G���{L?��(05y�*���X�Vk�C�|� ��(#�<��8�� ��J���*�ԭop�E�V]ݼv�3��H\u�U�}�?�ʽW�vk�5��B7K ����
��̌��`<��Uu
�*�N�T�����Ո��s��H�+�y4b�m��a���S�>���ER�����p7L�����V� ��� �����I��Y����v@Q|^���p��)��\-�v�r7��!{���RGQb���_�����2h��I\��3�߀��FG_�TE���]�BG�Q������N�����`[m��`d���|�A���Z wur�����BS��0���8���c��q��!o:�Le�糭Z�����b�l�N���rU�P�X�;�\�6��Y��+�4���zpB���Z킄��#��N�����6SSئC��'��[�\�CG�;�*fY(P·:�9Cv�&8�G��� �ti�.��I�ъ�O��!�F�6Ү�Fk����.�b3(�#s"� Pd3w���U�{%^���.#���\φ�AQr�blY�hKm�)�w#k� �Q���ߎV��]P�0
!�>�l�5B�82�����ՐC7|
�������Z#��<�T��`
�
dԔ1��w��8+���!R@���Q��Ա�)�������p��pA�(kj��(W\=�
�S���Q�����p�s
��npu�ߤ4E{�Z# �"PW_������e�=E��p����ted(T#���DO�R�H1B<c��J�s�f�� >�32܅���O�h+�m���x�b!��c�Om�g.�����g��FGH�\p������\q97��a<h�xJ�u~�j�4�o�qm(l#"BboD��N�_
�(����[~��)�#�B�=ۇ0.��s�LA|��r�덖e���M���� IEND�B`�public/wa-icon-url.png
RIFF� WEBPVP8X
# # ALPH� pIv=�w ��#�Fx�ǫ�;K.U������`�m$����<B�TB*�x��[��i�2.ѓQ /c���t�uaZ��a~��q�%w��� TW��GU|T LD�VE�U���t�NT�g�X��S@�@;��f<՛��X�d�,/s��v�?�WK
�"VP8 � �*$ $ >�:�I%�"�*�^�� l ���x��]R0��r��o���p�+
�����w��� ����;*V�'��V�;��\���|���?��Z�qg�Ne�������/�<�)8�Ab�B��%��1�hJ����A�Z���c���xkI���Z�?W�+O\;��)������(�+{�H|B�#�,9r6����N��,�ž0a�9��S9��HMme��?�w¿糱mQ���&�R�9���ʮ��P public/wa-icon-phone.png
RIFF WEBPVP8X
0 ICCP� �lcms mntrRGB XYZ � acspMSFT sawsctrl �� �-hand����<�QE(z�� desc � cprt wtpt rXYZ , gXYZ @ bXYZ T rTRC h `gTRC h `bTRC h `desc uP3 text CC0 XYZ �Q �XYZ �� =�����XYZ J� �7
�XYZ (8
ȹcurv * | ��u��N
b���j. C$�)j.~3�9�?�FWM6Tv\dl�uV~��,�6�����۾����e�w����ALPHr ` P�C�B!�3��`
3!�B�ﺍ "�mF��m�=>z�(��Zū����y���kv;TOf\ Gj���XFq،!���"R����������[#�U�#�j� VP8 � 0 �* >�F�K%���� � l �w����>�n � �+�(PIր ��i0@�x0֛!�ZeWŏ����!ka��� +7t�~P�����=\#sk}l�O�vf��p�VuV3�L\��%-���<��������:'�������~��Ϣ��v�18 public/wa-icon-flow.png
RIFF> WEBPVP8X
ALPHU ` h",�Q��D��6�&���@�ݧ�3"&