Перейти к содержимому

Вступление

An illustration of planets and stars featuring the word Astro

Руководства помогают пользователю решить конкретную задачу, которую он хочет выполнить, часто с последовательностью шагов. Для написания хорошего руководства необходимо подумать о том, что пытаются сделать ваши пользователи.

Установка

Окно терминала
pnpm create astro@latest

Global CSS / Tailwind CSS 4

src/styles/global.css
@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

src/layouts/Layout.astro
---
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>