4efa71515e
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.
467 lines
8.2 KiB
CSS
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;
|
|
}
|