Files
house-plan-maker/apps/client/src/components/editor/editor-toolbar.module.css
T
alexei.dolgolyov af8b9fe00f feat: complete house plan maker application
Full-featured house/apartment floor plan editor with:

- Turborepo monorepo (React/Vite client, Fastify/Prisma server, shared Zod schemas)
- 2D room editor with walls, doors, windows, furniture, electrical elements
- 3D room preview with Three.js (auto-hide nearest walls, bird's eye default)
- Wall projection views with interactive drag (elevation, position)
- Apartment floor plan view with room positioning
- Copy/paste, alignment tools, measurement tool, annotations
- Item-attached annotations with leader lines (visible on projections)
- Door open direction (LEFT/RIGHT/INWARD/OUTWARD) with swing arc
- Floor type textures (wood, tile, concrete, laminate, herringbone)
- Wall color picker for 3D view
- Furniture: bed, desk, wardrobe, sofa, table, chair, shelf, nightstand, dresser, bookcase, TV (with stand toggle), AC unit
- Furniture elevation support (wall-mounted items)
- Auto-save with dirty state tracking, batch save API
- Rotation-aware collision detection (SAT/OBB) with 3D elevation check
- Rotation-aware hit testing
- i18n (English/Russian) with locale-aware number formatting
- Dark mode with system preference detection
- Undo/redo, keyboard shortcuts, scale bar
- PDF/PNG/JSON export and JSON import
- Focus trap modal, toast notifications, tooltips
- Responsive layout with overlay palettes
2026-04-05 22:34:03 +03:00

168 lines
3.6 KiB
CSS

.toolbar {
display: flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-2) var(--space-3);
background-color: var(--color-bg-elevated);
border-bottom: 1px solid var(--color-border);
min-height: 44px;
flex-shrink: 0;
}
.group {
display: flex;
align-items: center;
gap: var(--space-1);
}
.separator {
width: 1px;
height: 24px;
background-color: var(--color-border);
margin: 0 var(--space-1);
}
.spacer {
flex: 1;
}
.toolBtn {
display: flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-1) var(--space-2);
border: 1px solid transparent;
border-radius: var(--radius-md);
background: none;
cursor: pointer;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
transition: background var(--transition-fast), color var(--transition-fast);
font-family: var(--font-family);
}
.toolBtn:hover {
background-color: var(--color-bg-hover);
color: var(--color-text-primary);
}
.toolBtnActive {
background-color: var(--color-accent-50);
border-color: var(--color-accent-200);
color: var(--color-accent-700);
}
.toolBtnActive:hover {
background-color: var(--color-accent-100);
}
.toolIcon {
font-size: var(--font-size-base);
line-height: 1;
}
.toolLabel {
font-size: var(--font-size-xs);
}
.actionBtn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: none;
border-radius: var(--radius-md);
background: none;
cursor: pointer;
font-size: var(--font-size-md);
color: var(--color-text-secondary);
transition: background var(--transition-fast), color var(--transition-fast);
font-family: var(--font-family);
}
.actionBtn:hover:not(:disabled) {
background-color: var(--color-bg-hover);
color: var(--color-text-primary);
}
.actionBtn:disabled {
opacity: 0.35;
cursor: default;
}
.toggleBtn {
padding: var(--space-1) var(--space-2);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: none;
cursor: pointer;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
transition: all var(--transition-fast);
font-family: var(--font-family);
}
.toggleBtn:hover {
background-color: var(--color-bg-hover);
}
.toggleBtnActive {
background-color: var(--color-accent-50);
border-color: var(--color-accent-200);
color: var(--color-accent-700);
}
.zoomLabel {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
min-width: 40px;
text-align: center;
font-variant-numeric: tabular-nums;
}
.saveBtn {
padding: var(--space-1) var(--space-3);
border: none;
border-radius: var(--radius-md);
background-color: var(--color-accent-600);
color: var(--color-text-on-accent);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
cursor: pointer;
transition: background var(--transition-fast);
font-family: var(--font-family);
}
.saveBtn:hover:not(:disabled) {
background-color: var(--color-accent-700);
}
.saveBtn:disabled {
opacity: 0.6;
cursor: default;
}
.unsavedLabel {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
color: var(--color-warning-600, #ca8a04);
padding: var(--space-1) var(--space-2);
}
.autoSaveLabel {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
padding: var(--space-1) var(--space-2);
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}