Останні новинки postcardware у Spatie: як команда оцифрувала свою стіну з листівками

Перекладено ШІ 0 Spatie 30 липня, 2026

Команда Spatie за один хакатон перетворила свою колекцію листівок на інтерактивний 3D-глобус за допомогою Bun, Astro та React Three Fiber. Дізнайтеся, як розробники використовували AI для прискорення процесів та приборкали GPU-рендеринг для створення плавної візуалізації.

На початку цього року ми в Spatie провели цілий день за розробкою цікавих сайд-проєктів у межах нашого внутрішнього хакатону. Мета була простою: створити щось фанкове та функціональне до кінця дня.
Ми з Ніком (Nick) та Дрісом (Dries) вирішили оцифрувати нашу стіну з листівками — ту саму, де ми збираємо та показуємо всі postcards, які ви нам надсилаєте!

Оскільки сторінка з листівками виглядала дещо нудною і давно не оновлювалася, ми створили Kaartje (/ˈkaːr.tʲə/) — діджитал-версію нашої стіни. Концепція елементарна: це 3D-глобус, що обертається, де кожна листівка розміщена в місті відправника. (Не хвилюйтеся, ми не розкриваємо точні адреси, це лише приблизна геолокація).

Сама ідея була крутою, але ми пішли далі. А що, якби ми могли оцифрувати й сам процес надсилання? Тож ми розробили внутрішній додаток на Expo, який дозволяє сфотографувати листівку та миттєво додати її в систему. Демонстрацію можна переглянути нижче.

Стек технологій

Оскільки ми переважно frontend-розробники, ми обрали знайомі нам інструменти:

  • API на базі Bun-сервера без жодних фреймворків — максимально просто та легко;
  • SQLite як база даних;
  • Astro з React для фронтенду;
  • Expo для нативного мобільного додатка;
  • Спільний пакет (shared package) для фронтенду та Expo з єдиною логікою та UI-елементами.

Усе це ми об'єднали в monorepo та використали npm workspaces для керування структурою. Вийшов дуже лаконічний setup.

Спільний код

Використання React та React Native дозволило нам створити універсальний компонент глобуса. Завдяки react-three-fiber це було легко: ми лише додали кілька перевірок для web-специфічного коду, і глобус «просто запрацював» в Expo-додатку.

Роль ШІ

Проєкт був амбітним для одного дня, тому ми залучили Claude, щоб пришвидшити процес. Ми діяли методично, встановлюючи чіткі межі (guard rails), щоб ШІ не почав «галюцинувати» на середині завдання. Це дозволило зберегти повний контроль над процесом, отримавши максимум продуктивності.

Ми почали з детального опису: вимоги, критерії прийняття, обмеження. Використовуючи специфічні навички на кшталт web-design-guidelines, vercel-react-native-skills та r3f-skills, ми швидко взялися до роботи.
Кожен етап проєкту був ізольованим, щоб ми могли протестувати та перевірити результат. Ми зупиняли допомогу ШІ для валідації коду та аналізу взаємодії між частинами системи.

Оскільки це був внутрішній хакатон, ми не витрачали час на зайву семантику — якщо код працював і виглядав добре, ми рухалися далі. За 4 години ми мали стабільний прототип. Решту часу присвятили ручному поліруванню та оптимізації продуктивності, де Нік продемонстрував справжню магію фронтенду.

Оптимізація рендерингу 3D-глобуса

Рендеринг глобуса з сотнями анімованих карток зазвичай не проблема для сучасного MacBook на M4, але ми помітили мікрофризи. Нашою метою була плавна робота навіть на пристроях середньої потужності.

Перша імплементація була наївною: один THREE.Mesh на кожну листівку. У кожної — власна геометрія, матеріал, текстура та окремий draw call. Це працювало для 10 карток, але на 100 кадри почали «просідати».

Ми застосували GPU instancing. Ця техніка дозволяє сказати відеокарті: «Ось одна фігура, намалюй її 512 разів у різних позиціях, з різними розмірами та текстурами». Браузер не перераховує форму кожного разу, а тримає її в пам'яті.
Тепер усі листівки рендериться як єдиний THREE.InstancedMesh. Одна геометрія, один матеріал, один draw call. Дані про розташування, розмір та текстуру кожної картки передаються як атрибути.

Це вирішило проблему з геометрією, але залишилося питання текстур: кожна листівка має бути унікальною. Ми використали DataArrayTexture — текстуру, що складається з шарів, де кожен шар — окрема листівка. Це як колода карт: форма однакова, але малюнки різні. Shader зчитує індекс шару для кожного екземпляра та відображає потрібне зображення.

Наш клас StampTextureAtlas керує пулом шарів RGBA розміром 128x86 і призначає індекси під час завантаження:

// Кожна марка отримує індекс шару; шейдер обирає потрібний
const layerIndex = atlas.allocateLayer(card.frontImageUrl);

Дрібні, але важливі рішення

Кілька нюансів, які разом зробили проєкт цілісним:

Покрокове завантаження зображень. Сторінка завантажує листівки батчами по 10 штук із паузою у 300 мс. Зображення прелоадяться перед додаванням на глобус. Це запобігає «лавині» запитів, яка могла б «покласти» сервер або спричинити таймаути в браузері.

Refs замість state у мобільному додатку. Процес сканування проходить крізь п'ять екранів. Зазвичай використовують state, але це спричиняє зайві ре-рендери при кожній зміні поля. Ми використали PostcardContext із useRef, що дозволило ділитися даними з нульовою кількістю ре-рендерів. Фінальний екран просто зчитує дані з ref перед відправкою на сервер.

Каскад координат. API спершу намагається отримати GPS-координати з телефону. Якщо доступу немає, використовується центр країни відправника. Якщо і це неможливо — ставиться дефолтна точка нашого офісу в Антверпені. Так кожна листівка знаходить своє місце.

Захист бюджету кадрів. Анімаційний цикл на фронтенді обмежує дельту часу (time delta) максимум 0.1 сек. Якщо користувач перемкне вкладку на пів хвилини і повернеться, глобус не буде шалено крутитися, наздоганяючи час, а продовжить плавний рух.

Чого ми навчилися

Зазвичай ми пишемо CSS та React для створення UI, тож занурення у світ 3D стало ковтком свіжого повітря. Ми обоє цікавимося шейдерами, і робота з Claude як із віртуальним колегою допомогла втілити ідеї в реальність неймовірно швидко.

До цього ми мало знали про GPU instancing, але тепер розуміємо, як він рятує продуктивність. Ми також поглибили знання у WebGL-шейдерах, створюючи ефекти на кшталт «хвилі» при появі картки. Це був крутий досвід виходу за межі звичного робочого середовища.

Для тих, хто хоче подивитися на результат: ми запустили демо-версію на kaartje.earth. Там наразі використовуються тестові листівки, адже ми ще не встигли відзняти всі ваші чудові послання.

Популярні

Інше, що варто прочитати

51 Оновлено 26 червня, 2026

Nuxt 3 + Laravel Sanctum: Просте та надійне рішення для автентифікації вашого SPA та API

У сучасній веб-розробці аутентифікація є ключовою для захисту додатків і даних користувачів. Дізнайтеся, як модуль nuxt-sanctum-authentication спростить інтеграцію між Nuxt 3 та Laravel Sanctum, забезпечуючи надійний і зручний спосіб реалізації аутентифікації для вашого проєкту

42 Оновлено 26 червня, 2026

Що нового в PHP 8.5

PHP 8.5 обіцяє безліч нових можливостей, таких як оператор Pipe, функції `array_first()` та `array_last()`, а також нове розширення URI. Чи готові ви дізнатися, як ці функції можуть спростити вашу розробку? Читайте далі, щоб дізнатися більше про ці захоплюючі нововведення

12 Оновлено 26 червня, 2026

Генерація документації в Laravel за допомогою штучного інтелекту

Docudoodle — це потужний пакет для генерації документації в Laravel, який допомагає легко аналізувати вашу кодову базу та створювати документацію за допомогою обраного вами AI. Чи готові ви дізнатися, як цей інструмент може спростити вашу роботу з документуванням коду? Читайте далі!