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
This commit is contained in:
2026-04-05 22:34:03 +03:00
parent b84807bbdb
commit af8b9fe00f
188 changed files with 35795 additions and 0 deletions
@@ -0,0 +1,133 @@
.panel {
width: 260px;
min-width: 260px;
background-color: var(--color-bg-elevated);
border-left: 1px solid var(--color-border);
overflow-y: auto;
flex-shrink: 0;
}
.header {
padding: var(--space-3) var(--space-4);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
border-bottom: 1px solid var(--color-border);
letter-spacing: var(--letter-spacing-wide);
text-transform: uppercase;
}
.empty {
padding: var(--space-6) var(--space-4);
text-align: center;
}
.emptyText {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
margin-bottom: var(--space-1);
}
.emptyHint {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
}
.section {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border);
}
.sectionTitle {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
color: var(--color-text-muted);
text-transform: uppercase;
letter-spacing: var(--letter-spacing-wide);
margin-bottom: var(--space-2);
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-1) 0;
min-height: 28px;
}
.rowLabel {
font-size: var(--font-size-xs);
color: var(--color-text-secondary);
flex-shrink: 0;
}
.rowValue {
font-size: var(--font-size-xs);
color: var(--color-text-primary);
font-variant-numeric: tabular-nums;
text-align: right;
}
.editableValue {
font-size: var(--font-size-xs);
color: var(--color-text-primary);
font-variant-numeric: tabular-nums;
text-align: right;
background: none;
border: none;
padding: var(--space-1) var(--space-1);
border-radius: var(--radius-sm);
cursor: pointer;
font-family: var(--font-family);
transition: background var(--transition-fast);
}
.editableValue:hover {
background-color: var(--color-bg-hover);
}
.editRow {
display: flex;
align-items: center;
gap: var(--space-1);
}
.editInput {
width: 60px;
padding: 2px var(--space-1);
border: 1px solid var(--color-accent-300);
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-family: var(--font-mono);
color: var(--color-text-primary);
text-align: right;
outline: none;
background-color: var(--color-bg-elevated);
}
.editInput:focus {
border-color: var(--color-accent-500);
box-shadow: 0 0 0 2px var(--color-focus-ring);
}
.editUnit {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
}
.selectInput {
padding: 2px var(--space-1);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-family: var(--font-family);
color: var(--color-text-primary);
background-color: var(--color-bg-elevated);
cursor: pointer;
outline: none;
}
.selectInput:focus {
border-color: var(--color-accent-500);
box-shadow: 0 0 0 2px var(--color-focus-ring);
}