This commit is contained in:
2026-02-04 20:44:51 +04:00
parent d6e28dd10a
commit ffb8212a99
4 changed files with 168 additions and 20 deletions

View File

@@ -40,24 +40,57 @@ const id = `profile-${side}`;
<div class="h-1 w-5 bg-(--primary) mx-auto rounded-full mb-2 transition"></div> <div class="h-1 w-5 bg-(--primary) mx-auto rounded-full mb-2 transition"></div>
<div class="text-center text-neutral-400 mb-2.5 transition">{profileConfig.bio}</div> <div class="text-center text-neutral-400 mb-2.5 transition">{profileConfig.bio}</div>
<div class="flex gap-2 justify-center mb-1"> <div class="flex gap-2 justify-center mb-1">
{profileConfig.links.length > 1 && profileConfig.links.map(item => {profileConfig.links.length > 1 && profileConfig.links.map((item, index) =>
<a rel="me" aria-label={item.name} href={item.url} target="_blank" class="btn-regular rounded-lg h-10 w-10 active:scale-90 flex items-center justify-center"> item.copy ? (
{item.rawIcon ? ( <button
<span class="text-[1.5rem] [&>svg]:w-full [&>svg]:h-full [&>svg]:block" set:html={item.rawIcon}></span> type="button"
) : ( aria-label={`Copy ${item.name}`}
<Icon name={item.icon} class="text-[1.5rem]"></Icon> data-copy-value={item.copy}
)} class="btn-regular rounded-lg h-10 w-10 active:scale-90 flex items-center justify-center copy-button"
</a> >
{item.rawIcon ? (
<span class="text-[1.5rem] [&>svg]:w-full [&>svg]:h-full [&>svg]:block" set:html={item.rawIcon}></span>
) : (
<Icon name={item.icon || ""} class="text-[1.5rem]"></Icon>
)}
</button>
) : (
<a rel="me" aria-label={item.name} href={item.url} target="_blank" class="btn-regular rounded-lg h-10 w-10 active:scale-90 flex items-center justify-center">
{item.rawIcon ? (
<span class="text-[1.5rem] [&>svg]:w-full [&>svg]:h-full [&>svg]:block" set:html={item.rawIcon}></span>
) : (
<Icon name={item.icon || ""} class="text-[1.5rem]"></Icon>
)}
</a>
)
)} )}
{profileConfig.links.length == 1 && <a rel="me" aria-label={profileConfig.links[0].name} href={profileConfig.links[0].url} target="_blank" {profileConfig.links.length == 1 && (
class="btn-regular rounded-lg h-10 gap-2 px-3 font-bold active:scale-95 flex items-center"> profileConfig.links[0].copy ? (
{profileConfig.links[0].rawIcon ? ( <button
<span class="text-[1.5rem] [&>svg]:w-full [&>svg]:h-full [&>svg]:block" set:html={profileConfig.links[0].rawIcon}></span> type="button"
aria-label={`Copy ${profileConfig.links[0].name}`}
data-copy-value={profileConfig.links[0].copy}
class="btn-regular rounded-lg h-10 gap-2 px-3 font-bold active:scale-95 flex items-center copy-button"
>
{profileConfig.links[0].rawIcon ? (
<span class="text-[1.5rem] [&>svg]:w-full [&>svg]:h-full [&>svg]:block" set:html={profileConfig.links[0].rawIcon}></span>
) : (
<Icon name={profileConfig.links[0].icon || ""} class="text-[1.5rem]"></Icon>
)}
{profileConfig.links[0].name}
</button>
) : ( ) : (
<Icon name={profileConfig.links[0].icon} class="text-[1.5rem]"></Icon> <a rel="me" aria-label={profileConfig.links[0].name} href={profileConfig.links[0].url} target="_blank"
)} class="btn-regular rounded-lg h-10 gap-2 px-3 font-bold active:scale-95 flex items-center">
{profileConfig.links[0].name} {profileConfig.links[0].rawIcon ? (
</a>} <span class="text-[1.5rem] [&>svg]:w-full [&>svg]:h-full [&>svg]:block" set:html={profileConfig.links[0].rawIcon}></span>
) : (
<Icon name={profileConfig.links[0].icon || ""} class="text-[1.5rem]"></Icon>
)}
{profileConfig.links[0].name}
</a>
)
)}
</div> </div>
{umamiEnabled && ( {umamiEnabled && (
<hr class="my-2 border-t border-dashed border-gray-300 dark:border-gray-700" /> <hr class="my-2 border-t border-dashed border-gray-300 dark:border-gray-700" />
@@ -75,6 +108,119 @@ const id = `profile-${side}`;
</div> </div>
</div> </div>
<script is:inline>
// Функция для показа уведомления
function showToast(message) {
// Удаляем существующее уведомление, если есть
const existingToast = document.getElementById('copy-toast');
if (existingToast) {
existingToast.remove();
}
// Создаем элемент уведомления
const toast = document.createElement('div');
toast.id = 'copy-toast';
toast.textContent = message;
toast.style.cssText = `
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: var(--tw-prose-bg, #1a1a1a);
color: var(--tw-prose-fg, #fff);
padding: 12px 24px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 10000;
font-size: 14px;
opacity: 0;
transition: opacity 0.3s ease-in-out;
pointer-events: none;
`;
document.body.appendChild(toast);
// Показываем уведомление
requestAnimationFrame(() => {
toast.style.opacity = '1';
});
// Скрываем и удаляем через 2 секунды
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 300);
}, 2000);
}
// Обработка копирования в буфер обмена
function setupCopyButtons() {
const copyButtons = document.querySelectorAll('.copy-button');
copyButtons.forEach(button => {
// Удаляем старые обработчики, если есть
const newButton = button.cloneNode(true);
button.parentNode?.replaceChild(newButton, button);
newButton.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const copyValue = newButton.getAttribute('data-copy-value');
if (!copyValue) return;
try {
await navigator.clipboard.writeText(copyValue);
// Визуальная обратная связь
newButton.classList.add('opacity-50', 'scale-95');
setTimeout(() => {
newButton.classList.remove('opacity-50', 'scale-95');
}, 200);
// Показываем уведомление
showToast('Email скопирован!');
} catch (err) {
console.error('Failed to copy:', err);
// Fallback для старых браузеров
const textArea = document.createElement('textarea');
textArea.value = copyValue;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
textArea.style.pointerEvents = 'none';
textArea.style.left = '-9999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
newButton.classList.add('opacity-50', 'scale-95');
setTimeout(() => {
newButton.classList.remove('opacity-50', 'scale-95');
}, 200);
showToast('Email скопирован!');
}
} catch (fallbackErr) {
console.error('Fallback copy failed:', fallbackErr);
showToast('Не удалось скопировать');
}
document.body.removeChild(textArea);
}
});
});
}
// Инициализация при загрузке страницы
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupCopyButtons);
} else {
setupCopyButtons();
}
// Обработка навигации Astro
document.addEventListener('astro:page-load', setupCopyButtons);
document.addEventListener('astro:after-swap', setupCopyButtons);
</script>
{umamiEnabled && ( {umamiEnabled && (
<script> <script>
import "@/plugins/umami-share"; import "@/plugins/umami-share";

View File

@@ -282,8 +282,10 @@ export type ProfileConfig = {
// 链接配置 // 链接配置
links: { links: {
name: string; name: string;
url: string; url?: string;
icon: string; icon?: string;
rawIcon?: string;
copy?: string;
}[]; }[];
}; };

View File

@@ -137,10 +137,10 @@ profile:
url: "https://t.me/StepanovPlaton" url: "https://t.me/StepanovPlaton"
- name: "HeadHunter" - name: "HeadHunter"
rawIcon: '<svg width="25" height="25" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M400 200C400 310.457 310.457 400 200 400C89.5431 400 0 310.457 0 200C0 89.5431 89.5431 0 200 0C310.457 0 400 89.5431 400 200ZM160.34 161.226C146.98 161.226 136.74 166.526 130.84 176.486V127.126H99.8802V262.406H130.84V214.846C130.84 203.766 133.16 196.666 136.64 192.486C140.02 188.326 144.76 186.706 149.9 186.706C154.44 186.706 158.02 188.126 160.54 190.686C163.06 193.346 164.5 197.406 164.5 203.086V262.286H195.46V197.126C195.46 186.046 192.18 177.046 185.98 170.786C179.88 164.546 171.08 161.226 160.34 161.226ZM299.58 170.886C293.38 164.626 284.58 161.226 273.84 161.226C260.48 161.226 250.24 166.526 244.34 176.486V127.126H213.38V262.406H244.34V214.846C244.34 203.766 246.66 196.666 250.14 192.486C253.52 188.326 258.26 186.706 263.4 186.706C267.94 186.706 271.52 188.126 274.04 190.686C276.56 193.346 278 197.406 278 203.086V262.286H308.96V197.126C308.98 186.046 305.68 177.046 299.58 170.886Z" fill="currentColor"/></svg>' rawIcon: '<svg width="25" height="25" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M400 200C400 310.457 310.457 400 200 400C89.5431 400 0 310.457 0 200C0 89.5431 89.5431 0 200 0C310.457 0 400 89.5431 400 200ZM160.34 161.226C146.98 161.226 136.74 166.526 130.84 176.486V127.126H99.8802V262.406H130.84V214.846C130.84 203.766 133.16 196.666 136.64 192.486C140.02 188.326 144.76 186.706 149.9 186.706C154.44 186.706 158.02 188.126 160.54 190.686C163.06 193.346 164.5 197.406 164.5 203.086V262.286H195.46V197.126C195.46 186.046 192.18 177.046 185.98 170.786C179.88 164.546 171.08 161.226 160.34 161.226ZM299.58 170.886C293.38 164.626 284.58 161.226 273.84 161.226C260.48 161.226 250.24 166.526 244.34 176.486V127.126H213.38V262.406H244.34V214.846C244.34 203.766 246.66 196.666 250.14 192.486C253.52 188.326 258.26 186.706 263.4 186.706C267.94 186.706 271.52 188.126 274.04 190.686C276.56 193.346 278 197.406 278 203.086V262.286H308.96V197.126C308.98 186.046 305.68 177.046 299.58 170.886Z" fill="currentColor"/></svg>'
url: "https://hh.ru/resume/15c78a92ff09c786890039ed1f6f7474704952" url: "/assets/Резюме Степанов Платон.pdf"
- name: "Email" - name: "Email"
icon: "material-symbols:mail-outline" icon: "material-symbols:mail-outline"
url: "mailto:mail2platon@yandex.ru" copy: "mail2platon@yandex.ru"
announcement: announcement:
enable: false enable: false