259 lines
22 KiB
Svelte
259 lines
22 KiB
Svelte
<script lang="ts">
|
||
import PendingForm from '$lib/components/PendingForm.svelte';
|
||
import RegistryInlineEditor from '$lib/components/RegistryInlineEditor.svelte';
|
||
import RegistryPagination from '$lib/components/RegistryPagination.svelte';
|
||
import SortableColumnHeader from '$lib/components/SortableColumnHeader.svelte';
|
||
import SearchableAssignment from '$lib/components/SearchableAssignment.svelte';
|
||
import SegmentedChoice from '$lib/components/SegmentedChoice.svelte';
|
||
import { createDraft } from '$lib/builder/loadout';
|
||
|
||
let { data, form } = $props();
|
||
type StageEditor = {
|
||
stageKey: string;
|
||
label: string;
|
||
operator: string;
|
||
displayOrder: number;
|
||
lockedPosition: string | null;
|
||
};
|
||
type ProfileEditor = {
|
||
statAttributeId: string;
|
||
stageKey: string;
|
||
weight: number | string;
|
||
softCap: number | string;
|
||
hardCap: number | string;
|
||
curveType: string;
|
||
curveParameter: number | string;
|
||
weaponCategory: string;
|
||
};
|
||
type RevisionEditor = {
|
||
id: string;
|
||
basedOnRevisionId: string;
|
||
name: string;
|
||
notes: string;
|
||
stages: StageEditor[];
|
||
profiles: ProfileEditor[];
|
||
};
|
||
type CalibrationEditor = {
|
||
id: string;
|
||
name: string;
|
||
status: string;
|
||
loadoutSnapshot: string;
|
||
selectedWeapon: string;
|
||
gameVersion: string;
|
||
observedAt: string;
|
||
notes: string;
|
||
bodyHit: number | string;
|
||
criticalBodyHit: number | string;
|
||
headshot: number | string;
|
||
criticalHeadshot: number | string;
|
||
magazineSeconds: number | string;
|
||
reloadSeconds: number | string;
|
||
encounterDps: number | string;
|
||
predictedJson: string;
|
||
};
|
||
const canPublish = $derived(data.permissions?.includes('*') || data.permissions?.includes('calculation.publish'));
|
||
const activeRevision = $derived(data.revisions.find((revision: any) => revision.status === 'active'));
|
||
const stageItems = (revision: RevisionEditor) => revision.stages.map((stage) => ({
|
||
id: stage.stageKey,
|
||
name: stage.label,
|
||
category: stage.operator.replaceAll('_', ' ').toUpperCase()
|
||
}));
|
||
const curveOptions = [
|
||
{ value: 'none', label: 'NONE' },
|
||
{ value: 'power', label: 'POWER' },
|
||
{ value: 'smoothstep', label: 'SMOOTHSTEP' }
|
||
];
|
||
const stateOptions = [{ value: 'draft', label: 'DRAFT' }, { value: 'ready', label: 'READY' }];
|
||
const weaponOptions = [{ value: '0', label: 'PRIMARY' }, { value: '1', label: 'SECONDARY' }];
|
||
|
||
const revisionFrom = (revision: any, edit = false): RevisionEditor => ({
|
||
id: edit ? String(revision?.id ?? '') : '',
|
||
basedOnRevisionId: edit ? String(revision?.based_on_revision_id ?? '') : String(revision?.id ?? ''),
|
||
name: edit ? String(revision?.name ?? '') : `Combat model ${Number(revision?.version ?? 0) + 1}`,
|
||
notes: edit ? String(revision?.notes ?? '') : `Based on revision ${revision?.version ?? 1}.`,
|
||
stages: (revision?.stages ?? []).map((stage: any) => ({
|
||
stageKey: String(stage.stage_key),
|
||
label: String(stage.label),
|
||
operator: String(stage.operator),
|
||
displayOrder: Number(stage.display_order),
|
||
lockedPosition: stage.locked_position ? String(stage.locked_position) : null
|
||
})),
|
||
profiles: (revision?.profiles ?? []).map((profile: any) => ({
|
||
statAttributeId: String(profile.stat_attribute_id),
|
||
stageKey: String(profile.stage_key),
|
||
weight: Number(profile.weight),
|
||
softCap: profile.soft_cap ?? '',
|
||
hardCap: profile.hard_cap ?? '',
|
||
curveType: String(profile.curve_type),
|
||
curveParameter: profile.curve_parameter ?? '',
|
||
weaponCategory: String(profile.weapon_category ?? '*')
|
||
}))
|
||
});
|
||
const blankCalibration = (): CalibrationEditor => ({
|
||
id: '', name: '', status: 'draft', loadoutSnapshot: JSON.stringify(createDraft(), null, 2),
|
||
selectedWeapon: '0', gameVersion: '', observedAt: '', notes: '', bodyHit: '',
|
||
criticalBodyHit: '', headshot: '', criticalHeadshot: '', magazineSeconds: '',
|
||
reloadSeconds: '', encounterDps: '', predictedJson: ''
|
||
});
|
||
const calibrationFrom = (item: any): CalibrationEditor => ({
|
||
id: String(item.id), name: String(item.name), status: String(item.status),
|
||
loadoutSnapshot: JSON.stringify(JSON.parse(String(item.loadout_snapshot_json)), null, 2),
|
||
selectedWeapon: String(item.selected_weapon), gameVersion: String(item.game_version ?? ''),
|
||
observedAt: String(item.observed_at ?? ''), notes: String(item.notes ?? ''),
|
||
bodyHit: item.body_hit ?? '', criticalBodyHit: item.critical_body_hit ?? '',
|
||
headshot: item.headshot ?? '', criticalHeadshot: item.critical_headshot ?? '',
|
||
magazineSeconds: item.magazine_seconds ?? '', reloadSeconds: item.reload_seconds ?? '',
|
||
encounterDps: item.encounter_dps ?? '', predictedJson: String(item.predicted_json ?? '')
|
||
});
|
||
|
||
let revisionEditing = $state<RevisionEditor | null>(null);
|
||
let calibrationEditing = $state<CalibrationEditor | null>(null);
|
||
let publishingId = $state('');
|
||
let publishConfirmed = $state(false);
|
||
let handledToken = $state('');
|
||
|
||
function addProfile() {
|
||
if (!revisionEditing) return;
|
||
revisionEditing.profiles = [...revisionEditing.profiles, {
|
||
statAttributeId: '', stageKey: revisionEditing.stages.find((stage) => stage.operator === 'additive_percent')?.stageKey ?? '',
|
||
weight: 1, softCap: '', hardCap: '', curveType: 'none', curveParameter: '', weaponCategory: '*'
|
||
}];
|
||
}
|
||
function moveStage(index: number, direction: -1 | 1) {
|
||
if (!revisionEditing) return;
|
||
const target = index + direction;
|
||
const stages = [...revisionEditing.stages].sort((left, right) => left.displayOrder - right.displayOrder);
|
||
if (index < 0 || target < 0 || target >= stages.length || stages[index].lockedPosition || stages[target].lockedPosition) return;
|
||
[stages[index], stages[target]] = [stages[target], stages[index]];
|
||
revisionEditing.stages = stages.map((stage, stageIndex) => ({ ...stage, displayOrder: stageIndex + 1 }));
|
||
}
|
||
|
||
$effect(() => {
|
||
const token = String(form?.resultToken ?? '');
|
||
if (!token || token === handledToken) return;
|
||
handledToken = token;
|
||
if (form?.revisionSaved) revisionEditing = form.revisionMode === 'add' ? revisionFrom(activeRevision) : null;
|
||
if (form?.calibrationSaved) calibrationEditing = form.calibrationMode === 'add' ? blankCalibration() : null;
|
||
if (form?.revisionPublished) {
|
||
publishingId = '';
|
||
publishConfirmed = false;
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<svelte:head><title>Combat calculations — ISAC</title></svelte:head>
|
||
|
||
<div class="module-toolbar">
|
||
<div><p class="eyebrow">MODELED DAMAGE + EMPIRICAL CALIBRATION</p><h2>COMBAT CALCULATIONS</h2><p>Configure typed calculation stages, Attribute bindings, and field observations used by the loadout builder.</p></div>
|
||
<div class="calculation-actions"><button class="secondary" onclick={() => calibrationEditing = blankCalibration()}>ADD CALIBRATION CASE +</button><button class="primary" onclick={() => revisionEditing = revisionFrom(activeRevision)}>NEW REVISION +</button></div>
|
||
</div>
|
||
|
||
<div class="calculation-kpis">
|
||
<div><span>ACTIVE REVISION</span><b>{activeRevision ? `R${activeRevision.version}` : 'NONE'}</b><small>{activeRevision?.name ?? 'NO ACTIVE RULES'}</small></div>
|
||
<div><span>ATTRIBUTE PROFILES</span><b>{activeRevision?.profiles.length ?? 0}</b><small>TYPED BINDINGS</small></div>
|
||
<div><span>READY CASES</span><b>{Number(data.calibrationSummary?.ready ?? 0)}</b><small>{Number(data.calibrationSummary?.predicted ?? 0)} WITH PREDICTIONS</small></div>
|
||
</div>
|
||
|
||
{#if revisionEditing}
|
||
<div class="dashboard-editor calculation-revision-editor">
|
||
<div class="editor-title"><div><p class="eyebrow">{revisionEditing.id ? 'MODIFY DRAFT REVISION' : 'NEW DRAFT REVISION'}</p><h3>{revisionEditing.name || 'CALCULATION RULE SET'}</h3></div><button aria-label="Close revision editor" onclick={() => revisionEditing = null}>×</button></div>
|
||
<PendingForm action="?/saveRevision">
|
||
<input type="hidden" name="id" value={revisionEditing.id}/>
|
||
<input type="hidden" name="basedOnRevisionId" value={revisionEditing.basedOnRevisionId}/>
|
||
<input type="hidden" name="stagesJson" value={JSON.stringify(revisionEditing.stages)}/>
|
||
<input type="hidden" name="profilesJson" value={JSON.stringify(revisionEditing.profiles)}/>
|
||
<div class="calculation-revision-identity"><label class="field isac-record-name">REVISION NAME<input name="name" bind:value={revisionEditing.name} maxlength="120" required/></label><label class="field">NOTES<textarea name="notes" bind:value={revisionEditing.notes} maxlength="4000"></textarea></label></div>
|
||
<div class="selection-head"><div><span>ORDERED CALCULATION STAGES</span><small>BASE AND FIRING-CYCLE POSITIONS ARE LOCKED</small></div><b>{revisionEditing.stages.length} STAGES</b></div>
|
||
<div class="calculation-stage-list">{#each [...revisionEditing.stages].sort((left, right) => left.displayOrder - right.displayOrder) as stage, index}
|
||
<div class:locked={stage.lockedPosition}><b>{String(stage.displayOrder).padStart(2, '0')}</b><label class="field">LABEL<input bind:value={stage.label} required/></label><span>{stage.operator.replaceAll('_', ' ').toUpperCase()}</span><em>{stage.lockedPosition?.toUpperCase() ?? 'ORDERED'}</em><div class="stage-order-actions"><button type="button" class="secondary" aria-label={`Move ${stage.label} earlier`} disabled={Boolean(stage.lockedPosition) || index === 1} onclick={() => moveStage(index,-1)}>↑</button><button type="button" class="secondary" aria-label={`Move ${stage.label} later`} disabled={Boolean(stage.lockedPosition) || index === revisionEditing!.stages.length - 2} onclick={() => moveStage(index,1)}>↓</button></div>
|
||
</div>{/each}</div>
|
||
<div class="selection-head"><div><span>ATTRIBUTE CALCULATION PROFILES</span><small>WEIGHT, CAPS, AND SAFE CURVES</small></div><button type="button" class="secondary" onclick={addProfile}>ADD PROFILE +</button></div>
|
||
<div class="calculation-profile-list">{#each revisionEditing.profiles as profile, index}
|
||
<section>
|
||
<header><span>PROFILE {String(index + 1).padStart(2, '0')}</span><button type="button" class="moderation-action danger" onclick={() => revisionEditing && (revisionEditing.profiles = revisionEditing.profiles.filter((_, itemIndex) => itemIndex !== index))}>REMOVE</button></header>
|
||
<div class="field"><span>ATTRIBUTE</span><SearchableAssignment items={data.attributes} mode="single" compact fieldName={`profile-attribute-${index}`} subjectLabel="ATTRIBUTE" searchPlaceholder="Search Attributes…" bind:value={profile.statAttributeId}/></div>
|
||
<div class="field"><span>CALCULATION STAGE</span><SearchableAssignment items={stageItems(revisionEditing)} mode="single" compact fieldName={`profile-stage-${index}`} subjectLabel="STAGE" searchPlaceholder="Search stages…" bind:value={profile.stageKey}/></div>
|
||
<div class="calculation-profile-values">
|
||
<label class="field">WEIGHT<input type="number" min="-100" max="100" step="any" bind:value={profile.weight} required/></label>
|
||
<label class="field">SOFT CAP<input type="number" min="0" step="any" bind:value={profile.softCap} placeholder="NONE"/></label>
|
||
<label class="field">HARD CAP<input type="number" min="0" step="any" bind:value={profile.hardCap} placeholder="NONE"/></label>
|
||
<label class="field">CURVE PARAMETER<input type="number" min="0.01" step="any" bind:value={profile.curveParameter} disabled={profile.curveType === 'none'} placeholder="1"/></label>
|
||
</div>
|
||
<SegmentedChoice legend="CURVE" name={`profile-curve-${index}`} options={curveOptions} bind:value={profile.curveType}/>
|
||
</section>
|
||
{/each}</div>
|
||
{#if form?.revisionError}<p class="error">{form.revisionError}</p>{/if}
|
||
<div class="form-actions"><button type="button" class="secondary" onclick={() => revisionEditing = null}>CANCEL</button><button class="primary">SAVE DRAFT REVISION →</button></div>
|
||
</PendingForm>
|
||
</div>
|
||
{/if}
|
||
|
||
{#snippet calibrationEditor()}
|
||
{#if calibrationEditing}
|
||
<div class="dashboard-editor calibration-editor">
|
||
<div class="editor-title"><div><p class="eyebrow">{calibrationEditing.id ? 'MODIFY CALIBRATION CASE' : 'NEW CALIBRATION CASE'}</p><h3>{calibrationEditing.name || 'FIELD OBSERVATION'}</h3></div><button aria-label="Close calibration editor" onclick={() => calibrationEditing = null}>×</button></div>
|
||
<PendingForm action="?/saveCalibration">
|
||
<input type="hidden" name="id" value={calibrationEditing.id}/>
|
||
<div class="calibration-identity"><label class="field isac-record-name">CASE NAME<input name="name" bind:value={calibrationEditing.name} maxlength="120" required/></label><SegmentedChoice legend="CASE STATE" name="status" options={stateOptions} bind:value={calibrationEditing.status}/><SegmentedChoice legend="MEASURED WEAPON" name="selectedWeapon" options={weaponOptions} bind:value={calibrationEditing.selectedWeapon}/><label class="field">GAME VERSION<input name="gameVersion" bind:value={calibrationEditing.gameVersion}/></label><label class="field">OBSERVED DATE<input name="observedAt" type="date" bind:value={calibrationEditing.observedAt}/></label></div>
|
||
<label class="field calibration-snapshot">LOADOUT SNAPSHOT · JSON<textarea name="loadoutSnapshot" bind:value={calibrationEditing.loadoutSnapshot} required spellcheck="false"></textarea></label>
|
||
<div class="calibration-observations">
|
||
<label class="field">BODY HIT<input name="bodyHit" type="number" min="0" step="any" bind:value={calibrationEditing.bodyHit}/></label>
|
||
<label class="field">CRITICAL BODY<input name="criticalBodyHit" type="number" min="0" step="any" bind:value={calibrationEditing.criticalBodyHit}/></label>
|
||
<label class="field">HEADSHOT<input name="headshot" type="number" min="0" step="any" bind:value={calibrationEditing.headshot}/></label>
|
||
<label class="field">CRITICAL HEADSHOT<input name="criticalHeadshot" type="number" min="0" step="any" bind:value={calibrationEditing.criticalHeadshot}/></label>
|
||
<label class="field">MAGAZINE TIME · SECONDS<input name="magazineSeconds" type="number" min="0.01" step="any" bind:value={calibrationEditing.magazineSeconds}/></label>
|
||
<label class="field">RELOAD TIME · SECONDS<input name="reloadSeconds" type="number" min="0.01" step="any" bind:value={calibrationEditing.reloadSeconds}/></label>
|
||
<label class="field">ENCOUNTER DPS · OPTIONAL<input name="encounterDps" type="number" min="0" step="any" bind:value={calibrationEditing.encounterDps}/></label>
|
||
</div>
|
||
<label class="field">NOTES<textarea name="notes" bind:value={calibrationEditing.notes} maxlength="4000"></textarea></label>
|
||
<label class="field calibration-prediction">PREDICTION SNAPSHOT · OPTIONAL JSON<textarea name="predictedJson" bind:value={calibrationEditing.predictedJson} spellcheck="false"></textarea></label>
|
||
{#if form?.calibrationError}<p class="error">{form.calibrationError}</p>{/if}
|
||
<div class="form-actions"><button type="button" class="secondary" onclick={() => calibrationEditing = null}>CANCEL</button><button class="primary">SAVE CALIBRATION CASE →</button></div>
|
||
</PendingForm>
|
||
</div>
|
||
{/if}
|
||
{/snippet}
|
||
|
||
{#if calibrationEditing && !calibrationEditing.id}
|
||
{@render calibrationEditor()}
|
||
{/if}
|
||
|
||
<div class="calculation-revision-registry">
|
||
<div class="selection-head"><div><span>CALCULATION REVISIONS</span><small>SAVED BUILDS USE THE ACTIVE REVISION</small></div><PendingForm action="?/suggestRevision">{#snippet children(pending)}<button class="secondary" disabled={pending}>{pending ? 'ANALYZING…' : 'GENERATE ASSISTED SUGGESTION'}</button>{/snippet}</PendingForm></div>
|
||
{#if form?.suggestionError}<p class="error">{form.suggestionError}</p>{/if}
|
||
{#each data.revisions as revision}
|
||
<article class:active={revision.status === 'active'}>
|
||
<header><div><span>REVISION {revision.version}</span><h3>{revision.name}</h3></div><b>{revision.status.toUpperCase()}</b></header>
|
||
<p>{revision.notes || 'No revision notes.'}</p>
|
||
<footer><span>{revision.stages.length} STAGES · {revision.profiles.length} ATTRIBUTE PROFILES</span><div>{#if revision.status === 'draft'}<button class="secondary" onclick={() => revisionEditing = revisionFrom(revision, true)}>EDIT</button>{#if canPublish}<button class="primary" onclick={() => { publishingId = revision.id; publishConfirmed = false; }}>REVIEW + ACTIVATE</button>{/if}{/if}</div></footer>
|
||
{#if publishingId === revision.id}
|
||
<div class="publish-confirmation">
|
||
<div><strong>ACTIVATE REVISION {revision.version}?</strong><span>Every saved and shared build will recalculate using these rules.</span></div>
|
||
<label><input type="checkbox" bind:checked={publishConfirmed}/> I understand this changes current builder results.</label>
|
||
<PendingForm action="?/publishRevision"><input type="hidden" name="id" value={revision.id}/><input type="hidden" name="confirmation" value="activate-current-rules"/><div><button type="button" class="secondary" onclick={() => publishingId = ''}>CANCEL</button><button class="primary" disabled={!publishConfirmed}>ACTIVATE REVISION →</button></div></PendingForm>
|
||
</div>
|
||
{/if}
|
||
</article>
|
||
{/each}
|
||
</div>
|
||
|
||
<div class="selection-head"><div><span>CALIBRATION REGISTRY</span><small>OBSERVED GAME VALUES + MODEL COMPARISON</small></div><b>{Number(data.calibrationSummary?.total ?? 0)} CASES</b></div>
|
||
<RegistryPagination pagination={data.pagination} search={data.search}/>
|
||
<div class="dashboard-table">
|
||
<div class="dashboard-row calibration-row table-heading"><SortableColumnHeader label="CASE" column="name" sort={data.sort}/><SortableColumnHeader label="WEAPON" column="weapon" sort={data.sort}/><SortableColumnHeader label="OBSERVATIONS" column="observations" sort={data.sort} defaultDirection="desc"/><SortableColumnHeader label="PREDICTION" column="prediction" sort={data.sort} defaultDirection="desc"/><SortableColumnHeader label="STATE" column="state" sort={data.sort}/><span></span></div>
|
||
{#each data.calibrationCases as item}
|
||
<div class="dashboard-row calibration-row" class:registry-row-editing={String(calibrationEditing?.id ?? '') === String(item.id)}><div><strong>{item.name}</strong><small>{item.game_version || 'GAME VERSION NOT RECORDED'} · {item.creator_name}</small></div><span>{Number(item.selected_weapon) === 0 ? 'PRIMARY' : 'SECONDARY'}</span><span>{[item.body_hit,item.critical_body_hit,item.headshot,item.critical_headshot,item.magazine_seconds,item.reload_seconds].filter((value) => value !== null).length} / 6 CORE</span><span>{item.predicted_json ? 'AVAILABLE' : 'INSUFFICIENT DATA'}</span><b class:configured={item.status === 'ready'} class="state-dot">{item.status.toUpperCase()}</b><button aria-expanded={String(calibrationEditing?.id ?? '') === String(item.id)} aria-controls={`calibration-editor-${item.id}`} onclick={() => calibrationEditing = calibrationFrom(item)}>EDIT →</button></div>
|
||
{#if String(calibrationEditing?.id ?? '') === String(item.id)}
|
||
<RegistryInlineEditor id={`calibration-editor-${item.id}`} label={`Edit ${item.name}`}>
|
||
{@render calibrationEditor()}
|
||
</RegistryInlineEditor>
|
||
{/if}
|
||
{/each}
|
||
</div>
|
||
|
||
<style>
|
||
.module-toolbar>div:first-child{min-width:0;padding-right:12px}.calculation-actions{flex:0 0 auto}.calculation-actions,.calculation-revision-registry article footer div{display:flex;gap:7px;align-items:center}.calculation-kpis{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:7px;margin-bottom:9px}.calculation-kpis>div{display:grid;gap:2px;padding:10px;border:1px solid var(--shd-border);background:var(--shd-surface)}.calculation-kpis span,.calculation-kpis small{color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.8px}.calculation-kpis b{color:var(--shd-orange);font-size:22px}.calculation-revision-identity,.calibration-identity{display:grid;grid-template-columns:1.2fr 1.8fr;gap:8px}.calculation-stage-list{display:grid;gap:4px}.calculation-stage-list>div{display:grid;grid-template-columns:42px minmax(220px,1fr) minmax(170px,.7fr) 90px 76px;gap:7px;align-items:center;padding:6px;border:1px solid var(--shd-border);background:#0d1011}.calculation-stage-list>div>b{color:var(--shd-orange);text-align:center}.calculation-stage-list>div>span,.calculation-stage-list>div>em{font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px}.stage-order-actions{display:grid;grid-template-columns:1fr 1fr;gap:3px}.stage-order-actions button{min-width:0;height:32px;padding:0}.calculation-profile-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}.calculation-profile-list>section{display:grid;gap:7px;padding:9px;border:1px solid var(--shd-border);background:#0d1011}.calculation-profile-list header{display:flex;justify-content:space-between;align-items:center}.calculation-profile-values{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.calibration-identity{grid-template-columns:1.4fr 1fr 1fr 1fr 1fr}.calibration-snapshot textarea{min-height:180px;font-family:var(--shd-font-mono)}.calibration-prediction textarea{min-height:90px;font-family:var(--shd-font-mono)}.calibration-observations{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:7px}.calculation-revision-registry{display:grid;gap:7px;margin:10px 0}.calculation-revision-registry>article{border:1px solid var(--shd-border);border-left:2px solid var(--shd-border);background:var(--shd-surface)}.calculation-revision-registry>article.active{border-left-color:var(--shd-orange)}.calculation-revision-registry article>header,.calculation-revision-registry article>footer{display:flex;justify-content:space-between;gap:8px;align-items:center;padding:9px}.calculation-revision-registry article>header{border-bottom:1px solid var(--shd-border)}.calculation-revision-registry h3,.calculation-revision-registry p{margin:0}.calculation-revision-registry article>p{padding:9px;color:var(--shd-text-muted)}.calculation-revision-registry article>footer{border-top:1px solid var(--shd-border);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px}.publish-confirmation{display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center;padding:10px;border-top:1px solid var(--shd-orange);background:#1b160d}.publish-confirmation>div{display:grid}.publish-confirmation label{font-size:var(--shd-type-supporting)}.calibration-row{grid-template-columns:minmax(230px,1.4fr) .5fr .65fr .8fr .55fr 70px}
|
||
@media(max-width:1000px){.calculation-profile-list{grid-template-columns:1fr}.calibration-identity,.calibration-observations{grid-template-columns:repeat(2,minmax(0,1fr))}.publish-confirmation{grid-template-columns:1fr}.calibration-row{grid-template-columns:1fr .5fr .6fr}.calibration-row>*:nth-child(4),.calibration-row>*:nth-child(5){display:none}}
|
||
@media(max-width:650px){.calculation-actions{display:grid}.calculation-kpis{grid-template-columns:1fr}.calculation-revision-identity,.calibration-identity,.calibration-observations,.calculation-stage-list>div,.calculation-profile-values{grid-template-columns:1fr}.calculation-stage-list>div>b{text-align:left}.calculation-revision-registry article>header,.calculation-revision-registry article>footer{align-items:start;display:grid}.calibration-row{grid-template-columns:1fr 70px}.calibration-row>*:nth-child(2),.calibration-row>*:nth-child(3){display:none}}
|
||
</style>
|