This commit is contained in:
parent
ef5f977f8a
commit
d7871736c8
10 changed files with 566 additions and 8 deletions
87
AGENTS.md
Normal file
87
AGENTS.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# Agent Instructions
|
||||
|
||||
Sokko G est une webapp React + Vite + Sass centrée sur des toolboxes locales et des pages de jeu.
|
||||
|
||||
## Références projet
|
||||
|
||||
Avant de modifier un outil toolbox, le stockage IndexedDB, l'import/export ou les formats de données, lire :
|
||||
|
||||
- `docs/STORAGE_SCHEMA.md`
|
||||
- `docs/FEATURE_CHECKLIST.md`
|
||||
|
||||
Après ce type de modification, mettre à jour ces documents si le format ou le workflow change.
|
||||
|
||||
## Contenu éditable
|
||||
|
||||
Le contenu éditorial du site doit rester dans :
|
||||
|
||||
- `website/public/data/site.json`
|
||||
- `website/public/data/<gameId>/...`
|
||||
|
||||
Ne pas réintroduire de fallback massif type `DEFAULT_SITE_CONTENT` dans le code React. `site.json` est la source de vérité et `npm run check` doit échouer si le contenu requis est invalide.
|
||||
|
||||
## Outils toolbox
|
||||
|
||||
Chaque outil toolbox doit rester dans son propre fichier dans :
|
||||
|
||||
```text
|
||||
website/src/features/toolboxes/modules/
|
||||
```
|
||||
|
||||
Quand un outil est ajouté ou modifié :
|
||||
|
||||
- déclarer l'outil dans `website/src/features/toolboxes/modules/index.jsx` ;
|
||||
- ajouter les textes dans `website/public/data/site.json` ;
|
||||
- mettre à jour la validation dans `tests/helpers/data-validation.mjs` ;
|
||||
- mettre à jour la structure IndexedDB dans `docs/STORAGE_SCHEMA.md` si nécessaire ;
|
||||
- vérifier l'affichage page toolbox et panneau latéral.
|
||||
|
||||
## Style
|
||||
|
||||
Le style est en Sass dans `website/src/styles/`.
|
||||
|
||||
Privilégier :
|
||||
|
||||
- les tokens existants ;
|
||||
- les classes de boutons existantes ;
|
||||
- les patterns visuels déjà présents pour les cards, panels, badges, scrollbars et hovers.
|
||||
|
||||
Éviter les styles isolés qui ne réutilisent pas le thème.
|
||||
|
||||
## Validation
|
||||
|
||||
Avant de terminer une modification significative, lancer :
|
||||
|
||||
```bash
|
||||
npm run check
|
||||
```
|
||||
|
||||
Le check couvre la génération des index de listes, les tests et le build de production.
|
||||
|
||||
## Pré-push check
|
||||
|
||||
Quand l'utilisateur indique qu'il va push, faire une passe globale avant de conclure.
|
||||
|
||||
Vérifier et corriger si nécessaire :
|
||||
|
||||
- code mort, imports inutilisés, fonctions inutilisées, styles devenus obsolètes ;
|
||||
- duplication de code ou de style pouvant être factorisée sans complexifier le projet ;
|
||||
- composants qui utilisent des classes trop spécifiques alors qu'un pattern général existe déjà ;
|
||||
- incohérences entre boutons, badges, cards, panels, hovers, focus states et scrollbars ;
|
||||
- cohérence entre `website/public/data/site.json`, les validations et les textes utilisés ;
|
||||
- cohérence entre `docs/STORAGE_SCHEMA.md` et les normalisations IndexedDB réelles ;
|
||||
- import/export des toolboxes quand la structure de données a changé ;
|
||||
- champs inutiles persistés dans IndexedDB ou dans les exports ;
|
||||
- fichiers générés attendus, notamment les index de listes ;
|
||||
- optimisations simples de performance : recalculs au render, listeners non nettoyés, observers, effets CSS lourds appliqués en masse.
|
||||
|
||||
Utiliser en priorité :
|
||||
|
||||
```bash
|
||||
rg
|
||||
git status --short
|
||||
git diff --stat
|
||||
npm run check
|
||||
```
|
||||
|
||||
Ne pas faire de refactor large sans bénéfice clair. Garder les corrections pré-push ciblées, vérifiables et faciles à relire.
|
||||
60
docs/FEATURE_CHECKLIST.md
Normal file
60
docs/FEATURE_CHECKLIST.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# Feature Checklist
|
||||
|
||||
Checklist à suivre lors de l'ajout ou de la modification d'un outil, d'une page jeu ou d'un format de données.
|
||||
|
||||
## Outil Toolbox
|
||||
|
||||
- Ajouter ou modifier le composant dans `website/src/features/toolboxes/modules/`.
|
||||
- Déclarer l'outil dans `website/src/features/toolboxes/modules/index.jsx`.
|
||||
- Ajouter les textes nécessaires dans `website/public/data/site.json`.
|
||||
- Mettre à jour la validation de `site.json` dans `tests/helpers/data-validation.mjs`.
|
||||
- Mettre à jour la normalisation et le stockage compact dans `website/src/main.jsx`.
|
||||
- Mettre à jour `docs/STORAGE_SCHEMA.md`.
|
||||
- Vérifier l'import et l'export si l'outil stocke des données.
|
||||
- Vérifier l'affichage dans la page toolbox complète.
|
||||
- Vérifier l'affichage dans le panneau latéral.
|
||||
- Vérifier le mode une colonne et deux colonnes.
|
||||
- Vérifier le quota de stockage si l'outil manipule des données lourdes.
|
||||
|
||||
## Page Jeu
|
||||
|
||||
- Ajouter les données dans `website/public/data/`.
|
||||
- Ajouter les images dans `website/public/static/img/games/<gameId>/`.
|
||||
- Déclarer le jeu dans `website/public/data/site.json`.
|
||||
- Vérifier la page `/games`.
|
||||
- Vérifier la page `/games/<gameId>`.
|
||||
- Vérifier le bouton toolbox et le panneau latéral.
|
||||
- Vérifier le breadcrumb.
|
||||
- Vérifier les textes avec retours à la ligne.
|
||||
|
||||
## Listes de jeu
|
||||
|
||||
- Ajouter la liste JSON dans `website/public/data/<gameId>/lists/`.
|
||||
- Lancer `npm run lists:index`.
|
||||
- Vérifier que `index.json` est régénéré.
|
||||
- Vérifier que `nom` est présent sur chaque élément.
|
||||
- Vérifier que `description` et `quantite` restent facultatifs.
|
||||
- Vérifier la copie au format checklist.
|
||||
- Vérifier la création directe de checklist quand une toolbox est associée.
|
||||
|
||||
## Style
|
||||
|
||||
- Réutiliser les tokens existants avant d'ajouter une nouvelle couleur ou taille.
|
||||
- Réutiliser les classes de boutons existantes.
|
||||
- Vérifier les boutons icon, danger, primary et secondary.
|
||||
- Vérifier les cartes, panels et scrollbars.
|
||||
- Vérifier les états hover/focus.
|
||||
- Vérifier mobile et desktop quand le changement touche la mise en page.
|
||||
|
||||
## Validation avant push
|
||||
|
||||
```bash
|
||||
npm run check
|
||||
```
|
||||
|
||||
Le check doit passer avant de pousser :
|
||||
|
||||
- syntaxe serveur et Vite ;
|
||||
- génération des index de listes ;
|
||||
- tests Node ;
|
||||
- build de production.
|
||||
313
docs/STORAGE_SCHEMA.md
Normal file
313
docs/STORAGE_SCHEMA.md
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
# Storage Schema
|
||||
|
||||
Ce document décrit les données persistées dans IndexedDB pour les toolboxes.
|
||||
Il doit être mis à jour à chaque ajout ou modification d'outil.
|
||||
|
||||
## IndexedDB
|
||||
|
||||
Base : `sokkog`
|
||||
|
||||
Object stores :
|
||||
|
||||
- `kv` : données globales simples.
|
||||
- `modules` : données des outils, séparées par toolbox et par module.
|
||||
|
||||
Clés principales dans `kv` :
|
||||
|
||||
```json
|
||||
{
|
||||
"toolboxes": [],
|
||||
"links": {},
|
||||
"setting:toolboxOrder": [],
|
||||
"setting:storageBudgetBytes": 536870912
|
||||
}
|
||||
```
|
||||
|
||||
Entrée type dans `modules` :
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "tbx1:mod1",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
## Toolbox
|
||||
|
||||
Les toolboxes sont stockées dans `kv/toolboxes`.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "tbx1",
|
||||
"name": "Monster Hunter Wilds",
|
||||
"gameId": "mhwilds",
|
||||
"icon": "/static/img/toolbox-icons/toolbox.png",
|
||||
"modules": [
|
||||
{
|
||||
"id": "mod1",
|
||||
"type": "checklist",
|
||||
"title": "Armures"
|
||||
}
|
||||
],
|
||||
"layout": "two",
|
||||
"updatedAt": "2026-07-25T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
Notes :
|
||||
|
||||
- `gameId` est vide pour une toolbox libre.
|
||||
- `icon` est utilisé uniquement pour les toolboxes libres.
|
||||
- `layout` vaut généralement `one` ou `two`.
|
||||
- Les données lourdes des outils ne sont pas stockées dans la toolbox, mais dans `modules`.
|
||||
|
||||
## Liens Jeu / Toolbox
|
||||
|
||||
Les associations entre pages jeux et toolboxes sont stockées dans `kv/links`.
|
||||
|
||||
```json
|
||||
{
|
||||
"mhwilds": "tbx1",
|
||||
"diablo4": "tbx2"
|
||||
}
|
||||
```
|
||||
|
||||
## Outil Bloc Notes
|
||||
|
||||
Type : `notepad`
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "Notes rapides..."
|
||||
}
|
||||
```
|
||||
|
||||
Stockage compact :
|
||||
|
||||
- si `text` est vide, l'entrée de module peut être supprimée.
|
||||
|
||||
## Outil Checklist
|
||||
|
||||
Type : `checklist`
|
||||
|
||||
Format simple, sans catégorie :
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "item1",
|
||||
"label": "Potion",
|
||||
"qtyTarget": 10,
|
||||
"qtyCurrent": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Format avec catégories :
|
||||
|
||||
```json
|
||||
{
|
||||
"hideCompletedSections": true,
|
||||
"hideCompletedSectionsFully": false,
|
||||
"sections": [
|
||||
{
|
||||
"id": "section1",
|
||||
"title": "Rey Dau alpha",
|
||||
"hideWhenComplete": false,
|
||||
"collapsed": false,
|
||||
"items": [
|
||||
{
|
||||
"id": "item1",
|
||||
"label": "Casque",
|
||||
"qtyTarget": 1,
|
||||
"qtyCurrent": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Stockage compact :
|
||||
|
||||
- `qtyTarget` est omis si la valeur vaut `1`.
|
||||
- `qtyCurrent` est omis si la valeur vaut `0`.
|
||||
- `hideCompletedSections` est omis si `false`.
|
||||
- `hideCompletedSectionsFully` est omis si `false`.
|
||||
- `title` est omis si vide.
|
||||
- `hideWhenComplete` est omis si non défini.
|
||||
- `collapsed` est omis si `false`.
|
||||
|
||||
## Outil Images
|
||||
|
||||
Type : `screenshots`
|
||||
|
||||
```json
|
||||
{
|
||||
"shots": [
|
||||
{
|
||||
"id": "shot1",
|
||||
"label": "Map zone nord",
|
||||
"dataUrl": "data:image/webp;base64,..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Stockage compact :
|
||||
|
||||
- `label` est facultatif.
|
||||
- `label` est omis si vide.
|
||||
- `createdAt` n'est pas stocké.
|
||||
- Le nom de fichier original n'est pas stocké.
|
||||
|
||||
## Outil Annotation d'images
|
||||
|
||||
Type : `imageAnnotation`
|
||||
|
||||
```json
|
||||
{
|
||||
"image": "data:image/webp;base64,...",
|
||||
"markers": [
|
||||
{
|
||||
"id": "marker1",
|
||||
"x": 42.5,
|
||||
"y": 68,
|
||||
"label": "Entrée"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Stockage compact :
|
||||
|
||||
- un seul visuel par outil.
|
||||
- `x` et `y` sont des pourcentages entre `0` et `100`.
|
||||
- `label` est facultatif.
|
||||
- `label` est omis si vide.
|
||||
|
||||
## Outil Liens
|
||||
|
||||
Type : `links`
|
||||
|
||||
```json
|
||||
{
|
||||
"links": [
|
||||
{
|
||||
"id": "link1",
|
||||
"title": "Build rogue",
|
||||
"url": "https://example.com/build"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Stockage compact :
|
||||
|
||||
- `title` est facultatif.
|
||||
- `title` est omis si vide.
|
||||
- `url` est obligatoire.
|
||||
|
||||
## Outil Compteurs
|
||||
|
||||
Type : `counters`
|
||||
|
||||
```json
|
||||
{
|
||||
"counters": [
|
||||
{
|
||||
"id": "counter1",
|
||||
"label": "Victoire",
|
||||
"value": 12
|
||||
},
|
||||
{
|
||||
"id": "counter2",
|
||||
"label": "Défaite",
|
||||
"value": -2
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Notes :
|
||||
|
||||
- `value` est numérique.
|
||||
- Les valeurs négatives sont acceptées.
|
||||
- Les compteurs sans libellé ne sont pas conservés.
|
||||
|
||||
## Outil Calculateur
|
||||
|
||||
Type : `calculator`
|
||||
|
||||
```json
|
||||
{
|
||||
"scrollResults": true,
|
||||
"entries": [
|
||||
{
|
||||
"id": "calc1",
|
||||
"label": "Lingots de fer",
|
||||
"value": 100
|
||||
},
|
||||
{
|
||||
"id": "calc2",
|
||||
"parentId": "calc1",
|
||||
"label": "Minerais de fer",
|
||||
"value": 500
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Stockage compact :
|
||||
|
||||
- `scrollResults` est omis si `false`.
|
||||
- `parentId` est omis si vide.
|
||||
- `label` peut être vide.
|
||||
- `value` est numérique.
|
||||
- Si un `parentId` ne pointe vers aucune entrée existante, il est réinitialisé à vide.
|
||||
|
||||
## Export
|
||||
|
||||
L'export d'une toolbox remappe les identifiants pour produire des IDs courts et indépendants.
|
||||
|
||||
Exemple :
|
||||
|
||||
```json
|
||||
{
|
||||
"toolbox": {
|
||||
"id": "t1",
|
||||
"name": "MHW",
|
||||
"modules": [
|
||||
{
|
||||
"id": "m1",
|
||||
"type": "checklist",
|
||||
"title": "Armures"
|
||||
}
|
||||
],
|
||||
"updatedAt": "2026-07-25T12:00:00.000Z"
|
||||
},
|
||||
"modules": {
|
||||
"m1": {
|
||||
"items": [
|
||||
{
|
||||
"id": "i1",
|
||||
"label": "Casque"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## A maintenir à chaque update
|
||||
|
||||
Quand un outil change de structure :
|
||||
|
||||
- mettre à jour ce document ;
|
||||
- mettre à jour les normalisations dans `website/src/main.jsx` ;
|
||||
- mettre à jour les textes dans `website/public/data/site.json` si nécessaire ;
|
||||
- mettre à jour la validation dans `tests/helpers/data-validation.mjs` si nécessaire ;
|
||||
- vérifier import/export ;
|
||||
- lancer `npm run check`.
|
||||
|
|
@ -106,6 +106,8 @@ export function validateSiteContent(site) {
|
|||
"toolboxes.modules.screenshots.pastePlaceholder",
|
||||
"toolboxes.modules.screenshots.pasteAriaLabel",
|
||||
"toolboxes.modules.screenshots.imageAlt",
|
||||
"toolboxes.modules.screenshots.labelPlaceholder",
|
||||
"toolboxes.modules.screenshots.labelAriaLabel",
|
||||
"toolboxes.modules.screenshots.previewAriaLabel",
|
||||
"toolboxes.modules.screenshots.annotateAriaLabel",
|
||||
"toolboxes.modules.screenshots.annotateTitle",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,19 @@ test("vite entrypoint loads the react application", async () => {
|
|||
assert.match(html, /\/src\/main\.jsx/);
|
||||
});
|
||||
|
||||
test("project documentation tracks storage and feature updates", async () => {
|
||||
const storageSchema = await readFile("docs/STORAGE_SCHEMA.md", "utf8");
|
||||
const featureChecklist = await readFile("docs/FEATURE_CHECKLIST.md", "utf8");
|
||||
|
||||
assert.match(storageSchema, /IndexedDB/);
|
||||
assert.match(storageSchema, /Outil Checklist/);
|
||||
assert.match(storageSchema, /Outil Images/);
|
||||
assert.match(storageSchema, /Outil Annotation d'images/);
|
||||
assert.match(storageSchema, /Outil Calculateur/);
|
||||
assert.match(featureChecklist, /Mettre à jour `docs\/STORAGE_SCHEMA\.md`/);
|
||||
assert.match(featureChecklist, /npm run check/);
|
||||
});
|
||||
|
||||
test("react application defines the expected local toolbox primitives", async () => {
|
||||
const source = await readFile("website/src/main.jsx", "utf8");
|
||||
const styles = await readFile("website/src/styles/main.scss", "utf8");
|
||||
|
|
|
|||
|
|
@ -224,6 +224,8 @@
|
|||
"pastePlaceholder": "Coller une image ici",
|
||||
"pasteAriaLabel": "Coller une image depuis le presse-papiers",
|
||||
"imageAlt": "Image",
|
||||
"labelPlaceholder": "Libellé de l'image",
|
||||
"labelAriaLabel": "Libellé de l'image",
|
||||
"previewAriaLabel": "Agrandir l'image",
|
||||
"annotateAriaLabel": "Annoter l'image",
|
||||
"annotateTitle": "Annoter",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ export function ScreenshotsModule({ toolboxId, moduleId, context, editing }) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateShot(shotId, patch) {
|
||||
context.setModuleData(toolboxId, moduleId, {
|
||||
shots: data.shots.map((shot) => shot.id === shotId ? { ...shot, ...patch } : shot)
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{editing && (
|
||||
|
|
@ -66,6 +72,17 @@ export function ScreenshotsModule({ toolboxId, moduleId, context, editing }) {
|
|||
<button className="shot-preview" onClick={() => context.setScreenshot(shot)} aria-label={textContent.previewAriaLabel || "Agrandir l'image"}>
|
||||
<img src={shot.dataUrl} alt={textContent.imageAlt || "Image"} />
|
||||
</button>
|
||||
{editing ? (
|
||||
<input
|
||||
className="shot-label-input"
|
||||
value={shot.label || ""}
|
||||
placeholder={textContent.labelPlaceholder || "Libellé de l'image"}
|
||||
onChange={(event) => updateShot(shot.id, { label: event.target.value })}
|
||||
aria-label={textContent.labelAriaLabel || "Libellé de l'image"}
|
||||
/>
|
||||
) : shot.label ? (
|
||||
<figcaption className="shot-label">{shot.label}</figcaption>
|
||||
) : null}
|
||||
<div className="shot-actions">
|
||||
<button
|
||||
className="shot-annotate-button"
|
||||
|
|
|
|||
|
|
@ -241,6 +241,11 @@ function normalizeCalculatorValue(value) {
|
|||
return Number.isFinite(parsed) ? parsed : 0;
|
||||
}
|
||||
|
||||
function labelFromFileName(name) {
|
||||
if (!name || name === "image.png") return "";
|
||||
return name.replace(/\.[^.]+$/, "").trim();
|
||||
}
|
||||
|
||||
function normalizeCalculatorData(data) {
|
||||
const entries = (data?.entries || [])
|
||||
.map((entry) => ({
|
||||
|
|
@ -313,7 +318,11 @@ function compactModuleDataForStorage(type, value) {
|
|||
if (type === "screenshots") {
|
||||
const shots = (Array.isArray(value?.shots) ? value.shots : [])
|
||||
.filter((shot) => shot?.dataUrl)
|
||||
.map((shot) => ({ id: shot.id || uid("shot"), dataUrl: shot.dataUrl }));
|
||||
.map((shot) => {
|
||||
const compact = { id: shot.id || uid("shot"), dataUrl: shot.dataUrl };
|
||||
if (shot.label) compact.label = shot.label;
|
||||
return compact;
|
||||
});
|
||||
return shots.length ? { shots } : null;
|
||||
}
|
||||
if (type === "imageAnnotation") {
|
||||
|
|
@ -798,7 +807,10 @@ function App() {
|
|||
if (!imageFiles.length) return false;
|
||||
const data = store.getModuleData(toolboxId, moduleId, { shots: [] });
|
||||
for (const file of imageFiles) {
|
||||
data.shots.unshift({ id: uid("shot"), dataUrl: await compressImage(file) });
|
||||
const shot = { id: uid("shot"), dataUrl: await compressImage(file) };
|
||||
const label = labelFromFileName(file.name);
|
||||
if (label) shot.label = label;
|
||||
data.shots.unshift(shot);
|
||||
}
|
||||
return store.updateModuleData(toolboxId, moduleId, data);
|
||||
}
|
||||
|
|
@ -1812,10 +1824,13 @@ function ScreenshotViewer({ shot, onClose }) {
|
|||
<div className="screenshot-viewer-backdrop" onClick={onClose} />
|
||||
<section ref={viewerRef} className="screenshot-viewer" role="dialog" aria-modal="true" aria-label="Screenshot">
|
||||
<header>
|
||||
{shot.label && <strong className="screenshot-viewer-title">{shot.label}</strong>}
|
||||
<div className="screenshot-viewer-actions">
|
||||
{!canAnnotate && !hasMarkers && (
|
||||
<button className="screenshot-viewer-button" onClick={() => openImageInNewTab(shot.dataUrl)} aria-label="Ouvrir l'image en taille réelle" title="Taille réelle"><Icon name="zoom" /></button>
|
||||
)}
|
||||
<button className="screenshot-viewer-button" onClick={onClose} aria-label="Fermer" title="Fermer"><Icon name="close" /></button>
|
||||
</div>
|
||||
</header>
|
||||
<div className={`screenshot-viewer-body ${canAnnotate ? "has-annotation-side" : ""}`}>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -31,8 +31,22 @@
|
|||
}
|
||||
|
||||
.screenshot-viewer header {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.screenshot-viewer-title {
|
||||
overflow: hidden;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-md);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.screenshot-viewer-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1710,6 +1710,8 @@ textarea:focus {
|
|||
|
||||
.shots figure {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
|
|
@ -1732,6 +1734,40 @@ textarea:focus {
|
|||
box-shadow: 0 0 0 2px rgba(246, 196, 83, 0.28);
|
||||
}
|
||||
|
||||
.shot-label,
|
||||
.shot-label-input {
|
||||
min-width: 0;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.shot-label {
|
||||
overflow: hidden;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.shot-label-input {
|
||||
width: 100%;
|
||||
min-height: 36px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid rgba(138, 150, 197, 0.2);
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(8, 13, 30, 0.62);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.shot-label-input::placeholder {
|
||||
color: rgba(185, 194, 215, 0.48);
|
||||
}
|
||||
|
||||
.shot-label-input:focus {
|
||||
border-color: rgba(246, 196, 83, 0.72);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(246, 196, 83, 0.14);
|
||||
}
|
||||
|
||||
.shots img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
|
@ -1744,7 +1780,6 @@ textarea:focus {
|
|||
.shot-actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.shot-actions .shot-annotate-button,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue