v0.3 Phase 5c polish: tighter header, disabled-record clarity, softer notice, attached action bar

Focused polish pass on the Dictation quietware layout per round-10
feedback. No colour exploration; only the eight specific tweaks.

1. Capture header tightened.

  Record button + status block now grouped in their own flex
  container (gap-3) so they read as one capture component. Record
  size dropped from 72px to 64px so the group balances against the
  status text. Timer pushed to far right via flex-1 on the inner
  group. The trio (record / status / timer) feels like one unit.

2. Disabled Record affordance now reads as record.

  Browser-preview state no longer renders as a neutral grey blob.
  Instead:

    bg-transparent
    border-2 border-[color-mix(in_srgb,var(--button-record-bg)_45%,
                                transparent)]
    opacity-80
    cursor-not-allowed
    inner muted-red dot bg-[color-mix(in_srgb,var(--button-record-bg)
                                       _55%,transparent)]

  Result: clearly a record control, clearly disabled, doesn't look
  clickable. aria-label updated to "Record (desktop app only)" in
  that state so screen readers carry the same message.

3. Slim notice border softened.

  LumotiaNotice slim variant now uses border-border-subtle for the
  outer border instead of the role-coloured border. Left bar stays
  full role colour (the visible signal). Icon stays role colour.
  Background stays at the role tinted bg. Net effect: notice is
  ambient, not loud.

4. Action bar attached to transcript surface.

  Dropped the LumotiaPageSkeleton actionBar slot for Dictation;
  moved the buttons into the primary snippet as a bottom row inside
  the same surface. Internal border-t + bg-bg-elevated/40 separator
  keeps it visually nested. Reads as transcript actions, not a
  generic page footer.

  Also dropped primaryBleed so LumotiaPageSkeleton's default card
  chrome wraps the transcript + action dock together — that gives
  the surface boundary Jake asked for in light mode without
  introducing a card-heavy treatment.

5. Compact one-line metadata footer.

  Was: "Smart · {model} · Local only"  +  "{formatMode} · {profile}"
  Now: "{formatMode} · {model} · Local only / Browser preview · {profile}"

  Right-aligned. Single line. No duplicated "Smart". JetBrains Mono
  via the existing --font-family-mono. text-tertiary.

6. Typography corrected.

  Status title moved from Young Serif italic (font-display) to Work
  Sans medium-weight (default body). Young Serif italic is now
  reserved for the empty-state "Talk now, think later." line, which
  remains the only emotional moment in the page.

7. Light mode surface intentional.

  By dropping primaryBleed the skeleton's default surface chrome
  (bg-bg-card + border-border-subtle + rounded-lg) wraps the
  transcript canvas. Light mode now reads as a deliberate paper
  surface, not an open page. Same boundary applies in dark.

8. Colour restraint preserved.

  No token changes. Same red/blue/green/yellow/orange grammar.
  Wireline contract unchanged. Brand orange still reserved for
  logo/identity.

Verified.

  - npm run check: 0 errors, 0 warnings across 5707 files.
  - Headless screenshots confirm both quietware modes deliver the
    polish: tight header, recognisable disabled-record, softer
    notice border, attached action dock, compact metadata,
    Work Sans status title, clear surface boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 17:14:31 +01:00
parent 4de3acd426
commit ce784e58ab
2 changed files with 90 additions and 86 deletions

View File

@@ -953,42 +953,49 @@
The v0.2 path stays unchanged for non-quietware users. The v0.2 path stays unchanged for non-quietware users.
================================================================= --> ================================================================= -->
<LumotiaPageSkeleton primaryBleed> <LumotiaPageSkeleton>
{#snippet header()} {#snippet header()}
<div class="flex items-center gap-4"> <div class="flex items-center gap-5">
<!-- Record control + status block grouped tightly so they read
as one capture component. Timer pushed to far right. -->
<div class="flex items-center gap-3 flex-1 min-w-0">
<button <button
class="relative flex items-center justify-center w-[72px] h-[72px] rounded-full shrink-0 class="relative flex items-center justify-center w-[64px] h-[64px] rounded-full shrink-0
text-[var(--button-record-fg)] text-[var(--button-record-fg)]
focus-visible:outline-none focus-visible:ring-[var(--wire-width-focus,2px)] focus-visible:outline-none focus-visible:ring-[var(--wire-width-focus,2px)]
focus-visible:ring-[var(--button-record-bg)] focus-visible:ring-offset-2 focus-visible:ring-[var(--button-record-bg)] focus-visible:ring-offset-2
transition-colors duration-150
{page.recording {page.recording
? 'bg-danger animate-pulse-warm' ? 'bg-danger animate-pulse-warm'
: modelLoading : modelLoading
? 'bg-warning opacity-60 cursor-wait' ? 'bg-warning/30 cursor-wait border border-[color-mix(in_srgb,var(--color-caution)_55%,transparent)]'
: !tauriRuntimeAvailable : !tauriRuntimeAvailable
? 'bg-bg-elevated text-text-tertiary cursor-not-allowed' ? 'bg-transparent border-2 border-[color-mix(in_srgb,var(--button-record-bg)_45%,transparent)] cursor-not-allowed opacity-80'
: 'bg-[var(--button-record-bg)] hover:bg-[var(--button-record-border)]'}" : 'bg-[var(--button-record-bg)] hover:bg-[var(--button-record-border)]'}"
onclick={toggleRecording} onclick={toggleRecording}
disabled={modelLoading || !tauriRuntimeAvailable} disabled={modelLoading || !tauriRuntimeAvailable}
aria-label={page.recording ? "Stop recording" : "Start recording"} aria-label={page.recording ? "Stop recording" : (tauriRuntimeAvailable ? "Start recording" : "Record (desktop app only)")}
> >
{#if page.recording} {#if page.recording}
<span class="w-[20px] h-[20px] rounded-[3px] bg-white"></span> <span class="w-[18px] h-[18px] rounded-[3px] bg-white"></span>
{:else if modelLoading} {:else if modelLoading}
<Loader2 size={24} class="animate-spin" aria-hidden="true" /> <Loader2 size={22} class="animate-spin text-[var(--color-caution)]" aria-hidden="true" />
{:else if !tauriRuntimeAvailable}
<!-- Disabled but still reads as "record": muted red dot. -->
<span class="w-[18px] h-[18px] rounded-full bg-[color-mix(in_srgb,var(--button-record-bg)_55%,transparent)]"></span>
{:else} {:else}
<span class="w-[20px] h-[20px] rounded-full bg-white/90"></span> <span class="w-[18px] h-[18px] rounded-full bg-white/90"></span>
{/if} {/if}
</button> </button>
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<h1 class="font-display italic text-[22px] text-text leading-tight"> <h1 class="text-[18px] font-medium text-text leading-tight">
{#if page.recording}Recording… {#if page.recording}Recording…
{:else if modelLoading}Loading model {:else if modelLoading}Loading model
{:else if !tauriRuntimeAvailable}Desktop preview mode {:else if !tauriRuntimeAvailable}Desktop preview mode
{:else}Ready to capture {:else}Ready to capture
{/if} {/if}
</h1> </h1>
<p class="text-[13px] text-text-secondary mt-0.5"> <p class="text-[12px] text-text-secondary mt-0.5">
{#if page.recording}Press to stop, or use the global hotkey. {#if page.recording}Press to stop, or use the global hotkey.
{:else if modelLoading}One moment. {:else if modelLoading}One moment.
{:else if !tauriRuntimeAvailable}Local transcription needs the Lumotia desktop app. {:else if !tauriRuntimeAvailable}Local transcription needs the Lumotia desktop app.
@@ -996,16 +1003,10 @@
{/if} {/if}
</p> </p>
</div> </div>
<div class="text-[20px] font-mono tabular-nums text-text-secondary shrink-0"> </div>
<div class="text-[18px] font-mono tabular-nums text-text-secondary shrink-0">
{page.timerText || "00:00"} {page.timerText || "00:00"}
</div> </div>
{#if page.recording}
<LumotiaStatusPill status="recording" />
{:else if modelLoading}
<LumotiaStatusPill status="transcribing" />
{:else if tauriRuntimeAvailable}
<LumotiaStatusPill status="ready" />
{/if}
</div> </div>
{/snippet} {/snippet}
@@ -1022,7 +1023,11 @@
{/snippet} {/snippet}
{#snippet primary()} {#snippet primary()}
<div class="h-full flex flex-col px-8 py-6"> <div class="h-full flex flex-col">
<!-- Transcript canvas. Action dock below sits inside the same
surface so it reads as transcript actions, not a generic
page footer. -->
<div class="flex-1 flex flex-col px-8 py-8 min-h-0 overflow-hidden">
{#if !transcript.trim()} {#if !transcript.trim()}
<div class="flex-1 flex flex-col items-center justify-center text-center gap-4 text-text-tertiary"> <div class="flex-1 flex flex-col items-center justify-center text-center gap-4 text-text-tertiary">
<Mic size={56} class="opacity-50" aria-hidden="true" /> <Mic size={56} class="opacity-50" aria-hidden="true" />
@@ -1036,10 +1041,7 @@
>{transcript}</div> >{transcript}</div>
{/if} {/if}
</div> </div>
{/snippet} <div class="flex items-center gap-2 justify-end px-6 py-3 border-t border-border-subtle bg-bg-elevated/40">
{#snippet actionBar()}
<div class="flex items-center gap-2 justify-end">
<LumotiaButton variant="tertiary" size="sm" onclick={copyAll} disabled={!transcript.trim()}>Copy</LumotiaButton> <LumotiaButton variant="tertiary" size="sm" onclick={copyAll} disabled={!transcript.trim()}>Copy</LumotiaButton>
<LumotiaButton variant="primary" size="sm" onclick={saveTypedText} disabled={!transcript.trim()}>Save</LumotiaButton> <LumotiaButton variant="primary" size="sm" onclick={saveTypedText} disabled={!transcript.trim()}>Save</LumotiaButton>
<LumotiaButton variant="secondary" size="sm" onclick={manualExtractTasks} disabled={!transcript.trim()}>Extract Tasks</LumotiaButton> <LumotiaButton variant="secondary" size="sm" onclick={manualExtractTasks} disabled={!transcript.trim()}>Extract Tasks</LumotiaButton>
@@ -1057,12 +1059,12 @@
onclick={() => invoke("open_viewer_window").catch(() => {})} onclick={() => invoke("open_viewer_window").catch(() => {})}
>Open Viewer</LumotiaButton> >Open Viewer</LumotiaButton>
</div> </div>
</div>
{/snippet} {/snippet}
{#snippet metadata()} {#snippet metadata()}
<div class="flex items-center justify-between gap-4"> <div class="flex items-center justify-end">
<span>Smart · {settings.modelSize} · {tauriRuntimeAvailable ? "Local only" : "Browser preview"}</span> {settings.formatMode} · {settings.modelSize} · {tauriRuntimeAvailable ? "Local only" : "Browser preview"} · {page.activeProfile === "None" ? "No profile" : page.activeProfile}
<span>{settings.formatMode} · {page.activeProfile === "None" ? "No profile" : page.activeProfile}</span>
</div> </div>
{/snippet} {/snippet}
</LumotiaPageSkeleton> </LumotiaPageSkeleton>

View File

@@ -118,7 +118,9 @@
role={ariaRole} role={ariaRole}
class="rounded-r-lg rounded-l-sm border border-l-4 text-[13px] flex gap-2.5 class="rounded-r-lg rounded-l-sm border border-l-4 text-[13px] flex gap-2.5
{slim ? 'px-3 py-2 items-center' : 'px-4 py-3 items-start'} {slim ? 'px-3 py-2 items-center' : 'px-4 py-3 items-start'}
{tonePal.bar} {tonePal.border} {tonePal.bg} {counterline ? tonePal.shadow : ''} {classes}" {tonePal.bar}
{slim ? 'border-border-subtle' : tonePal.border}
{tonePal.bg} {counterline ? tonePal.shadow : ''} {classes}"
> >
<ToneIcon size={slim ? 14 : 16} class="shrink-0 {slim ? '' : 'mt-0.5'} {tonePal.iconColor}" aria-hidden="true" /> <ToneIcon size={slim ? 14 : 16} class="shrink-0 {slim ? '' : 'mt-0.5'} {tonePal.iconColor}" aria-hidden="true" />
<div class="flex-1 min-w-0 leading-relaxed text-text"> <div class="flex-1 min-w-0 leading-relaxed text-text">