Files
PubScript/apps/web/src/styles/entities.css
T
Schwitzz 4efa71515e feat(entity): Chunk #4 data layer + entity UI (characters & world-building)
Data layer:
- Fuzzy scorer (packages/core/src/fuzzy.ts) with index recovery + unit tests
- Backlink computation (packages/core/src/backlinks.ts) incl. entity-level backlinks
- Consistency checker (packages/core/src/consistency.ts) incl. reciprocity/scope + unit tests
- StorageAdapter contract extended with listEntities/searchEntities/saveEntity/deleteEntity;
  implemented in Local (Dexie) and Server adapters; contract tests cover entity CRUD + ?q= search
- WorldItem.fields now supports list values (keyPeople, rivals); expanded RelationshipKind
- @mention autocomplete extension + picker in the editor package

Entity UI (apps/web/src/features/entities/):
- Generic field renderer (EntityFields) driven by ENTITY_FIELDS/FieldDef
- Tabbed directory with fuzzy search + entity cards (EntitiesView)
- Create/edit/delete drawer (EntityEditor) reusing shared fields
- Relationship editor + force-directed SVG relationship map
- Entity engine stylesheet; wired into nav

Store: project-store entity methods (load/create/update/delete) over the StorageAdapter contract.

CI-green: typecheck, lint, 65 tests, and web build all pass.
2026-10-25 22:42:55 -05:00

467 lines
8.2 KiB
CSS

/**
* Entity engine styles (apps/web).
*
* Styles for the Chunk #4 entity engine UI: the tabbed directory, the fuzzy
* search bar, the entity card grid, the create/edit drawer, the relationship
* editor, and the force-directed relationship map. These sit on top of the
* `@pubscript/ui` component kit (AppShell.css) and reuse its design tokens
* (`--ps-*`) and field/button/drawer/tabs/badge classes.
*/
/* Full-width container for the entity engine (overrides .ps-content narrowness). */
.ps-entities-view {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.ps-entities-view > .ps-tabs {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* ---- Search bar + new-entity actions ---- */
.ps-entity-search {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.ps-entity-search-input {
flex: 1;
min-width: 240px;
}
.ps-entity-new-group {
display: flex;
gap: 8px;
}
/* ---- Entity card grid ---- */
.ps-entity-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 14px;
padding-bottom: 96px;
}
.ps-entity-card {
text-align: left;
border: 1px solid var(--ps-border);
border-radius: 10px;
padding: 14px 16px;
background: var(--ps-surface-2);
cursor: pointer;
transition: border-color 0.15s, transform 0.1s, box-shadow 0.15s;
display: flex;
flex-direction: column;
gap: 8px;
font-family: var(--ps-font-ui);
}
.ps-entity-card:hover {
border-color: var(--ps-accent);
transform: translateY(-2px);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
}
.ps-entity-card-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.ps-entity-type-badge {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 2px 8px;
border-radius: 999px;
background: color-mix(in srgb, var(--ps-accent) 16%, transparent);
color: var(--ps-accent);
}
.ps-type-character {
background: color-mix(in srgb, var(--ps-accent) 16%, transparent);
color: var(--ps-accent);
}
.ps-type-worldItem {
background: color-mix(in srgb, #8a5cf5 16%, transparent);
color: #8a5cf5;
}
.ps-entity-card-name {
font-size: 16px;
font-weight: 600;
color: var(--ps-text);
margin: 0;
word-break: break-word;
}
.ps-entity-card-sub {
font-size: 13px;
color: var(--ps-text-muted);
margin: 0;
}
.ps-entity-card-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 2px;
}
.ps-entity-tag {
font-size: 11px;
padding: 1px 7px;
border-radius: 999px;
background: var(--ps-surface);
color: var(--ps-text-muted);
border: 1px solid var(--ps-border);
}
/* ---- Relationship map ---- */
.ps-entity-map {
padding: 8px;
display: flex;
justify-content: center;
}
.ps-relationship-map {
width: 100%;
max-width: 640px;
height: auto;
border: 1px solid var(--ps-border);
border-radius: 10px;
background: var(--ps-surface-2);
}
.ps-relationship-empty {
display: flex;
align-items: center;
justify-content: center;
height: 420px;
color: var(--ps-text-muted);
font-size: 15px;
}
.ps-relationship-node {
transition: transform 0.1s;
}
.ps-relationship-label {
font-family: var(--ps-font-ui);
}
/* ---- Entity editor drawer ---- */
.ps-entity-editor {
display: flex;
flex-direction: column;
gap: 8px;
}
.ps-entity-editor-actions {
display: flex;
align-items: center;
gap: 8px;
margin-top: 12px;
padding-top: 16px;
border-top: 1px solid var(--ps-border);
}
.ps-spacer {
flex: 1;
}
.ps-entity-delete {
margin-right: auto;
}
/* ---- Generic entity fields ---- */
.ps-entity-fields {
display: flex;
flex-direction: column;
}
.ps-field-span-2 {
grid-column: span 2;
}
.ps-textarea {
resize: vertical;
min-height: 60px;
font-family: var(--ps-font-ui);
line-height: 1.5;
}
.ps-field-checkbox {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: var(--ps-text);
}
/* ---- Tag field ---- */
.ps-tag-field {
display: flex;
flex-direction: column;
gap: 6px;
}
.ps-tag-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.ps-tag-chip {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
padding: 2px 6px 2px 10px;
border-radius: 999px;
background: var(--ps-surface);
border: 1px solid var(--ps-border);
color: var(--ps-text);
}
.ps-tag-remove {
border: none;
background: none;
cursor: pointer;
color: var(--ps-text-muted);
font-size: 15px;
line-height: 1;
padding: 0 2px;
}
.ps-tag-remove:hover {
color: var(--ps-danger);
}
/* ---- Relationship editor ---- */
.ps-relationship-editor {
margin-top: 8px;
padding-top: 16px;
border-top: 1px solid var(--ps-border);
}
.ps-relationship-list {
list-style: none;
margin: 8px 0 12px;
padding: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.ps-relationship-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
background: var(--ps-surface-2);
border: 1px solid var(--ps-border);
border-radius: 8px;
}
.ps-relationship-kind {
font-size: 11px;
font-weight: 600;
text-transform: capitalize;
padding: 2px 8px;
border-radius: 999px;
background: color-mix(in srgb, var(--ps-accent) 16%, transparent);
color: var(--ps-accent);
}
.ps-relationship-target {
flex: 1;
font-size: 14px;
color: var(--ps-text);
}
.ps-relationship-remove {
border: none;
background: none;
cursor: pointer;
color: var(--ps-text-muted);
font-size: 18px;
line-height: 1;
padding: 0 4px;
}
.ps-relationship-remove:hover {
color: var(--ps-danger);
}
.ps-relationship-add {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.ps-relationship-target-select,
.ps-relationship-kind-select {
flex: 1;
min-width: 120px;
}
.ps-relationship-note {
flex: 2;
min-width: 120px;
}
/* ---- Misc ---- */
.ps-muted {
color: var(--ps-text-muted);
font-size: 14px;
}
/* ---- Toolbar + tabs ---- */
.ps-entity-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.ps-entity-tabs {
display: flex;
gap: 4px;
border-bottom: 1px solid var(--ps-border);
padding-bottom: -1px;
}
.ps-entity-tab {
background: none;
border: none;
padding: 8px 12px;
font-size: 14px;
font-family: var(--ps-font-ui);
color: var(--ps-text-muted);
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
transition: color 0.15s, border-color 0.15s;
}
.ps-entity-tab:hover {
color: var(--ps-text);
}
.ps-entity-tab.active {
color: var(--ps-text);
border-bottom-color: var(--ps-accent);
font-weight: 600;
}
.ps-entity-new-kind {
min-width: 120px;
}
/* ---- Entity editor drawer header/body ---- */
.ps-entity-editor-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
}
.ps-entity-editor-header h2 {
font-size: 16px;
font-weight: 600;
margin: 0;
color: var(--ps-text);
}
.ps-entity-editor-body {
display: flex;
flex-direction: column;
gap: 16px;
overflow-y: auto;
padding: 4px 2px;
}
.ps-icon-btn {
border: none;
background: none;
font-size: 22px;
line-height: 1;
cursor: pointer;
color: var(--ps-text-muted);
padding: 0 4px;
}
.ps-icon-btn:hover {
color: var(--ps-text);
}
/* ---- Relationship section + map ---- */
.ps-relationship-section {
margin-top: 8px;
padding-top: 16px;
border-top: 1px solid var(--ps-border);
}
.ps-relationship-section h3 {
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
color: var(--ps-text-muted);
margin: 0 0 8px;
}
.ps-relationship-map-wrap {
margin-top: 12px;
display: flex;
justify-content: center;
}
.ps-relationship-svg {
width: 100%;
max-width: 520px;
height: auto;
}
.ps-relationship-map-empty {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
color: var(--ps-text-muted);
font-size: 14px;
}
.ps-rel-edge {
stroke: var(--ps-border);
stroke-width: 1.5;
}
.ps-rel-node {
stroke: var(--ps-surface);
stroke-width: 2;
transition: transform 0.1s;
}
.ps-rel-node-label {
font-family: var(--ps-font-ui);
font-size: 10px;
fill: var(--ps-text-muted);
pointer-events: none;
}