# Design
## Context
См. proposal.md — Why. См. сценарии в specs/ui-workspace/spec.md. Опорные факты по коду:
- `Viewer.svelte` — единая область просмотра: `scroller` c `
` (плоский зум/панорамирование,
`Space`, колесо), топбар, лента миниатюр, контекстное меню, `f`/fullscreen. Используется и в
маршруте фото, и на публичной share-странице.
- Теги персональные (`tags.user_id`); `photo.tags` приходят и в списке, и в `/photos/:id`.
На share-страницах `GetPublic` читает фото с пустым пользователем → тегов нет.
- Варианты: `thumb` 200 и `preview` 1200 (webp); плюс `original_url` (подписанный, 1 час).
- В проекте нет runtime-зависимостей; статика вшивается в Go-бинарник.
## Goals / Non-Goals
**Goals:**
- 360-обзор для фото с личным тегом `pano360`, без сторонних библиотек.
- Сохранить плоский просмотр и все прочие функции Viewer без регрессий.
- Деградация до плоского изображения, когда 360 невозможен.
**Non-Goals:**
- Бэкенд, новый pano-вариант, автоопределение GPano/XMP.
- 360 на share-страницах и у других участников (теги персональные).
- Редактор панорам, горячие точки, стерео/VR.
## Decisions
### 1. Детект: личный тег `pano360`
`isPanoTag(photo)` — `photo.tags?.some(t => t.name.trim().toLowerCase() === 'pano360')`.
Источник — уже отдаваемые теги; бэкенд не трогаем. Следствие: на share и у других участников
режим не включается (осознанное ограничение, покрыто сценарием спеки).
### 2. Рендер: мини-WebGL equirect без зависимостей
Отдельный `PanoViewer.svelte`, владеющий `