Вступление

Руководства помогают пользователю решить конкретную задачу, которую он хочет выполнить, часто с последовательностью шагов. Для написания хорошего руководства необходимо подумать о том, что пытаются сделать ваши пользователи.
Установка
pnpm create astro@latest
Global CSS / Tailwind CSS 4
@import "tailwindcss";@plugin "@tailwindcss/typography";
@font-face { font-family: 'FubonVF'; src: url('/fonts/FubonVF.woff') format('woff'); font-weight: normal; font-style: normal; font-display: swap;}
:root { --base-padding-x: max(5vw, 40px); --base-padding-y: clamp(16px, 4vw, 50px);
--base-logo: clamp(30px, 4vw, 50px);
body { @apply py-[var(--base-padding-y)]; }}
@theme { --font-sans: 'FubonVF', sans-serif;
/* COLORS */ --color-brand: 46 97% 65%; --color-background: var(--color-white); --color-foreground: 222.2 84% 4.9%; --color-primary: var(--color-blue-600); --color-error: var(--color-red-500);
/* CONTAINER */ --breakpoint-*: initial; /* 640 */ --breakpoint-mobile: 40rem; /* 1024 */ --breakpoint-tablet: 64rem; /* 1280 */ --breakpoint-laptop: 80rem; /* 1536 */ --breakpoint-desktop: 96rem;
/* ANIMATION */ --animate-motion-top-open: motion-top-open 0.3s cubic-bezier(0.22, 0.61, 0.36, 1) forwards; --animate-motion-top-close: motion-top-close 0.3s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
@keyframes motion-top-open { 0% { opacity: 0; transform: translateY(12%) translateX(0); }
100% { opacity: 1; transform: translateY(0) translateX(0); } }
@keyframes motion-top-close { 0% { opacity: 1; transform: translateY(0) translateX(0); }
100% { opacity: 0; transform: translateY(12%) translateX(0); } }
--animate-motion-left-open: motion-left-open 0.3s ease-out forwards; --animate-motion-left-close: motion-left-close 0.3s ease-out forwards;
@keyframes motion-left-open { 0% {
transform: rotate(5deg); }
100% {
transform: rotate(0deg); } }
@keyframes motion-left-close { 0% {
transform: rotate(0deg); }
100% {
transform: rotate(5deg); } }
--animate-motion-right-open: motion-right-open 0.3s ease-out forwards; --animate-motion-right-close: motion-right-close 0.3s ease-out forwards;
@keyframes motion-right-open { 0% {
transform: rotate(-5deg); }
100% {
transform: rotate(0deg); } }
@keyframes motion-right-close { 0% {
transform: rotate(0deg); }
100% {
transform: rotate(-5deg); } }
/* Rotate */ --animate-motion-rotate: motion-rotate 0.3s ease-out;
@keyframes motion-rotate { 0% { transform: rotate(10deg);
}
100% { transform: rotate(0deg);
} }
--animate-motion-fade-open: motion-fade-open 0.3s ease-out forwards; --animate-motion-fade-close: motion-fade-close 0.3s ease-out forwards;
@keyframes motion-fade-open { 0% { opacity: 0; }
100% { opacity: 1; } }
@keyframes motion-fade-close { 0% { opacity: 1; }
100% { opacity: 0; } }}
@utility container { @apply max-w-full px-4 mobile:px-[var(--base-padding-x)];}
@utility header { @apply container flex items-center justify-between sticky top-[var(--base-padding-y)] z-50}
@utility logo { @apply text-foreground text-3xl tablet:text-4xl laptop:text-5xl;}
Layout
---import Footer from '../components/Footer.astro'import Head from '../components/Head.astro'import Header from '../components/Header.astro'
interface Props { title?: string | undefined description?: string | undefined ogImage?: URL | undefined}
const { title, description, ogImage } = Astro.props---
<!doctype html><html lang="ru"> <head> <Head title={title} description={description} ogImage={ogImage} />
<style> .custom-cursor { position: fixed; width: 30px; height: 30px; border-radius: 50%; background-color: rgba(209, 203, 191, 0.55); border: 1px solid rgba(255, 255, 255, 0.07); backdrop-filter: blur(10px); pointer-events: none; transform: translate(-50%, -50%); transition: opacity 0.3s ease, width 0.3s, height 0.3s, background-color 0.3s; z-index: 1000; display: flex; /* Должен быть видим изначально, но с opacity */ align-items: center; justify-content: center; color: #fff; line-height: 22px; font-size: 16px; opacity: 0; /* Начальное состояние (скрыто) */ }
.hover-cursor { cursor: none !important; /* Убираем системный курсор при наведении */ }
.cursor-hover { opacity: 1; /* Плавно появляется */ width: 96px; height: 96px; background-color: rgba(209, 203, 191, 0.55); border: 1px solid rgba(255, 255, 255, 0.07); }
.custom-cursor svg { width: 12px; /* Исходный маленький размер */ height: 12px; transition: transform 0.3s ease; /* Плавный переход для иконки */ }
.cursor-hover svg { transform: scale(6); /* Увеличить иконку */ } </style> </head>
<body class="text-foreground min-h-screen bg-zinc-100 antialiased"> <div class="relative flex min-h-screen flex-col"> <Header />
<main class="flex-auto"> <slot /> </main>
<Footer /> </div>
<div class="custom-cursor"> <span class="cursor-text"> <svg class="stroke-[0.75]" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"> <path d="M7 7h10v10"></path> <path d="M7 17 17 7"></path> </svg> </span> </div>
<script> document.addEventListener('DOMContentLoaded', function () { const cursor = document.querySelector('.custom-cursor') as HTMLElement const hoverElements = document.querySelectorAll('.hover-cursor')
if (cursor) { document.addEventListener('mousemove', (e) => { cursor.style.top = `${e.clientY}px` cursor.style.left = `${e.clientX}px` })
hoverElements.forEach((element) => { element.addEventListener('mouseenter', () => { cursor.classList.add('cursor-hover') // Плавное появление })
element.addEventListener('mouseleave', () => { cursor.classList.remove('cursor-hover') // Плавное исчезновение }) }) } else { console.error('Cursor element not found!') } }) </script> </body></html>