diff --git a/docs/DESIGN_SYSTEM.md b/docs/DESIGN_SYSTEM.md index 7ba5b3b..8225b7a 100644 --- a/docs/DESIGN_SYSTEM.md +++ b/docs/DESIGN_SYSTEM.md @@ -198,7 +198,9 @@ The canonical tokens live in `src/app.css` under `:root`. | Page background | `--shd-bg` | `#0a0c0d` | | Base surface | `--shd-surface` | `#111415` | | Raised surface | `--shd-surface-raised` | `#141718` | +| Subordinate border | `--shd-border-soft` | `#272c2d` | | Primary border | `--shd-border` | `#303536` | +| Interactive boundary | `--shd-border-strong` | `#424849` | | Main text | `--shd-text` | `#ecebe5` | | Muted text | `--shd-text-muted` | `#858b8d` | | SHD action orange | `--shd-orange` | `#f6a821` | @@ -745,14 +747,21 @@ never silently describe only the visible page. On narrow screens, retain previous, next, current page, and page-size controls before optional page numbers. +When only one page exists, omit page navigation and row-count presets; retain +only the useful result range and any registry search. Do not render disabled +pagination controls simply to preserve the rail's shape. + Registries with a meaningful name, title, or agent-identity column place the -shared server-side search inside this pagination rail. Submission resets the -page number while preserving the selected KPI filter and row-count preset; -page navigation preserves the query. Provide an explicit clear action whenever -a query is active. Search applies to the complete filtered registry before -pagination, including visible pending proposals, and never only to the current -page. Specialized oversight and history searches may remain in their richer -filter consoles when they search evidence beyond a single identity column. +shared server-side search inside this pagination rail. Results update after a +short typing debounce, so do not place a redundant Search button beside the +field. Label the behavior as `LIVE SEARCH`, replace it with `SEARCHING…` while +navigation is pending, and provide an explicit clear action whenever a query is +active. Search resets the page number while preserving the selected KPI filter +and row-count preset; page navigation preserves the query. Search applies to +the complete filtered registry before pagination, including visible pending +proposals, and never only to the current page. Specialized oversight and +history searches may remain in their richer filter consoles when they search +evidence beyond a single identity column. At intermediate widths the search spans the pagination rail above its range and navigation controls. At mobile widths all groups stack, the input retains a @@ -771,10 +780,25 @@ Filtering is server-backed and occurs before pagination. Filter links preserve compatible search and page-size parameters, reset the page number, remain bookmarkable, and expose the selected state with both orange emphasis and `aria-current`. Cards require pointer, hover, focus-visible, pressed, and -textually identifiable active states. Counts describe the full search scope, +textually identifiable active states. Every card explicitly says `FILTER →` +or `SELECTED`; do not rely on KPI-card styling alone to communicate that the +metrics are controls. Counts describe the full search scope, not merely the visible page. Do not use aggregate facts that cannot filter the affected registry—replace them with actionable subsets. +### Registry row interaction + +Desktop registry rows are passive data containers unless the entire row +actually opens a detail view. Do not apply a whole-row hover surface when only +the trailing Edit or View action is interactive. Render that action as a +compact bordered control with hover, focus-visible, and pressed states so its +interaction boundary is unmistakable. + +On mobile, a row may become a full-width detail target when the route provides +an intentional mobile detail interaction. In that case retain the explicit +`TAP TO VIEW` or `TAP TO REVIEW` text, full-row focus treatment, and minimum +touch target; do not infer full-row behavior from desktop hover styling. + ### Image uploads All image uploads use the shared SHD drop-zone treatment and expose three equivalent @@ -1278,9 +1302,14 @@ Chips. Attributes are implementation vocabulary resolved into the facts and effects shown on those records; do not promote Attributes to a public top-level category. -Each index category is a fully interactive square registry card with a live -approved-record count, concise scope statement, visible hover/focus state, and -pressed feedback. A category page uses the shared compact category rail, +Each index category is a fully interactive square registry card with its full +player-facing category name as the first and strongest element. Record count +and availability are supporting metadata in the footer; operational states +such as “active registry” or “intake” must never compete with the category +identity. Use the larger display-weight category name with a short orange +identity rail; render any adjacent count in muted telemetry styling. Cards +retain a concise scope statement, visible hover/focus state, and pressed +feedback. A category page uses the shared compact category rail, live name search, pagination, and a responsive card grid. Record cards use the same hierarchy: @@ -1315,9 +1344,22 @@ narrow layouts use one. The eight-category rail reduces from eight to four to two columns. Preserve keyboard focus, pressed feedback, reduced-motion behavior, and readable textual state in every layout. -Category-specific discovery uses the shared tactical filter matrix. Filters -apply immediately, remain encoded in the URL, reset pagination, and compose -with name search. Multiple selections inside one facet are inclusive (`OR`); +Category-specific discovery uses the shared `DatabaseDiscoveryConsole`. It +joins the tactical filter matrix, live registry search, result range, +pagination, and page-size controls inside one connected instrument; do not +stack separate bordered filter and pagination bars. Categories without facets +retain the same console with search and pagination alone. + +The matrix is a disclosure inside that console. It starts expanded on desktop +and collapsed on viewports at or below 700px when no filters are active. Active +filters make the initial state expanded. Its header always exposes the active +filter count and an explicit `SHOW FILTERS` or `HIDE FILTERS` action, while +search and pagination remain visible when the matrix is closed. Preserve the +user's manual disclosure choice in session storage rather than carrying it +between unrelated browser sessions. + +Filters apply immediately, remain encoded in the URL, reset pagination, and +compose with name search. Multiple selections inside one facet are inclusive (`OR`); active facet groups combine restrictively (`AND`). For example, selecting two Brands and four Talents returns Gear matching any selected Brand and any selected Talent. Never implement multi-select facets as an implicit match-all @@ -1538,4 +1580,28 @@ Pair the short state label with a complete consequence sentence. Contributors must not have to infer this behavior from their role name, data-mode color, or a success message shown after saving. Read-only operators do not see a mutation statement because their mutation controls are absent. At narrow widths the -message becomes its own compact row above the module toolbar. + message becomes its own compact row above the module toolbar. + +### Public database index + +The public Database is a lookup surface first and a contribution gateway +second. Use a compact angular command band rather than a cinematic display +hero: keep the archive identity, state how many registries are online, state the +record total, and present contribution as a bordered secondary action. + +Place one discovery bar directly below the command band. Its label presents +browsing and global search as equal paths—select a registry or search all +data—and its search field searches across all public registry names and useful +descriptive data such as Talent text, Damage Types, item slots, qualities, and +Brand bonus Attributes. Do not stack a separate registry-heading bar above or +below it. Results name both the matching record and its registry, then link into +that registry with the record name applied as its local search. Keep the search +and results dense, keyboard accessible, responsive, and URL-addressable. + +Registry selection begins immediately after the shared discovery bar. Do not +place stewardship, status, promotional, or redundant instructional panels +between it and the registry grid. At wide desktop widths, eight registries form a +four-by-two matrix; the grid reduces to two columns and then one as available +space narrows. Registry cards lead with complete category names. Counts and +availability remain quiet footer metadata, and internal abbreviations or +workflow-centric states are not exposed as primary public labels. diff --git a/src/app.css b/src/app.css index 8e07ab4..d8b1bf3 100644 --- a/src/app.css +++ b/src/app.css @@ -5,6 +5,7 @@ --shd-surface-active:#15191a; --shd-border:#303536; --shd-border-soft:#272c2d; + --shd-border-strong:#424849; --shd-text:#ecebe5; --shd-text-muted:#858b8d; --shd-text-dim:#5d6567; @@ -431,3 +432,363 @@ .mutation-consequence{align-items:flex-start;flex-direction:column;gap:5px} } +/* Public database prioritizes registry lookup over gateway spectacle. */ +.community-index-page{padding-top:20px} +.community-index-hero{ + min-height:138px; + display:grid; + grid-template-columns:minmax(0,1fr) auto; + gap:28px; + align-items:center; + margin-bottom:8px; + padding:18px 24px; +} +.community-index-hero h1{ + margin:7px 0 5px; + font-size:clamp(34px,4vw,56px); + line-height:.9; + letter-spacing:-.025em; +} +.community-index-hero>div>p:last-child{ + max-width:720px; + margin:0; + font-size:var(--shd-type-supporting); + line-height:1.5; +} +.community-index-hero .catalogue-count{ + min-width:230px; + justify-items:end; + text-align:right; +} +.community-index-hero .catalogue-count strong{ + color:var(--shd-text); + font-size:var(--shd-type-supporting); + letter-spacing:.5px; +} +.community-index-hero .catalogue-count span{margin:4px 0 10px} +.community-index-hero .catalogue-count .secondary{ + min-height:36px; + display:grid; + place-items:center; + padding:0 12px; + border:1px solid var(--shd-border); + color:var(--shd-text-muted); + font-size:var(--shd-type-telemetry); + font-weight:900; + letter-spacing:.7px; +} +.community-index-hero .catalogue-count .secondary:hover, +.community-index-hero .catalogue-count .secondary:focus-visible{ + border-color:var(--shd-orange); + color:var(--shd-orange); +} +.community-index-page .database-registry-grid{grid-template-columns:repeat(4,minmax(0,1fr))} +.community-index-page .database-registry-grid>a{min-height:165px} +.community-index-page .database-registry-grid>a>header{ + align-items:center; + gap:12px; + min-height:52px; + padding:6px 10px 10px; + border-bottom:1px solid var(--shd-border); + border-left:2px solid var(--shd-orange); +} +.community-index-page .database-registry-grid h2{ + margin:0; + color:var(--shd-text); + font-size:clamp(20px,1.35vw,24px); + line-height:1; + letter-spacing:.3px; +} +.community-index-page .database-registry-grid header b{ + flex:none; + color:var(--shd-text-muted); + font-size:var(--shd-type-telemetry); + line-height:1; +} +.community-index-page .database-registry-grid p{margin:12px 0} +.community-index-page .database-registry-grid>a>footer{ + display:flex; + align-items:center; + justify-content:space-between; + gap:10px; + margin-top:auto; + padding-top:12px; +} +.community-index-page .database-registry-grid footer small{ + color:var(--shd-text-muted); + font-size:var(--shd-type-telemetry); + letter-spacing:.7px; +} +.community-index-page .database-registry-grid footer span{ + color:var(--shd-orange); + font-size:var(--shd-type-telemetry); + font-weight:900; + letter-spacing:.7px; +} +.database-global-search{ + display:grid; + grid-template-columns:minmax(220px,.45fr) minmax(0,1fr); + align-items:center; + gap:18px; + margin-bottom:8px; + padding:12px; + border:1px solid var(--shd-border); + border-left:2px solid var(--shd-orange); + background:var(--shd-terminal); +} +.database-global-search>div{display:grid;gap:3px} +.database-global-search>div span, +.database-global-search>div strong{ + font-size:var(--shd-type-telemetry); + font-weight:900; + letter-spacing:.8px; +} +.database-global-search>div span{color:var(--shd-orange)} +.database-global-search>div strong{color:var(--shd-text)} +.database-global-search form{ + display:grid; + grid-template-columns:minmax(0,1fr) auto auto; + gap:6px; +} +.database-global-search input{ + min-width:0; + min-height:40px; + padding:0 12px; + border:1px solid var(--shd-border-strong); + background:#090b0c; + color:var(--shd-text); + font:700 var(--shd-type-supporting)/1 var(--shd-font-ui); + letter-spacing:.35px; +} +.database-global-search input:focus-visible{ + outline:1px solid var(--shd-orange); + outline-offset:1px; + border-color:var(--shd-orange); +} +.database-global-search :is(button,a){ + min-height:40px; + display:grid; + place-items:center; + padding:0 14px; + border:1px solid var(--shd-orange); + background:var(--shd-orange); + color:#101010; + font-size:var(--shd-type-telemetry); + font-weight:900; + letter-spacing:.7px; +} +.database-global-search a{ + border-color:var(--shd-border-strong); + background:transparent; + color:var(--shd-text-muted); +} +.database-global-search :is(button,a):hover, +.database-global-search :is(button,a):focus-visible{filter:brightness(1.12)} +.database-global-search :is(button,a):active{transform:translateY(1px);filter:brightness(.86)} +.database-global-results{ + margin-bottom:8px; + border:1px solid var(--shd-border); + background:#0e1112; +} +.database-global-results>header{ + display:flex; + align-items:center; + justify-content:space-between; + gap:16px; + min-height:42px; + padding:7px 12px; + border-bottom:1px solid var(--shd-border); +} +.database-global-results>header>div{display:flex;align-items:center;gap:10px} +.database-global-results>header span, +.database-global-results>header strong, +.database-global-results>header b{ + font-size:var(--shd-type-telemetry); + letter-spacing:.7px; +} +.database-global-results>header span{color:var(--shd-orange);font-weight:900} +.database-global-results>header strong{color:var(--shd-text)} +.database-global-results>header b{color:var(--shd-text-muted)} +.database-global-results>div{ + display:grid; + grid-template-columns:repeat(2,minmax(0,1fr)); +} +.database-global-results>div>a{ + display:grid; + grid-template-columns:110px minmax(0,1fr) minmax(100px,.7fr) auto; + align-items:center; + gap:10px; + min-height:48px; + padding:7px 12px; + border-top:1px solid var(--shd-border); + color:var(--shd-text); +} +.database-global-results>div>a:nth-child(-n+2){border-top:0} +.database-global-results>div>a:nth-child(even){border-left:1px solid var(--shd-border)} +.database-global-results>div>a:hover, +.database-global-results>div>a:focus-visible{background:#171b1c;box-shadow:inset 2px 0 var(--shd-orange)} +.database-global-results>div>a:active{filter:brightness(.86)} +.database-global-results>div small, +.database-global-results>div span, +.database-global-results>div b{font-size:var(--shd-type-telemetry);letter-spacing:.5px} +.database-global-results>div small{color:var(--shd-orange);font-weight:900} +.database-global-results>div strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--shd-type-supporting)} +.database-global-results>div span{overflow:hidden;color:var(--shd-text-muted);text-overflow:ellipsis;white-space:nowrap} +.database-global-results>div b{color:var(--shd-orange)} +.database-global-results>p{margin:0;padding:16px;color:var(--shd-text-muted);font:var(--shd-type-supporting)/1.5 var(--shd-font-editorial)} +.database-category-nav>a{grid-template-columns:minmax(0,1fr) auto} +@media(max-width:1100px){ + .community-index-page .database-registry-grid{grid-template-columns:repeat(2,minmax(0,1fr))} + .database-global-results>div{grid-template-columns:1fr} + .database-global-results>div>a:nth-child(n){border-top:1px solid var(--shd-border);border-left:0} + .database-global-results>div>a:first-child{border-top:0} +} +@media(max-width:750px){ + .community-index-hero{grid-template-columns:1fr;gap:14px;padding:18px} + .community-index-hero .catalogue-count{min-width:0;justify-items:start;text-align:left} + .database-global-search{grid-template-columns:1fr;gap:9px} + .database-global-search form{grid-template-columns:1fr auto} + .database-global-search form input{grid-column:1/-1} + .database-global-results>header{align-items:flex-start} + .database-global-results>div>a{grid-template-columns:minmax(0,1fr) auto} + .database-global-results>div :is(small,span){grid-column:1/-1} + .community-index-page .database-registry-grid{grid-template-columns:1fr} +} + +.database-discovery-console{ + margin:10px 0; + border:1px solid var(--shd-border); + border-left:2px solid var(--shd-orange); + background:#0d1011; +} +.database-discovery-console>.database-facet-panel{ + margin:0; + border:0; + border-left:0; +} +.database-discovery-console>.registry-pagination{ + margin:0; + border:0; + border-top:1px solid var(--shd-border); + background:#101314; +} +.database-discovery-console>.registry-pagination:first-child{border-top:0} +.database-facet-actions{display:flex;align-items:center;gap:5px} +.database-facet-panel>header .database-facet-toggle{ + display:flex; + align-items:center; + gap:8px; + border-color:var(--shd-border-strong); + background:#101314; + color:var(--shd-text); +} +.database-facet-panel>header .database-facet-toggle b{ + min-width:12px; + color:var(--shd-orange); + font-size:15px; + line-height:1; +} +.database-facet-panel>header .database-facet-toggle:hover, +.database-facet-panel>header .database-facet-toggle:focus-visible{ + border-color:var(--shd-orange); + color:var(--shd-orange); +} +.database-facet-panel.collapsed>header{border-bottom:0} +.database-facet-groups[hidden]{display:none} +@media(max-width:700px){ + .database-facet-panel>header{align-items:stretch} + .database-facet-actions{width:100%} + .database-facet-actions>button{flex:1} +} + +/* ISAC interaction polish: distinguish controls from telemetry and preserve readable supporting data. */ +.kpi-filters>a{ + align-content:center; +} +.kpi-filters>a>i{ + position:absolute; + top:50%; + right:9px; + padding:3px 5px; + border:1px solid var(--shd-border-strong); + color:var(--shd-text-muted); + font-size:var(--shd-type-telemetry); + font-style:normal; + font-weight:900; + letter-spacing:.55px; + transform:translateY(-50%); +} +.kpi-filters>a>strong{padding-right:76px} +.kpi-filters>a>small{color:var(--shd-text-muted)} +.kpi-filters>a.active>i{ + border-color:var(--shd-orange); + background:#211a0e; + color:var(--shd-orange); +} +.kpi-filters>a:hover>i, +.kpi-filters>a:focus-visible>i{ + border-color:var(--shd-orange); + color:var(--shd-orange); +} +.pagination-search-state{ + min-width:82px; + height:30px; + display:grid; + place-items:center; + color:var(--shd-text-muted); + font-size:var(--shd-type-telemetry); + font-weight:900; + letter-spacing:.55px; + white-space:nowrap; +} +.pagination-search-state.pending{color:var(--shd-orange)} +.dashboard-row:not(.table-heading):not(.mobile-detail-row):not(.pending-record):hover{background:transparent} +.dashboard-row>button:not(.mobile-row-open), +.dashboard-row>.row-action, +.dashboard-row>.table-action{ + min-height:30px; + display:inline-grid; + place-items:center; + justify-self:end; + padding:0 9px; + border:1px solid #4c3d26; + background:#17140f; + color:var(--shd-orange); + font-size:var(--shd-type-telemetry); + font-weight:900; + letter-spacing:.55px; + cursor:pointer; +} +.dashboard-row>button:not(.mobile-row-open):hover, +.dashboard-row>button:not(.mobile-row-open):focus-visible, +.dashboard-row>.row-action:hover, +.dashboard-row>.row-action:focus-visible, +.dashboard-row>.table-action:hover, +.dashboard-row>.table-action:focus-visible{ + border-color:var(--shd-orange); + background:#211a0e; +} +.dashboard-row>button:not(.mobile-row-open):active, +.dashboard-row>.row-action:active, +.dashboard-row>.table-action:active{ + transform:translateY(1px); + filter:brightness(.86); +} +.module-toolbar>div>p:last-child, +.module-kpis small, +.dashboard-row>div small, +.dashboard-row>span small, +.table-heading, +.pagination-range, +.pagination-sizes>span, +.database-category-command>div:last-child span, +.database-item-card>header small, +.database-item-facts span, +.database-item-lines span, +.database-item-icon-groups>div>span{color:var(--shd-text-muted)} +@media(prefers-reduced-motion:reduce){ + .dashboard-row>button:not(.mobile-row-open):active, + .dashboard-row>.row-action:active, + .dashboard-row>.table-action:active{transform:none} +} + diff --git a/src/lib/components/DatabaseCategoryNav.svelte b/src/lib/components/DatabaseCategoryNav.svelte index 676ea19..d3d5807 100644 --- a/src/lib/components/DatabaseCategoryNav.svelte +++ b/src/lib/components/DatabaseCategoryNav.svelte @@ -3,10 +3,10 @@ let { active = '' }: { active?: string } = $props(); -