1+ ---
2+ type Lang = " pl" | " en" ;
3+
4+ const {
5+ lang = " en" ,
6+ } = Astro .props as {
7+ lang? : Lang ;
8+ };
9+
10+ const email = " hello@rocketdeploy.dev" ;
11+ const freelancerUrl = " https://www.freelancer.com/u/D4m1an0101" ;
12+ const linkedinUrl = " https://www.linkedin.com/in/profile_name/" ;
13+
14+ const t = {
15+ pl: {
16+ fab: " Kontakt" ,
17+ title: " Wybierz formę kontaktu" ,
18+ copy: " Kopiuj" ,
19+ copied: " Skopiowano ✓" ,
20+ emailHint: " Najlepsze do rozmów technicznych." ,
21+ freelancerLabel: " Napisz na Freelancer" ,
22+ freelancerHint: " Formalna współpraca przez platformę." ,
23+ linkedinLabel: " Napisz na LinkedIn" ,
24+ linkedinHint: " Pierwszy kontakt i networking." ,
25+ foot: " Nie prowadzimy czatu na żywo — odpowiadamy asynchronicznie." ,
26+ },
27+ en: {
28+ fab: " Contact" ,
29+ title: " Choose a contact option" ,
30+ copy: " Copy" ,
31+ copied: " Copied ✓" ,
32+ emailHint: " Best for technical discussions." ,
33+ freelancerLabel: " Message on Freelancer" ,
34+ freelancerHint: " If you want to work via the platform." ,
35+ linkedinLabel: " Message on LinkedIn" ,
36+ linkedinHint: " Great for introductions and quick follow-ups." ,
37+ foot: " We don’t use live chat — we reply asynchronously." ,
38+ },
39+ } as const ;
40+
41+ const L = t [lang ];
42+ ---
43+
44+ <div class =" rd-contact" data-open =" false" >
45+ <button
46+ class =" rd-contact__fab"
47+ type =" button"
48+ aria-haspopup =" dialog"
49+ aria-expanded =" false"
50+ >
51+ <span class =" rd-contact__fab-dot" aria-hidden =" true" ></span >
52+ <span class =" rd-contact__fab-text" >{ L .fab } </span >
53+ </button >
54+
55+ <div class =" rd-contact__pop" role =" dialog" >
56+ <div class =" rd-contact__pop-head" >
57+ <div class =" rd-contact__title" >{ L .title } </div >
58+ <button class =" rd-contact__close" type =" button" aria-label =" Close" >✕</button >
59+ </div >
60+
61+ <div class =" rd-contact__items" >
62+ <!-- EMAIL -->
63+ <div class =" rd-contact__item" >
64+ <div class =" rd-contact__kicker" >email</div >
65+ <div class =" rd-contact__row" >
66+ <a class =" rd-contact__link" href ={ ` mailto:${email } ` } >{ email } </a >
67+ <button
68+ class =" rd-contact__copy"
69+ type =" button"
70+ data-copy ={ email }
71+ data-copy-label ={ L .copy }
72+ data-copied-label ={ L .copied }
73+ >
74+ { L .copy }
75+ </button >
76+ </div >
77+ <div class =" rd-contact__hint" >{ L .emailHint } </div >
78+ </div >
79+
80+ <!-- FREELANCER -->
81+ <a
82+ class =" rd-contact__item rd-contact__item--link"
83+ href ={ freelancerUrl }
84+ target =" _blank"
85+ rel =" noreferrer"
86+ >
87+ <div class =" rd-contact__kicker" >freelancer</div >
88+ <div class =" rd-contact__row" >
89+ <div class =" rd-contact__label" >{ L .freelancerLabel } </div >
90+ <div class =" rd-contact__go" >↗</div >
91+ </div >
92+ <div class =" rd-contact__hint" >{ L .freelancerHint } </div >
93+ </a >
94+
95+ <!-- LINKEDIN -->
96+ <a
97+ class =" rd-contact__item rd-contact__item--link"
98+ href ={ linkedinUrl }
99+ target =" _blank"
100+ rel =" noreferrer"
101+ >
102+ <div class =" rd-contact__kicker" >linkedin</div >
103+ <div class =" rd-contact__row" >
104+ <div class =" rd-contact__label" >{ L .linkedinLabel } </div >
105+ <div class =" rd-contact__go" >↗</div >
106+ </div >
107+ <div class =" rd-contact__hint" >{ L .linkedinHint } </div >
108+ </a >
109+ </div >
110+
111+ <div class =" rd-contact__foot" >{ L .foot } </div >
112+ </div >
113+
114+ <div class =" rd-contact__backdrop" ></div >
115+ </div >
116+
117+ <script >
118+ (() => {
119+ const root = document.querySelector<HTMLElement>(".rd-contact");
120+ if (!root) return;
121+
122+ const fab = root.querySelector<HTMLButtonElement>(".rd-contact__fab");
123+ const closeBtn = root.querySelector<HTMLButtonElement>(".rd-contact__close");
124+ const backdrop = root.querySelector<HTMLElement>(".rd-contact__backdrop");
125+
126+ if (!fab || !closeBtn || !backdrop) return;
127+
128+ const setOpen = (open: boolean): void => {
129+ root.dataset.open = open ? "true" : "false";
130+ fab.setAttribute("aria-expanded", open ? "true" : "false");
131+ };
132+
133+ fab.addEventListener("click", () => setOpen(root.dataset.open !== "true"));
134+ closeBtn.addEventListener("click", () => setOpen(false));
135+ backdrop.addEventListener("click", () => setOpen(false));
136+
137+ document.addEventListener("keydown", (e: KeyboardEvent) => {
138+ if (e.key === "Escape") setOpen(false);
139+ });
140+
141+ root.querySelectorAll<HTMLButtonElement>("[data-copy]").forEach((btn) => {
142+ btn.addEventListener("click", async (e) => {
143+ e.preventDefault();
144+ const text = btn.dataset.copy;
145+ if (!text) return;
146+
147+ const copyLabel = btn.dataset.copyLabel || btn.textContent || "Copy";
148+ const copiedLabel = btn.dataset.copiedLabel || "Copied ✓";
149+
150+ try {
151+ await navigator.clipboard.writeText(text);
152+ } catch {
153+ const ta = document.createElement("textarea");
154+ ta.value = text;
155+ document.body.appendChild(ta);
156+ ta.select();
157+ document.execCommand("copy");
158+ document.body.removeChild(ta);
159+ }
160+
161+ btn.textContent = copiedLabel;
162+ setTimeout(() => {
163+ btn.textContent = copyLabel;
164+ }, 1200);
165+ });
166+ });
167+ })();
168+ </script >
0 commit comments