| <script lang="ts"> |
| import { |
| wizards, |
| activeMethod, |
| setStage, |
| highestUnlocked, |
| downloadExport, |
| METHOD_META, |
| WATERMARKS, |
| type Stage, |
| } from '../lib/stores/app-state'; |
| |
| import KSplit from './stages/kirchenbauer/Split.svelte'; |
| import KBias from './stages/kirchenbauer/Bias.svelte'; |
| import KResult from './stages/kirchenbauer/Result.svelte'; |
| import SClusters from './stages/ksemstamp/Clusters.svelte'; |
| import SRejection from './stages/ksemstamp/Rejection.svelte'; |
| import SResult from './stages/ksemstamp/Result.svelte'; |
| import TRValues from './stages/textseal/RValues.svelte'; |
| import TGumbel from './stages/textseal/Gumbel.svelte'; |
| import TEvidence from './stages/textseal/Evidence.svelte'; |
| import EditDetect from './stages/EditDetect.svelte'; |
| |
| const method = $derived($activeMethod); |
| const meta = $derived(METHOD_META[method]); |
| const wizard = $derived($wizards[method]); |
| const unlocked = $derived(highestUnlocked(wizard)); |
| |
| const STAGE_NUMS: Stage[] = [1, 2, 3, 4]; |
| const NUM_LABEL = ['1', '2', '3', '4']; |
| |
| function lockedReason(s: Stage): string { |
| if (s <= unlocked) return ''; |
| if (s === 2) return 'Enter a seed in stage 1 first'; |
| return 'Generate the watermarked text first'; |
| } |
| </script> |
|
|
| <section class="panel wm"> |
| <div class="methods"> |
| {#each WATERMARKS as m} |
| <button class="method" class:active={m === method} onclick={() => activeMethod.set(m)}> |
| {METHOD_META[m].title} |
| <span class="badge">{METHOD_META[m].year}</span> |
| </button> |
| {/each} |
| <button |
| class="export" |
| title="Download seed, parameters, trace and scores as JSON" |
| disabled={!wizard.result} |
| onclick={() => downloadExport(method)}>export JSON</button |
| > |
| </div> |
|
|
| <p class="tagline dim small">{meta.tagline}</p> |
|
|
| <div class="stages"> |
| {#each STAGE_NUMS as s, i} |
| {@const locked = s > unlocked} |
| <button |
| class="stage-tab" |
| class:active={wizard.stage === s} |
| class:locked |
| disabled={locked} |
| title={lockedReason(s)} |
| onclick={() => setStage(method, s)} |
| > |
| <span class="num">{NUM_LABEL[i]}</span> |
| {meta.stages[i]} |
| </button> |
| {/each} |
| </div> |
|
|
| <div class="body"> |
| {#if wizard.error} |
| <p class="err small mono">{wizard.error}</p> |
| {/if} |
|
|
| {#if method === 'kirchenbauer'} |
| {#if wizard.stage === 1} |
| <KSplit {wizard} /> |
| {:else if wizard.stage === 2} |
| <KBias {wizard} /> |
| {:else if wizard.stage === 3} |
| <KResult {wizard} /> |
| {:else} |
| <EditDetect {wizard} /> |
| {/if} |
| {:else if method === 'ksemstamp'} |
| {#if wizard.stage === 1} |
| <SClusters {wizard} /> |
| {:else if wizard.stage === 2} |
| <SRejection {wizard} /> |
| {:else if wizard.stage === 3} |
| <SResult {wizard} /> |
| {:else} |
| <EditDetect {wizard} /> |
| {/if} |
| {:else if wizard.stage === 1} |
| <TRValues {wizard} /> |
| {:else if wizard.stage === 2} |
| <TGumbel {wizard} /> |
| {:else if wizard.stage === 3} |
| <TEvidence {wizard} /> |
| {:else} |
| <EditDetect {wizard} /> |
| {/if} |
| </div> |
| </section> |
|
|
| <style> |
| .wm { |
| display: flex; |
| flex-direction: column; |
| gap: 10px; |
| min-width: 0; |
| } |
| .methods { |
| display: flex; |
| gap: 6px; |
| align-items: center; |
| flex-wrap: wrap; |
| } |
| .method { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| font-weight: 550; |
| } |
| .method.active { |
| border-color: var(--accent); |
| background: var(--accent-soft); |
| color: var(--accent); |
| } |
| .export { |
| margin-left: auto; |
| font-size: 12.5px; |
| padding: 4px 10px; |
| } |
| .tagline { |
| margin: 0; |
| } |
| .stages { |
| display: flex; |
| gap: 4px; |
| border-bottom: 1px solid var(--border); |
| padding-bottom: 8px; |
| flex-wrap: wrap; |
| } |
| .stage-tab { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| font-size: 13px; |
| padding: 4px 10px; |
| border-color: transparent; |
| background: transparent; |
| } |
| .stage-tab .num { |
| display: inline-flex; |
| align-items: center; |
| justify-content: center; |
| width: 18px; |
| height: 18px; |
| border-radius: 50%; |
| background: var(--panel-3); |
| font-size: 11px; |
| font-weight: 700; |
| } |
| .stage-tab.active { |
| background: var(--accent-soft); |
| border-color: var(--accent); |
| color: var(--accent); |
| } |
| .stage-tab.active .num { |
| background: var(--accent); |
| color: #fff; |
| } |
| .stage-tab.locked { |
| opacity: 0.4; |
| } |
| .body { |
| min-height: 260px; |
| } |
| .err { |
| color: var(--red); |
| white-space: pre-wrap; |
| } |
| </style> |
|
|