Motiva
Мои коллекции
← каталог

Retro Grid

Ретро-сетка: клетчатая плоскость уходит к горизонту и едет на камеру. Одна CSS-перспектива вместо WebGL, бесшовный цикл — сдвиг ровно на клетку.

cssautobasic0reduced-motion ✓
🎛 Настройки — крути и копируй

Пропсы

Разобрано из кода записи: обязательные — без «?», у остальных показано значение по умолчанию.

ПропТипПо умолчаниюЧто это
backdrop?string | falseBACKDROPПодложка (база под сеткой): CSS-значение `background` или `false`. `false` нужен, когда запись стоит фоном СЕКЦИИ и подложку задаёт она.
hue?number300Тон линий сетки, градусы oklch (0–360).
angle?number64Наклон плоскости, градусы (50–78: меньше — площе, больше — «пол под ногами»).
cell?number48Шаг сетки, px.
speed?number1.6Скорость: секунд на одну клетку.
children?ReactNodeСодержимое поверх. В витрине — подпись, в секции обычно пусто.

Установка

npx shadcn@latest add https://motiva.pages.dev/r/retro-grid.json
Что нужно в проекте до установки
  1. components.json в корне — с алиасами в форме"components": "@/components". Нет файла — CLI не поймёт, куда класть.
  2. Алиас @ объявлен дважды: "paths": {"@/*": ["./src/*"]} в корневом tsconfig.json (в Vite он часто содержит только references — тогда CLI создаёт папку@) и resolve.alias в конфиге сборщика.
  3. Токены подключены один раз на приложение:import "@/styles/motiva-tokens.css" — они приезжают вместе с записью. Без них секция потеряет цвета, ритм и тему.
  4. Своя типографика не должна спорить: если у вас на обёрткеtext-align: center, он протечёт внутрь блоков.
  5. Проект без TypeScript? Всё равно нужен минимальныйtsconfig.json с pathsи "tsx": true в components.json: CLI грузит tsconfig безусловно, а с "tsx": false идёт за jsconfig.json и падает так же. Приехавший рядом.tsx можно не трогать — вам нужен.vanilla.js.

Экспорт

↓ Скачать .zip

Код

import type { CSSProperties, ReactNode } from "react";

/** Подложка по умолчанию — как в витрине. */
const BACKDROP = "linear-gradient(180deg, oklch(0.16 0.02 285), oklch(0.13 0.02 285))";

export interface RetroGridProps {
  /**
   * Подложка (база под сеткой): CSS-значение `background` или `false`.
   * `false` нужен, когда запись стоит фоном СЕКЦИИ и подложку задаёт она.
   */
  backdrop?: string | false;
  /** Тон линий сетки, градусы oklch (0–360). */
  hue?: number;
  /** Наклон плоскости, градусы (50–78: меньше — площе, больше — «пол под ногами»). */
  angle?: number;
  /** Шаг сетки, px. */
  cell?: number;
  /** Скорость: секунд на одну клетку. */
  speed?: number;
  /** Содержимое поверх. В витрине — подпись, в секции обычно пусто. */
  children?: ReactNode;
}

/**
 * Ретро-сетка: плоскость с клеткой уходит к горизонту и едет на камеру.
 * Вся «трёхмерность» — одна CSS-перспектива, движение — сдвиг РОВНО на клетку
 * (кадр в конце цикла совпадает с первым, стыка не существует). Чистый CSS, 0 КБ.
 * Reduced-motion / data-shot — сетка стоит, картинка остаётся.
 */
export function RetroGrid({
  backdrop = BACKDROP,
  hue = 300,
  angle = 64,
  cell = 48,
  speed = 1.6,
  children,
}: RetroGridProps = {}) {
  const line = `oklch(0.65 0.14 ${hue} / 0.55)`;
  return (
    <div
      className="rg"
      style={{
        position: "absolute",
        inset: 0,
        overflow: "hidden",
        ...(backdrop === false ? null : { background: backdrop }),
      }}
    >
      <style>{`
        /* размерный контейнер: плоскость меряется в РОДИТЕЛЕ (cqw/cqh), не во вьюпорте —
           контракт слоя BUILDER 3.5 */
        .rg { container-type: size; }
        /* камера: линия горизонта на 35% высоты; растворение у горизонта — маской,
           а не заливкой поверх, чтобы фон секции просвечивал */
        .rg-cam {
          position: absolute; inset: 0;
          perspective: 200px;
          perspective-origin: 50% 35%;
          mask-image: linear-gradient(180deg, transparent 32%, #000 46%);
          -webkit-mask-image: linear-gradient(180deg, transparent 32%, #000 46%);
        }
        .rg-plane {
          position: absolute; left: 50%; top: 35%;
          width: 300cqw; height: 180cqh;
          transform: translateX(-50%) rotateX(var(--rg-angle));
          transform-origin: top center;
        }
        /* едет ВНУТРЕННИЙ бегунок трансформом (композитор), а не background-position
           (перекраска каждый кадр); запас сверху в одну клетку закрывает исходное положение */
        .rg-run {
          position: absolute; inset: calc(-1 * var(--rg-cell)) 0 0 0;
          background:
            repeating-linear-gradient(180deg, var(--rg-line) 0 1px, transparent 1px var(--rg-cell)),
            repeating-linear-gradient(90deg, var(--rg-line) 0 1px, transparent 1px var(--rg-cell));
          animation: rg-move var(--rg-speed) linear infinite;
        }
        @keyframes rg-move { to { transform: translate3d(0, var(--rg-cell), 0); } }
        @media (prefers-reduced-motion: reduce) { .rg-run { animation: none; } }
      `}</style>
      <div
        className="rg-cam"
        style={
          {
            "--rg-angle": `${angle}deg`,
            "--rg-cell": `${cell}px`,
            "--rg-speed": `${speed}s`,
            "--rg-line": line,
          } as CSSProperties
        }
      >
        <div className="rg-plane">
          <div className="rg-run" />
        </div>
      </div>
      {children && (
        <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center" }}>{children}</div>
      )}
    </div>
  );
}
лицензия: MITавтор: Motivaисточник: оригинал (Motiva); идея-референс — Magic UI Retro Grid (MIT)v1.0.0