Tile palette with previews, click-to-place, independent rotation

Completely reworked feature placement UX:
- Tile palette in sidebar shows 7 pattern types (dead-end, straight,
  wide curve, sharp bend, Y-split, Y-wide, crossroads) for each
  linear terrain (road, river, coastline) with small hex previews
- Click pattern to select, click hex to place — no more edge-clicking
- Q/E keys or scroll wheel to rotate selected pattern before placing
- Each placed feature has independent rotation controls (arrows) and
  remove button in the hex inspector panel
- Edge constraint enforcement still runs automatically on placement

Also added: core/tile-patterns.ts with canonical pattern definitions
and rotation math (accounting for symmetry).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Axel Meyer
2026-04-07 11:09:28 +00:00
parent f144063db9
commit b6bd66cd9e
6 changed files with 562 additions and 94 deletions

89
core/tile-patterns.ts Normal file
View File

@@ -0,0 +1,89 @@
/**
* Canonical tile patterns for linear features.
* Each pattern is defined by a base edge mask and a human-readable name.
* Patterns can be rotated by 60° increments to produce all placements.
*/
import type { EdgeMask } from './types.js';
import { HexEdge } from './types.js';
import { edgeMask, rotateMask, edgeCount } from './edge-connectivity.js';
export interface TilePattern {
id: string;
name: string;
baseMask: EdgeMask;
/** Number of distinct rotations (accounting for symmetry) */
rotations: number;
}
/** All canonical patterns for linear features */
export const TILE_PATTERNS: TilePattern[] = [
{
id: 'dead-end',
name: 'Dead End',
baseMask: edgeMask(HexEdge.E),
rotations: 6,
},
{
id: 'straight',
name: 'Straight',
baseMask: edgeMask(HexEdge.E, HexEdge.W),
rotations: 3, // Symmetric: E-W = same as W-E
},
{
id: 'gentle-curve',
name: 'Wide Curve',
baseMask: edgeMask(HexEdge.E, HexEdge.SW),
rotations: 6,
},
{
id: 'sharp-bend',
name: 'Sharp Bend',
baseMask: edgeMask(HexEdge.E, HexEdge.SE),
rotations: 6,
},
{
id: 'y-junction',
name: 'Y Split',
baseMask: edgeMask(HexEdge.NE, HexEdge.SE, HexEdge.W),
rotations: 6,
},
{
id: 'y-junction-wide',
name: 'Y Wide',
baseMask: edgeMask(HexEdge.NE, HexEdge.SW, HexEdge.SE),
rotations: 6,
},
{
id: 'crossroads',
name: 'Cross',
baseMask: edgeMask(HexEdge.NE, HexEdge.E, HexEdge.SW, HexEdge.W),
rotations: 3,
},
];
/**
* Get all unique rotations for a pattern.
* Returns array of { mask, rotation } where rotation is the number of 60° steps.
*/
export function getPatternRotations(pattern: TilePattern): Array<{ mask: EdgeMask; rotation: number }> {
const seen = new Set<EdgeMask>();
const result: Array<{ mask: EdgeMask; rotation: number }> = [];
for (let r = 0; r < 6; r++) {
const mask = rotateMask(pattern.baseMask, r);
if (!seen.has(mask)) {
seen.add(mask);
result.push({ mask, rotation: r });
}
}
return result;
}
/**
* Rotate a mask by N steps (each step = 60° clockwise).
*/
export function rotatePattern(mask: EdgeMask, steps: number): EdgeMask {
return rotateMask(mask, steps);
}

View File

@@ -7,11 +7,10 @@ import type { AxialCoord, TerrainType } from '../core/types.js';
import { createSidebar } from './ui/sidebar.js';
import { createToolbar, type ToolMode } from './ui/toolbar.js';
import { createTerrainPicker } from './ui/terrain-picker.js';
import { createHexInspector } from './ui/hex-inspector.js';
import { createHexInspector, type FeatureRotateEvent } from './ui/hex-inspector.js';
import { createMapSettings } from './ui/map-settings.js';
import type { SelectedPattern } from './ui/tile-palette.js';
import {
edgeMask,
toggleEdge,
enforceEdgeConstraints,
applyConstraintActions,
} from '../core/edge-connectivity.js';
@@ -21,9 +20,10 @@ const STORAGE_KEY = 'hexifyer_map';
// --- State ---
const hexMap = new HexMap();
let currentMode: ToolMode = 'select';
let selectedTerrain: TerrainType | null = null;
let selectedAreaTerrain: TerrainType | null = null;
let selectedPattern: SelectedPattern | null = null;
let selectedHex: AxialCoord | null = null;
const hexSize = 48; // Fixed per map — set once
const hexSize = 48;
const origin = { x: 0, y: 0 };
let saveTimeout: ReturnType<typeof setTimeout> | null = null;
@@ -44,30 +44,43 @@ hexLayer.addTo(map);
const sidebarEl = document.getElementById('sidebar')!;
const { toolbar, terrainPicker, hexInspector, settings } = createSidebar(sidebarEl);
const terrainPickerUI = createTerrainPicker(terrainPicker, (terrain) => {
selectedTerrain = terrain;
});
const terrainPickerUI = createTerrainPicker(
terrainPicker,
(terrain) => { selectedAreaTerrain = terrain; },
(pattern) => { selectedPattern = pattern; },
);
createToolbar(toolbar, (mode) => {
currentMode = mode;
terrainPickerUI.setMode(mode);
});
const hexInspectorUI = createHexInspector(hexInspector);
const hexInspectorUI = createHexInspector(hexInspector, (event: FeatureRotateEvent) => {
if (event.newMask === 0) {
hexMap.removeFeature(event.coord, event.terrainId);
} else {
hexMap.setFeature(event.coord, event.terrainId, event.newMask);
// Re-enforce constraints after rotation
const actions = enforceEdgeConstraints(hexMap, event.coord, event.terrainId, event.newMask);
applyConstraintActions(hexMap, actions);
}
hexLayer.redraw();
hexInspectorUI.update(event.coord, hexMap.getTerrain(event.coord));
scheduleSave();
});
createMapSettings(settings, { showGrid: true, opacity: 0.7 }, (s) => {
hexLayer.setShowGrid(s.showGrid);
hexLayer.setHexOpacity(s.opacity);
});
// --- Auto-save to localStorage (debounced) ---
// --- Auto-save to localStorage ---
function scheduleSave() {
if (saveTimeout) clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
if (!hexMap.dirty) return;
try {
const data = hexMap.serialize();
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
localStorage.setItem(STORAGE_KEY, JSON.stringify(hexMap.serialize()));
hexMap.markClean();
} catch (err) {
console.error('[save] localStorage failed:', err);
@@ -77,8 +90,7 @@ function scheduleSave() {
// --- File export/import ---
function exportMap() {
const data = hexMap.serialize();
const json = JSON.stringify(data, null, 2);
const json = JSON.stringify(hexMap.serialize(), null, 2);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
@@ -118,7 +130,6 @@ function importMap() {
input.click();
}
// Expose to sidebar buttons
(window as any).__hexifyer = { exportMap, importMap };
// --- Interaction handlers ---
@@ -130,29 +141,24 @@ function handleSelect(event: HexClickEvent) {
}
function handlePaint(event: HexClickEvent) {
if (!selectedTerrain) return;
hexMap.setBase(event.coord, selectedTerrain.id);
if (!selectedAreaTerrain) return;
hexMap.setBase(event.coord, selectedAreaTerrain.id);
hexLayer.redraw();
selectedHex = event.coord;
hexInspectorUI.update(selectedHex, hexMap.getTerrain(selectedHex));
scheduleSave();
}
function handleFeature(event: HexClickEvent) {
if (!selectedTerrain) return;
function handleFeaturePlacement(event: HexClickEvent) {
if (!selectedPattern) return;
const coord = event.coord;
const terrain = hexMap.getTerrain(coord);
const existing = terrain.features.find(f => f.terrainId === selectedTerrain!.id);
const currentMask = existing?.edgeMask ?? 0;
const newMask = toggleEdge(currentMask, event.edge);
hexMap.setFeature(coord, selectedTerrain.id, newMask);
// Place the selected pattern on this hex
hexMap.setFeature(coord, selectedPattern.terrainId, selectedPattern.mask);
if (newMask > currentMask) {
const addedEdgeMask = edgeMask(event.edge);
const actions = enforceEdgeConstraints(hexMap, coord, selectedTerrain.id, addedEdgeMask);
// Enforce edge constraints on neighbors
const actions = enforceEdgeConstraints(hexMap, coord, selectedPattern.terrainId, selectedPattern.mask);
applyConstraintActions(hexMap, actions);
}
selectedHex = coord;
hexLayer.setSelectedHex(selectedHex);
@@ -167,13 +173,34 @@ attachHexInteraction(
(event) => {
if (currentMode === 'select') handleSelect(event);
else if (currentMode === 'paint') handlePaint(event);
else if (currentMode === 'feature') handleFeature(event);
else if (currentMode === 'feature') handleFeaturePlacement(event);
},
(event) => {
if (currentMode === 'paint') handlePaint(event);
},
);
// --- Keyboard rotation (Q/E) ---
document.addEventListener('keydown', (e) => {
if (currentMode !== 'feature') return;
if (e.key === 'q' || e.key === 'Q') {
terrainPickerUI.rotateCCW();
} else if (e.key === 'e' || e.key === 'E') {
terrainPickerUI.rotateCW();
}
});
// --- Mouse wheel rotation ---
document.getElementById('map')?.addEventListener('wheel', (e) => {
if (currentMode !== 'feature' || !selectedPattern) return;
e.preventDefault();
if (e.deltaY > 0) {
terrainPickerUI.rotateCW();
} else {
terrainPickerUI.rotateCCW();
}
}, { passive: false });
// --- Load from localStorage ---
try {
const saved = localStorage.getItem(STORAGE_KEY);
@@ -187,7 +214,6 @@ try {
}
hexMap.markClean();
hexLayer.redraw();
console.log(`[init] Loaded ${data.hexes.length} hexes from localStorage`);
}
} catch (err) {
console.warn('[init] Failed to load from localStorage:', err);

View File

@@ -168,3 +168,108 @@ html, body {
color: #666;
margin: 8px 0 4px 0;
}
/* Tile Palette */
.palette-terrain-header {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
font-weight: 600;
color: #ccc;
margin: 8px 0 4px;
}
.palette-terrain-header .terrain-swatch {
width: 10px;
height: 10px;
border-radius: 2px;
display: inline-block;
}
.palette-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 3px;
margin-bottom: 8px;
}
.palette-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
padding: 4px 2px;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
transition: all 0.1s;
}
.palette-item:hover {
background: #2a2a4a;
border-color: #3a3a6a;
}
.palette-item.selected {
background: #2a2a5a;
border-color: #7a7aff;
}
.palette-item canvas {
display: block;
}
.palette-label {
font-size: 8px;
color: #888;
text-align: center;
line-height: 1.1;
}
.palette-hint {
font-size: 10px;
color: #666;
text-align: center;
padding: 4px;
margin-top: 4px;
border-top: 1px solid #2a2a4a;
}
/* Feature rotation controls */
.feature-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 2px;
}
.rotate-controls {
display: flex;
gap: 2px;
}
.rotate-btn {
width: 22px;
height: 22px;
padding: 0;
border: 1px solid #2a2a4a;
border-radius: 3px;
background: #1a1a2e;
color: #ccc;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.rotate-btn:hover {
background: #2a2a4a;
color: #fff;
}
.rotate-btn.remove-btn:hover {
background: #4a2020;
border-color: #6a3030;
}

View File

@@ -1,6 +1,6 @@
import type { AxialCoord, HexTerrain } from '../../core/types.js';
import { getTerrainType } from '../../core/terrain.js';
import { connectedEdges } from '../../core/edge-connectivity.js';
import { connectedEdges, rotateMask } from '../../core/edge-connectivity.js';
import { HexEdge } from '../../core/types.js';
const EDGE_NAMES: Record<HexEdge, string> = {
@@ -12,39 +12,118 @@ const EDGE_NAMES: Record<HexEdge, string> = {
[HexEdge.NW]: 'NW',
};
export function createHexInspector(container: HTMLElement): {
export interface FeatureRotateEvent {
coord: AxialCoord;
terrainId: string;
newMask: number;
}
export function createHexInspector(
container: HTMLElement,
onRotateFeature?: (event: FeatureRotateEvent) => void,
): {
update: (coord: AxialCoord | null, terrain: HexTerrain | null) => void;
} {
function update(coord: AxialCoord | null, terrain: HexTerrain | null) {
if (!coord || !terrain) {
let currentCoord: AxialCoord | null = null;
let currentTerrain: HexTerrain | null = null;
function render() {
if (!currentCoord || !currentTerrain) {
container.innerHTML = '<div style="color:#666;font-size:12px">No hex selected</div>';
return;
}
const baseType = getTerrainType(terrain.base);
let html = '<div class="hex-info">';
html += `<div class="coord">q: ${coord.q}, r: ${coord.r}</div>`;
html += `<div class="terrain-label">`;
html += `<span class="terrain-swatch" style="background:${baseType?.color ?? '#666'};display:inline-block;width:10px;height:10px;border-radius:2px"></span>`;
html += `${baseType?.name ?? terrain.base}`;
html += `</div>`;
const baseType = getTerrainType(currentTerrain.base);
container.innerHTML = '';
if (terrain.features.length > 0) {
html += '<div style="margin-top:6px;font-size:11px;color:#888">Features:</div>';
for (const feature of terrain.features) {
// Coordinate
const coordEl = document.createElement('div');
coordEl.className = 'coord';
coordEl.textContent = `q: ${currentCoord.q}, r: ${currentCoord.r}`;
container.appendChild(coordEl);
// Base terrain
const baseEl = document.createElement('div');
baseEl.className = 'terrain-label';
baseEl.innerHTML = `<span class="terrain-swatch" style="background:${baseType?.color ?? '#666'};display:inline-block;width:10px;height:10px;border-radius:2px"></span> ${baseType?.name ?? currentTerrain.base}`;
container.appendChild(baseEl);
// Features with rotation controls
if (currentTerrain.features.length > 0) {
const featHeader = document.createElement('div');
featHeader.style.cssText = 'margin-top:6px;font-size:11px;color:#888';
featHeader.textContent = 'Features:';
container.appendChild(featHeader);
for (const feature of currentTerrain.features) {
const type = getTerrainType(feature.terrainId);
const edges = connectedEdges(feature.edgeMask)
.map(e => EDGE_NAMES[e])
.join(', ');
html += `<div class="terrain-label" style="margin-top:2px">`;
html += `<span class="terrain-swatch" style="background:${type?.color ?? '#666'};display:inline-block;width:10px;height:10px;border-radius:2px"></span>`;
html += `${type?.name ?? feature.terrainId}: ${edges}`;
html += `</div>`;
const edges = connectedEdges(feature.edgeMask).map(e => EDGE_NAMES[e]).join(', ');
const row = document.createElement('div');
row.className = 'feature-row';
const label = document.createElement('div');
label.className = 'terrain-label';
label.innerHTML = `<span class="terrain-swatch" style="background:${type?.color ?? '#666'};display:inline-block;width:10px;height:10px;border-radius:2px"></span> ${type?.name ?? feature.terrainId}: ${edges}`;
row.appendChild(label);
if (onRotateFeature) {
const controls = document.createElement('div');
controls.className = 'rotate-controls';
const ccwBtn = document.createElement('button');
ccwBtn.className = 'rotate-btn';
ccwBtn.textContent = '\u21B6'; // ↶
ccwBtn.title = 'Rotate CCW';
ccwBtn.addEventListener('click', () => {
const newMask = rotateMask(feature.edgeMask, 5); // -1 = +5 mod 6
onRotateFeature({
coord: currentCoord!,
terrainId: feature.terrainId,
newMask,
});
});
const cwBtn = document.createElement('button');
cwBtn.className = 'rotate-btn';
cwBtn.textContent = '\u21B7'; // ↷
cwBtn.title = 'Rotate CW';
cwBtn.addEventListener('click', () => {
const newMask = rotateMask(feature.edgeMask, 1);
onRotateFeature({
coord: currentCoord!,
terrainId: feature.terrainId,
newMask,
});
});
const removeBtn = document.createElement('button');
removeBtn.className = 'rotate-btn remove-btn';
removeBtn.textContent = '\u2715'; // ✕
removeBtn.title = 'Remove';
removeBtn.addEventListener('click', () => {
onRotateFeature({
coord: currentCoord!,
terrainId: feature.terrainId,
newMask: 0, // 0 = remove
});
});
controls.appendChild(ccwBtn);
controls.appendChild(cwBtn);
controls.appendChild(removeBtn);
row.appendChild(controls);
}
container.appendChild(row);
}
}
}
html += '</div>';
container.innerHTML = html;
function update(coord: AxialCoord | null, terrain: HexTerrain | null) {
currentCoord = coord;
currentTerrain = terrain;
render();
}
update(null, null);

View File

@@ -1,16 +1,21 @@
import type { TerrainType } from '../../core/types.js';
import { getAreaTerrains, getLinearTerrains } from '../../core/terrain.js';
import { getAreaTerrains } from '../../core/terrain.js';
import type { ToolMode } from './toolbar.js';
import { createTilePalette, type SelectedPattern } from './tile-palette.js';
export function createTerrainPicker(
container: HTMLElement,
onChange: (terrain: TerrainType) => void,
onAreaSelect: (terrain: TerrainType) => void,
onPatternSelect: (pattern: SelectedPattern | null) => void,
): {
setMode: (mode: ToolMode) => void;
getSelected: () => TerrainType | null;
getSelectedArea: () => TerrainType | null;
rotateCW: () => void;
rotateCCW: () => void;
} {
let selected: TerrainType | null = null;
let selectedArea: TerrainType | null = null;
let currentMode: ToolMode = 'select';
let palette: ReturnType<typeof createTilePalette> | null = null;
function render() {
container.innerHTML = '';
@@ -20,12 +25,12 @@ export function createTerrainPicker(
return;
}
const terrains = currentMode === 'paint' ? getAreaTerrains() : getLinearTerrains();
const label = currentMode === 'paint' ? 'Area Terrain' : 'Linear Features';
if (currentMode === 'paint') {
const terrains = getAreaTerrains();
const sectionLabel = document.createElement('div');
sectionLabel.className = 'terrain-section-label';
sectionLabel.textContent = label;
sectionLabel.textContent = 'Area Terrain';
container.appendChild(sectionLabel);
const grid = document.createElement('div');
@@ -34,7 +39,7 @@ export function createTerrainPicker(
for (const terrain of terrains) {
const btn = document.createElement('button');
btn.className = 'terrain-btn';
if (selected?.id === terrain.id) btn.classList.add('selected');
if (selectedArea?.id === terrain.id) btn.classList.add('selected');
const swatch = document.createElement('span');
swatch.className = 'terrain-swatch';
@@ -47,8 +52,8 @@ export function createTerrainPicker(
btn.appendChild(name);
btn.addEventListener('click', () => {
selected = terrain;
onChange(terrain);
selectedArea = terrain;
onAreaSelect(terrain);
render();
});
@@ -57,13 +62,22 @@ export function createTerrainPicker(
container.appendChild(grid);
// Auto-select first if nothing selected
if (!selected || !terrains.find(t => t.id === selected!.id)) {
selected = terrains[0] ?? null;
if (selected) onChange(selected);
// Re-render to show selection
const firstBtn = grid.querySelector('.terrain-btn');
firstBtn?.classList.add('selected');
// Auto-select first
if (!selectedArea || !terrains.find(t => t.id === selectedArea!.id)) {
selectedArea = terrains[0] ?? null;
if (selectedArea) onAreaSelect(selectedArea);
grid.querySelector('.terrain-btn')?.classList.add('selected');
}
return;
}
if (currentMode === 'feature') {
// Tile palette with previews
const paletteContainer = document.createElement('div');
container.appendChild(paletteContainer);
palette = createTilePalette(paletteContainer, onPatternSelect);
return;
}
}
@@ -72,9 +86,12 @@ export function createTerrainPicker(
return {
setMode(mode: ToolMode) {
currentMode = mode;
selected = null;
selectedArea = null;
palette = null;
render();
},
getSelected: () => selected,
getSelectedArea: () => selectedArea,
rotateCW() { palette?.rotateCW(); },
rotateCCW() { palette?.rotateCCW(); },
};
}

152
src/ui/tile-palette.ts Normal file
View File

@@ -0,0 +1,152 @@
import type { TerrainType, EdgeMask } from '../../core/types.js';
import { getLinearTerrains, getTerrainType } from '../../core/terrain.js';
import { TILE_PATTERNS, rotatePattern } from '../../core/tile-patterns.js';
import { computeHexGeometry } from '../../core/coords.js';
import { renderHex } from '../svg/renderer.js';
export interface SelectedPattern {
terrainId: string;
mask: EdgeMask;
rotation: number;
patternId: string;
}
export function createTilePalette(
container: HTMLElement,
onSelect: (pattern: SelectedPattern | null) => void,
): {
getSelected: () => SelectedPattern | null;
rotateCW: () => void;
rotateCCW: () => void;
} {
let selected: SelectedPattern | null = null;
let currentTerrainId: string | null = null;
function render() {
container.innerHTML = '';
const linearTerrains = getLinearTerrains();
for (const terrain of linearTerrains) {
// Terrain header
const header = document.createElement('div');
header.className = 'palette-terrain-header';
header.innerHTML = `<span class="terrain-swatch" style="background:${terrain.color}"></span>${terrain.name}`;
container.appendChild(header);
// Pattern grid
const grid = document.createElement('div');
grid.className = 'palette-grid';
for (const pattern of TILE_PATTERNS) {
const item = document.createElement('div');
item.className = 'palette-item';
const isSelected = selected?.terrainId === terrain.id &&
selected?.patternId === pattern.id;
if (isSelected) item.classList.add('selected');
// Render preview hex
const canvas = document.createElement('canvas');
const previewSize = 18;
canvas.width = previewSize * 2 + 4;
canvas.height = previewSize * 2 + 4;
const mask = isSelected && selected
? rotatePattern(pattern.baseMask, selected.rotation)
: pattern.baseMask;
renderPreview(canvas, terrain, mask, previewSize);
item.appendChild(canvas);
// Label
const label = document.createElement('span');
label.className = 'palette-label';
label.textContent = pattern.name;
item.appendChild(label);
item.addEventListener('click', () => {
if (isSelected) {
// Deselect
selected = null;
currentTerrainId = null;
} else {
selected = {
terrainId: terrain.id,
mask: pattern.baseMask,
rotation: 0,
patternId: pattern.id,
};
currentTerrainId = terrain.id;
}
onSelect(selected);
render();
});
grid.appendChild(item);
}
container.appendChild(grid);
}
// Rotation hint if something is selected
if (selected) {
const hint = document.createElement('div');
hint.className = 'palette-hint';
hint.textContent = 'Q/E or scroll to rotate before placing';
container.appendChild(hint);
}
}
function rotateCW() {
if (!selected) return;
selected.rotation = (selected.rotation + 1) % 6;
selected.mask = rotatePattern(
TILE_PATTERNS.find(p => p.id === selected!.patternId)!.baseMask,
selected.rotation,
);
onSelect(selected);
render();
}
function rotateCCW() {
if (!selected) return;
selected.rotation = (selected.rotation + 5) % 6; // +5 = -1 mod 6
selected.mask = rotatePattern(
TILE_PATTERNS.find(p => p.id === selected!.patternId)!.baseMask,
selected.rotation,
);
onSelect(selected);
render();
}
render();
return {
getSelected: () => selected,
rotateCW,
rotateCCW,
};
}
function renderPreview(
canvas: HTMLCanvasElement,
terrain: TerrainType,
mask: EdgeMask,
size: number,
): void {
const ctx = canvas.getContext('2d')!;
const cx = canvas.width / 2;
const cy = canvas.height / 2;
const geom = computeHexGeometry(cx, cy, size);
renderHex(ctx, geom, {
base: 'plains',
features: [{ terrainId: terrain.id, edgeMask: mask }],
}, {
opacity: 0.9,
showGrid: true,
selected: false,
});
}