prototype: explore public model status layouts
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { dev } from '$app/environment';
|
||||
import { page } from '$app/state';
|
||||
import ModelStatusPrototype from './ModelStatusPrototype.prototype.svelte';
|
||||
import type { PrototypeVariant } from './model-status.prototype';
|
||||
|
||||
let { data } = $props();
|
||||
const onlineRegistries = $derived(data.registries.filter((registry) => registry.count > 0).length);
|
||||
const prototypeVariant = $derived.by<PrototypeVariant | null>(() => {
|
||||
if (!dev || page.url.searchParams.get('prototype') !== 'model-status') return null;
|
||||
const variant = page.url.searchParams.get('variant');
|
||||
return variant === 'A' || variant === 'B' || variant === 'C' ? variant : null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -8,6 +18,9 @@
|
||||
<meta name="description" content="Community-maintained The Division Resurgence attributes, talents, brands, gear, weapons, mods, protocols, skill chips, and specializations data."/>
|
||||
</svelte:head>
|
||||
|
||||
{#if prototypeVariant}
|
||||
<ModelStatusPrototype variant={prototypeVariant} />
|
||||
{:else}
|
||||
<main class="page community-index-page">
|
||||
<header class="catalogue-hero community-index-hero">
|
||||
<div><p class="eyebrow"><span></span> ISAC PUBLIC ARCHIVE</p><h1>RESURGENCE <em>DATABASE.</em></h1><p>Explore equipment, talents, modifications, and gameplay effects from The Division Resurgence.</p></div>
|
||||
@@ -54,3 +67,4 @@
|
||||
{/each}
|
||||
</section>
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import ModelStatusPrototypeSwitcher from './ModelStatusPrototypeSwitcher.prototype.svelte';
|
||||
import ModelStatusVariantA from './ModelStatusVariantA.prototype.svelte';
|
||||
import ModelStatusVariantB from './ModelStatusVariantB.prototype.svelte';
|
||||
import ModelStatusVariantC from './ModelStatusVariantC.prototype.svelte';
|
||||
import type { PrototypeVariant } from './model-status.prototype';
|
||||
|
||||
let { variant }: { variant: PrototypeVariant } = $props();
|
||||
</script>
|
||||
|
||||
<!-- THROWAWAY PROTOTYPE: three issue #5 variants on the existing /database route. -->
|
||||
<main class="page model-status-prototype">
|
||||
{#if variant === 'A'}
|
||||
<ModelStatusVariantA />
|
||||
{:else if variant === 'B'}
|
||||
<ModelStatusVariantB />
|
||||
{:else}
|
||||
<ModelStatusVariantC />
|
||||
{/if}
|
||||
</main>
|
||||
<ModelStatusPrototypeSwitcher current={variant} />
|
||||
@@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { onMount } from 'svelte';
|
||||
import { prototypeVariantNames, type PrototypeVariant } from './model-status.prototype';
|
||||
|
||||
let { current }: { current: PrototypeVariant } = $props();
|
||||
const variants: PrototypeVariant[] = ['A', 'B', 'C'];
|
||||
|
||||
const cycle = (direction: -1 | 1) => {
|
||||
const currentIndex = variants.indexOf(current);
|
||||
const next = variants[(currentIndex + direction + variants.length) % variants.length];
|
||||
const params = new URLSearchParams(page.url.searchParams);
|
||||
params.set('prototype', 'model-status');
|
||||
params.set('variant', next);
|
||||
void goto(`${page.url.pathname}?${params.toString()}${page.url.hash}`, {
|
||||
keepFocus: true,
|
||||
noScroll: true,
|
||||
replaceState: true
|
||||
});
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
|
||||
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
||||
if (event.target instanceof HTMLElement && event.target.closest('input, textarea, select, [contenteditable]:not([contenteditable="false"])')) return;
|
||||
event.preventDefault();
|
||||
cycle(event.key === 'ArrowLeft' ? -1 : 1);
|
||||
};
|
||||
window.addEventListener('keydown', handleKeydown);
|
||||
return () => window.removeEventListener('keydown', handleKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- THROWAWAY PROTOTYPE: development-only variant switcher for issue #5. -->
|
||||
<nav class="prototype-switcher" aria-label="Model status prototype variants">
|
||||
<button type="button" onclick={() => cycle(-1)} aria-label="Show previous prototype variant">←</button>
|
||||
<div aria-live="polite"><small>THROWAWAY PROTOTYPE</small><strong>VARIANT {current}</strong><span>{prototypeVariantNames[current]}</span></div>
|
||||
<button type="button" onclick={() => cycle(1)} aria-label="Show next prototype variant">→</button>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.prototype-switcher{position:fixed;z-index:1000;left:50%;bottom:18px;transform:translateX(-50%);min-width:min(470px,calc(100vw - 24px));display:grid;grid-template-columns:48px minmax(0,1fr) 48px;border:1px solid var(--shd-border-strong);box-shadow:0 12px 38px #000c;background:var(--shd-bg);color:var(--shd-text)}.prototype-switcher button{display:grid;place-items:center;border:0;background:var(--shd-surface-raised);color:var(--shd-orange);font:800 22px/1 var(--shd-font-mono);cursor:pointer}.prototype-switcher button:first-child{border-right:1px solid var(--shd-border)}.prototype-switcher button:last-child{border-left:1px solid var(--shd-border)}.prototype-switcher button:hover{background:var(--shd-surface-active);color:var(--shd-orange-hover)}.prototype-switcher button:active{transform:translateY(1px);filter:brightness(.82)}.prototype-switcher button:focus-visible{position:relative;z-index:1;outline:2px solid var(--shd-focus-ring);outline-offset:2px}.prototype-switcher>div{display:grid;grid-template-columns:auto 1fr;align-items:center;column-gap:10px;padding:8px 12px;text-align:left}.prototype-switcher small{grid-column:1/-1;color:var(--shd-text-dim);font-size:9px;font-weight:900;letter-spacing:1px}.prototype-switcher strong{color:var(--shd-orange);font-size:var(--shd-type-telemetry);letter-spacing:.8px}.prototype-switcher span{overflow:hidden;color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);text-overflow:ellipsis;white-space:nowrap}@media(max-width:520px){.prototype-switcher{bottom:10px;grid-template-columns:44px minmax(0,1fr) 44px}.prototype-switcher>div{grid-template-columns:1fr;gap:2px}.prototype-switcher small{display:none}}@media(prefers-reduced-motion:reduce){.prototype-switcher button{transition:none}}
|
||||
</style>
|
||||
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
modelStatusFixtures,
|
||||
publicBehavior,
|
||||
publicStatus
|
||||
} from './model-status.prototype';
|
||||
|
||||
const quickViewFixtures = [modelStatusFixtures[0], modelStatusFixtures[3]];
|
||||
</script>
|
||||
|
||||
<!-- THROWAWAY PROTOTYPE: issue #5, variant A. -->
|
||||
<div class="prototype-a">
|
||||
<header class="prototype-heading">
|
||||
<div>
|
||||
<p><span></span> DEVELOPMENT PROTOTYPE · ISSUE #5 · VARIANT A</p>
|
||||
<h1>MODELING <em>STATUS.</em></h1>
|
||||
</div>
|
||||
<p>Integrated card hierarchy keeps the public state in the identity header and gives behavior one primary reading block.</p>
|
||||
</header>
|
||||
|
||||
<section aria-labelledby="a-card-heading">
|
||||
<header class="surface-heading">
|
||||
<div><span>01 / CARD PRESENTATION</span><h2 id="a-card-heading">DATABASE CARDS</h2></div>
|
||||
<small>ALL MECHANIC OWNERS · PUBLIC STATE ONLY</small>
|
||||
</header>
|
||||
<div class="card-grid">
|
||||
{#each modelStatusFixtures as fixture}
|
||||
<article class="model-card">
|
||||
<header>
|
||||
<div><small>{fixture.owner}</small><h3>{fixture.name}</h3><span>{fixture.context}</span></div>
|
||||
<strong class:not-modeled={publicStatus(fixture) === 'NOT MODELED'}>{publicStatus(fixture)}</strong>
|
||||
</header>
|
||||
<div class="behavior-block">
|
||||
<small>{publicStatus(fixture) === 'MODELED' ? 'MODELED BEHAVIOR' : 'AUTHORED DESCRIPTION'}</small>
|
||||
<p>{publicBehavior(fixture)}</p>
|
||||
</div>
|
||||
<footer><span>READ-ONLY FIXTURE</span><b>VIEW DETAILS →</b></footer>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="a-detail-heading">
|
||||
<header class="surface-heading">
|
||||
<div><span>02 / DETAIL PRESENTATION</span><h2 id="a-detail-heading">QUICK VIEW</h2></div>
|
||||
<small>MODELED TEXT FIRST · AUTHORED FALLBACK WHEN NOT MODELED</small>
|
||||
</header>
|
||||
<div class="quick-view">
|
||||
{#each quickViewFixtures as fixture}
|
||||
<article>
|
||||
<header><div><small>{fixture.owner}</small><h3>{fixture.name}</h3></div><strong class:not-modeled={publicStatus(fixture) === 'NOT MODELED'}>{publicStatus(fixture)}</strong></header>
|
||||
<div><span>BEHAVIOR</span><p>{publicBehavior(fixture)}</p></div>
|
||||
<dl><div><dt>RECORD</dt><dd>{fixture.context}</dd></div><div><dt>DISPLAY SOURCE</dt><dd>{publicStatus(fixture) === 'MODELED' ? 'Modeled behavior' : 'Authored description'}</dd></div></dl>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="a-search-heading">
|
||||
<header class="surface-heading">
|
||||
<div><span>03 / GLOBAL SEARCH PRESENTATION</span><h2 id="a-search-heading">SEARCH RESULTS</h2></div>
|
||||
<small>{modelStatusFixtures.length} FIXTURE MATCHES</small>
|
||||
</header>
|
||||
<div class="search-results">
|
||||
{#each modelStatusFixtures as fixture}
|
||||
<div class="search-result">
|
||||
<small>{fixture.owner}</small><strong>{fixture.name}</strong><span>{publicBehavior(fixture)}</span><b class:not-modeled={publicStatus(fixture) === 'NOT MODELED'}>{publicStatus(fixture)}</b>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.prototype-a{display:grid;gap:28px;padding:20px 0 110px}.prototype-heading{min-height:150px;display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,420px);align-items:end;gap:24px;padding:24px;border:1px solid var(--shd-border);border-top:2px solid var(--shd-orange);background:var(--shd-surface)}.prototype-heading p{margin:0;color:var(--shd-text-muted);font:var(--shd-type-supporting)/1.5 var(--shd-font-editorial)}.prototype-heading>div>p{color:var(--shd-orange);font:900 var(--shd-type-telemetry)/1 var(--shd-font-sans);letter-spacing:1px}.prototype-heading>div>p span{display:inline-block;width:22px;border-top:2px solid currentColor;vertical-align:middle}.prototype-heading h1{margin:10px 0 0;font-size:clamp(34px,5vw,64px);line-height:.9;letter-spacing:-1px}.prototype-heading h1 em{color:var(--shd-orange);font-style:normal}.prototype-a>section{display:grid;gap:10px}.surface-heading{min-height:46px;display:flex;align-items:end;justify-content:space-between;gap:16px;padding-bottom:9px;border-bottom:1px solid var(--shd-border)}.surface-heading>div{display:flex;align-items:baseline;gap:12px}.surface-heading span,.surface-heading small{color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);font-weight:800;letter-spacing:.8px}.surface-heading h2{margin:0;font-size:20px;letter-spacing:.6px}.card-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.model-card{min-width:0;display:flex;flex-direction:column;border:1px solid var(--shd-border);border-top:2px solid var(--shd-border-strong);background:var(--shd-surface)}.model-card>header{min-height:92px;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:10px;padding:14px;border-bottom:1px solid var(--shd-border)}.model-card header small,.quick-view header small{display:block;margin-bottom:6px;color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.8px}.model-card h3,.quick-view h3{margin:0;font-size:18px;line-height:1.05}.model-card header span{display:block;margin-top:7px;color:var(--shd-text-dim);font-size:var(--shd-type-telemetry)}.model-card header strong,.quick-view header strong,.search-result b{padding:5px 7px;border:1px solid var(--shd-success);color:var(--shd-success);font-size:10px;line-height:1;letter-spacing:.7px;white-space:nowrap}.model-card header strong.not-modeled,.quick-view header strong.not-modeled,.search-result b.not-modeled{border-color:var(--shd-border-strong);color:var(--shd-text-muted)}.behavior-block{flex:1;margin:12px;padding:12px;border-left:2px solid var(--shd-success);background:var(--shd-model-surface)}.model-card:has(.not-modeled) .behavior-block{border-left-color:var(--shd-text-dim)}.behavior-block small{color:var(--shd-text-dim);font-size:10px;font-weight:900;letter-spacing:.9px}.behavior-block p{margin:7px 0 0;color:var(--shd-text);font:var(--shd-type-supporting)/1.5 var(--shd-font-editorial)}.model-card>footer{display:flex;justify-content:space-between;gap:10px;padding:10px 12px;border-top:1px solid var(--shd-border-soft);color:var(--shd-text-dim);font-size:10px;font-weight:900;letter-spacing:.6px}.model-card>footer b{color:var(--shd-orange)}.quick-view{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.quick-view>article{border:1px solid var(--shd-border);background:var(--shd-surface-raised)}.quick-view article>header{display:flex;align-items:start;justify-content:space-between;gap:16px;padding:16px;border-bottom:1px solid var(--shd-border)}.quick-view article>div{padding:18px 16px}.quick-view article>div span{color:var(--shd-text-muted);font-size:10px;font-weight:900;letter-spacing:1px}.quick-view article>div p{max-width:62ch;margin:8px 0 0;font:18px/1.55 var(--shd-font-editorial)}.quick-view dl{display:grid;grid-template-columns:1fr 1fr;margin:0;border-top:1px solid var(--shd-border)}.quick-view dl>div{padding:10px 16px;border-left:1px solid var(--shd-border)}.quick-view dl>div:first-child{border-left:0}.quick-view dt{color:var(--shd-text-dim);font-size:10px;font-weight:900;letter-spacing:.8px}.quick-view dd{margin:4px 0 0;color:var(--shd-text-muted);font-size:var(--shd-type-telemetry)}.search-results{border:1px solid var(--shd-border);background:var(--shd-model-surface)}.search-result{display:grid;grid-template-columns:190px minmax(150px,.7fr) minmax(260px,1.6fr) auto;align-items:center;gap:12px;min-height:58px;padding:8px 12px;border-top:1px solid var(--shd-border-soft)}.search-result:first-child{border-top:0}.search-result>small{color:var(--shd-orange);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.5px}.search-result>strong{font-size:var(--shd-type-supporting)}.search-result>span{color:var(--shd-text-muted);font:var(--shd-type-telemetry)/1.4 var(--shd-font-editorial)}
|
||||
@media(max-width:1000px){.card-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.search-result{grid-template-columns:minmax(140px,.7fr) minmax(0,1fr) auto}.search-result>small{grid-column:1/-1}.search-result>span{grid-column:2}}
|
||||
@media(max-width:700px){.prototype-heading,.quick-view,.card-grid{grid-template-columns:1fr}.prototype-heading{align-items:start}.surface-heading{align-items:start}.surface-heading>div{display:grid;gap:4px}.surface-heading>small{display:none}.search-result{grid-template-columns:1fr auto}.search-result>small,.search-result>span{grid-column:1/-1}}
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,93 @@
|
||||
// THROWAWAY PROTOTYPE for issue #5. Fixture lifecycle data must never become a public API.
|
||||
export type PrototypeVariant = 'A' | 'B' | 'C';
|
||||
export type PublicModelStatus = 'MODELED' | 'NOT MODELED';
|
||||
|
||||
type MechanicLifecycle =
|
||||
'ready' | 'requires_assumptions' | 'captured_only' | 'absent';
|
||||
|
||||
export type ModelStatusFixture = {
|
||||
id: string;
|
||||
owner:
|
||||
| 'Talent'
|
||||
| 'OS Protocol Talent'
|
||||
| 'Skill Chip Talent'
|
||||
| 'Specialization tree-rank effect';
|
||||
name: string;
|
||||
context: string;
|
||||
internal: {
|
||||
lifecycle: MechanicLifecycle;
|
||||
current: boolean;
|
||||
};
|
||||
modeledText?: string;
|
||||
authoredText: string;
|
||||
};
|
||||
|
||||
export const modelStatusFixtures: ModelStatusFixture[] = [
|
||||
{
|
||||
id: 'talent-ready',
|
||||
owner: 'Talent',
|
||||
name: 'Predatory Advance',
|
||||
context: 'Weapon Talent · Tier I',
|
||||
internal: { lifecycle: 'ready', current: true },
|
||||
modeledText:
|
||||
'On enemy kill: gain +15% Weapon Damage for 10 s. Refreshes and stacks up to 3 times.',
|
||||
authoredText:
|
||||
'Killing an enemy grants 15% weapon damage for 10s. Stacks up to 3 times.',
|
||||
},
|
||||
{
|
||||
id: 'protocol-assumptions',
|
||||
owner: 'OS Protocol Talent',
|
||||
name: 'Emergency Weave',
|
||||
context: 'Superior OS Protocol · Talent',
|
||||
internal: { lifecycle: 'requires_assumptions', current: true },
|
||||
modeledText:
|
||||
'When armor breaks: restore 20% Armor over 5 s. Cooldown: 20 s.',
|
||||
authoredText:
|
||||
'When armor is broken, restore 20% armor over 5s. Cooldown: 20s.',
|
||||
},
|
||||
{
|
||||
id: 'chip-captured',
|
||||
owner: 'Skill Chip Talent',
|
||||
name: 'Rapid Instruction',
|
||||
context: 'Tactical Skill Chip Set · 3-piece talent',
|
||||
internal: { lifecycle: 'captured_only', current: true },
|
||||
modeledText: 'After skill use: gain +8% Skill Haste for 12 s.',
|
||||
authoredText: 'Using a skill grants 8% Skill Haste for 12s.',
|
||||
},
|
||||
{
|
||||
id: 'specialization-absent',
|
||||
owner: 'Specialization tree-rank effect',
|
||||
name: 'Perfect Cadence · Rank 3',
|
||||
context: 'Sharpshooter · Precision branch',
|
||||
internal: { lifecycle: 'absent', current: true },
|
||||
authoredText:
|
||||
'After reaching 5 hits without missing, your next shot deals 25% increased damage.',
|
||||
},
|
||||
{
|
||||
id: 'talent-stale',
|
||||
owner: 'Talent',
|
||||
name: 'Measured Focus',
|
||||
context: 'Gear Talent · Tier II',
|
||||
internal: { lifecycle: 'ready', current: false },
|
||||
modeledText:
|
||||
'After a headshot: gain +8% Critical Hit Damage for 8 s, stacking up to 3 times.',
|
||||
authoredText:
|
||||
'Headshots grant 10% Critical Hit Damage for 8s. Stacks up to 4 times.',
|
||||
},
|
||||
];
|
||||
|
||||
export const publicStatus = (fixture: ModelStatusFixture): PublicModelStatus =>
|
||||
fixture.internal.current && fixture.internal.lifecycle !== 'absent'
|
||||
? 'MODELED'
|
||||
: 'NOT MODELED';
|
||||
|
||||
export const publicBehavior = (fixture: ModelStatusFixture): string =>
|
||||
publicStatus(fixture) === 'MODELED' && fixture.modeledText
|
||||
? fixture.modeledText
|
||||
: fixture.authoredText;
|
||||
|
||||
export const prototypeVariantNames = {
|
||||
A: 'Integrated card hierarchy',
|
||||
B: 'Evidence rail',
|
||||
C: 'Registry rows',
|
||||
} satisfies Record<PrototypeVariant, string>;
|
||||
Reference in New Issue
Block a user