refactor(frontend): CSS pay-down, derive batch (milestone 302 step 4) — page-header, error-msg/state-msg/empty-msg, empty-title/empty-sub, required, field-hint recipes into components.css; form-buttons into rules-shared.css; scoped copies trimmed to remainders/overrides; the three views' .input becomes fs-input + width/box-sizing remainder (canon #2336)
CI & Build / TypeScript typecheck (push) Failing after 2s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 24s
CI & Build / Python tests (push) Successful in 1m2s
CI & Build / Build & push image (push) Skipped

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 17:30:43 -04:00
co-authored by Claude Fable 5
parent c28c87c39e
commit 6fb0cb38a5
10 changed files with 93 additions and 131 deletions
+21
View File
@@ -330,3 +330,24 @@
.fs-input::placeholder { color: var(--fs-text-tertiary); }
.fs-input:focus { outline: none; box-shadow: var(--fs-focus-ring); }
.fs-input:disabled { opacity: var(--fs-disabled-opacity); cursor: not-allowed; }
/* Page scaffold + feedback text recipes (milestone 302, note 2917): name
families the consumer map showed to be one recipe living in many views.
A view keeps only its deviation as a scoped remainder/override. */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.page-header h1 { margin: 0; }
.error-msg { color: var(--fs-error); font-size: 0.9rem; }
.state-msg { color: var(--fs-text-tertiary); font-size: 0.9rem; }
.empty-msg { color: var(--fs-text-tertiary); font-size: 0.875rem; }
.empty-title { font-size: 1rem; font-weight: 500; color: var(--fs-text-secondary); margin: 0 0 0.35rem; }
.empty-sub { font-size: 0.85rem; color: var(--fs-text-tertiary); margin: 0 0 1rem; }
.required { color: var(--fs-error); }
.field-hint { margin: 0.3rem 0 0; font-size: 0.8rem; color: var(--fs-text-tertiary); }
+1
View File
@@ -12,3 +12,4 @@
font-style: italic;
margin: 0 0 0.5rem 0;
}
.form-buttons { display: flex; gap: 0.5rem; }
@@ -185,5 +185,4 @@ onMounted(load);
font-size: 0.9rem;
}
.inception-actions { display: flex; justify-content: flex-end; margin-top: 0.5rem; }
.error-msg { color: var(--fs-error); font-size: 0.9rem; }
</style>
+3 -3
View File
@@ -484,10 +484,10 @@ async function confirmDelete() {
border: 1px dashed var(--fs-border-color);
border-radius: var(--fs-radius-lg);
}
.empty-title { margin: 0; font-weight: 500; color: var(--fs-text-primary); }
.empty-sub { margin: 0 0 0.5rem; font-size: 0.82rem; color: var(--fs-text-tertiary); max-width: 32ch; }
/* remainders over the shared recipes (components.css, m302) */
.empty-title { margin: 0; color: var(--fs-text-primary); }
.empty-sub { margin: 0 0 0.5rem; font-size: 0.82rem; max-width: 32ch; }
.error-msg { color: var(--fs-error); font-size: 0.9rem; }
/* ── Skeleton ─────────────────────────────────────────────────── */
@keyframes skel-shine { to { background-position: 200% center; } }
+16 -16
View File
@@ -557,14 +557,14 @@ function isSelfContainedColour(value: string): boolean {
<div class="field">
<label class="field-label" for="first-title">Title</label>
<input
id="first-title" v-model="newTitle" class="input" type="text"
id="first-title" v-model="newTitle" class="fs-input input" type="text"
placeholder="Your house style" @keyup.enter="submitCreate"
/>
</div>
<div class="field">
<label class="field-label" for="first-desc">Description</label>
<input
id="first-desc" v-model="newDescription" class="input" type="text"
id="first-desc" v-model="newDescription" class="fs-input input" type="text"
placeholder="What it covers"
/>
</div>
@@ -612,20 +612,20 @@ function isSelfContainedColour(value: string): boolean {
<div class="field">
<label class="field-label" for="new-title">Title</label>
<input
id="new-title" v-model="newTitle" class="input" type="text"
id="new-title" v-model="newTitle" class="fs-input input" type="text"
placeholder="A house style, or one app in it" @keyup.enter="submitCreate"
/>
</div>
<div class="field">
<label class="field-label" for="new-desc">Description</label>
<input
id="new-desc" v-model="newDescription" class="input" type="text"
id="new-desc" v-model="newDescription" class="fs-input input" type="text"
placeholder="What it covers"
/>
</div>
<div class="field">
<label class="field-label" for="new-parent">Inherits from</label>
<select id="new-parent" v-model="newParentId" class="input">
<select id="new-parent" v-model="newParentId" class="fs-input input">
<option :value="null">Nothing — this is a family system</option>
<option v-for="s in systems" :key="s.id" :value="s.id">{{ s.title }}</option>
</select>
@@ -659,16 +659,16 @@ function isSelfContainedColour(value: string): boolean {
<div class="field">
<label class="field-label" for="edit-title">Title</label>
<input id="edit-title" v-model="editTitle" class="input" type="text" />
<input id="edit-title" v-model="editTitle" class="fs-input input" type="text" />
</div>
<div class="field">
<label class="field-label" for="edit-desc">Description</label>
<input id="edit-desc" v-model="editDescription" class="input" type="text" />
<input id="edit-desc" v-model="editDescription" class="fs-input input" type="text" />
</div>
<div class="field">
<label class="field-label" for="edit-guidance">Guidance</label>
<textarea
id="edit-guidance" v-model="editGuidance" class="input" rows="5"
id="edit-guidance" v-model="editGuidance" class="fs-input input" rows="5"
placeholder="Aesthetic, voice and tone, what's deliberately out of scope"
></textarea>
<p class="field-hint">
@@ -678,7 +678,7 @@ function isSelfContainedColour(value: string): boolean {
</div>
<div class="field">
<label class="field-label" for="edit-parent">Inherits from</label>
<select id="edit-parent" v-model="editParentId" class="input">
<select id="edit-parent" v-model="editParentId" class="fs-input input">
<option :value="null">Nothing — this is a family system</option>
<option v-for="s in parentOptions" :key="s.id" :value="s.id">{{ s.title }}</option>
</select>
@@ -887,19 +887,19 @@ function isSelfContainedColour(value: string): boolean {
<div class="field">
<label class="field-label" for="token-name">Name</label>
<input
id="token-name" v-model="tokenName" class="input mono" type="text"
id="token-name" v-model="tokenName" class="fs-input input mono" type="text"
placeholder="--surface-page"
/>
</div>
<div class="field-row">
<div class="field">
<label class="field-label" for="token-group">Group</label>
<input id="token-group" v-model="tokenGroup" class="input" type="text" placeholder="surface" />
<input id="token-group" v-model="tokenGroup" class="fs-input input" type="text" placeholder="surface" />
</div>
<div class="field">
<label class="field-label" for="token-purpose">Purpose</label>
<input
id="token-purpose" v-model="tokenPurpose" class="input" type="text"
id="token-purpose" v-model="tokenPurpose" class="fs-input input" type="text"
placeholder="page background, deepest surface"
/>
</div>
@@ -908,7 +908,7 @@ function isSelfContainedColour(value: string): boolean {
<div class="field">
<label class="field-label" for="token-rationale">Why this value</label>
<input
id="token-rationale" v-model="tokenRationale" class="input" type="text"
id="token-rationale" v-model="tokenRationale" class="fs-input input" type="text"
placeholder="Matches the primary action colour, deliberately"
/>
<p class="field-hint">
@@ -920,7 +920,7 @@ function isSelfContainedColour(value: string): boolean {
<div class="field">
<label class="field-label" for="token-supersedes">Use instead of</label>
<input
id="token-supersedes" v-model="tokenSupersedes" class="input mono" type="text"
id="token-supersedes" v-model="tokenSupersedes" class="fs-input input mono" type="text"
placeholder="#fff, #ffffff"
/>
<p class="field-hint">
@@ -941,8 +941,8 @@ function isSelfContainedColour(value: string): boolean {
</template>
</p>
<div v-for="(row, i) in tokenModes" :key="i" class="mode-row">
<input v-model="row.mode" class="input mono mode-key" type="text" placeholder="base" />
<input v-model="row.value" class="input mono" type="text" placeholder="#14171a" />
<input v-model="row.mode" class="fs-input input mono mode-key" type="text" placeholder="base" />
<input v-model="row.value" class="fs-input input mono" type="text" placeholder="#14171a" />
<span
v-if="isSelfContainedColour(row.value)" class="swatch"
:style="{ background: row.value }" aria-hidden="true"
+2 -17
View File
@@ -336,15 +336,6 @@ function overallPct(project: Project): { total: number; pct: number } {
</template>
<style scoped>
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.page-header h1 {
margin: 0;
}
/* Moss action-primary per Hybrid — list-view utility action,
not a brand moment. Empty-state .empty-action below keeps accent. */
@@ -376,20 +367,17 @@ function overallPct(project: Project): { total: number; pct: number } {
font-weight: 500;
}
.loading-msg,
.error-msg {
.loading-msg {
color: var(--fs-text-tertiary);
font-size: 0.9rem;
margin-top: 1rem;
}
.error-msg {
color: var(--fs-error);
margin-top: 1rem;
}
.empty-state-rich { text-align: center; padding: 3rem 1rem; color: var(--fs-text-tertiary); }
.empty-icon { font-size: 2.5rem; margin-bottom: 0.75rem; opacity: 0.3; }
.empty-title { font-size: 1rem; font-weight: 500; color: var(--fs-text-secondary); margin: 0 0 0.35rem; }
.empty-sub { font-size: 0.85rem; margin: 0 0 1rem; }
.empty-action { display: inline-block; padding: 0.4rem 1rem; border: 1px solid var(--fs-action-primary); border-radius: var(--fs-radius-sm); color: var(--fs-action-primary); background: none; cursor: pointer; font-size: 0.85rem; transition: background 0.15s, color 0.15s; }
.empty-action:hover { background: var(--fs-action-primary); color: var(--fs-text-on-action); }
@@ -592,9 +580,6 @@ function overallPct(project: Project): { total: number; pct: number } {
font-weight: 500;
color: var(--fs-text-primary);
}
.required {
color: var(--fs-error);
}
.modal-input,
.modal-textarea {
padding: 0.45rem 0.7rem;
+37 -37
View File
@@ -1236,7 +1236,7 @@ async function deleteUser(userId: number) {
id="user-timezone"
v-model="userTimezone"
type="text"
class="input"
class="fs-input input"
placeholder="e.g. America/New_York"
/>
<button class="btn-secondary" type="button" @click="detectTimezone">Detect</button>
@@ -1263,7 +1263,7 @@ async function deleteUser(userId: number) {
type="number"
min="0"
step="1"
class="input"
class="fs-input input"
style="max-width: 8rem"
/>
<p class="field-hint">Set to <strong>0</strong> to keep deleted items forever (never auto-purge).</p>
@@ -1301,7 +1301,7 @@ async function deleteUser(userId: number) {
min="0"
max="1"
step="0.05"
class="input"
class="fs-input input"
style="max-width: 8rem"
/>
<p class="field-hint">Minimum similarity to surface a note. Higher = stricter (fewer, more certain). Deliberately above the 0.45 used for searches you trigger yourself.</p>
@@ -1315,7 +1315,7 @@ async function deleteUser(userId: number) {
min="1"
max="10"
step="1"
class="input"
class="fs-input input"
style="max-width: 8rem"
/>
<p class="field-hint">Ceiling on titles surfaced at once (110).</p>
@@ -1345,7 +1345,7 @@ async function deleteUser(userId: number) {
min="0"
max="1"
step="0.01"
class="input"
class="fs-input input"
style="max-width: 8rem"
/>
<p class="field-hint">
@@ -1374,7 +1374,7 @@ async function deleteUser(userId: number) {
min="0"
max="1"
step="0.01"
class="input"
class="fs-input input"
style="max-width: 8rem"
/>
<p class="field-hint">
@@ -1394,7 +1394,7 @@ async function deleteUser(userId: number) {
min="0"
max="1"
step="0.01"
class="input"
class="fs-input input"
style="max-width: 8rem"
/>
<p class="field-hint">
@@ -1415,7 +1415,7 @@ async function deleteUser(userId: number) {
min="0"
max="1"
step="0.01"
class="input"
class="fs-input input"
style="max-width: 8rem"
/>
<p class="field-hint">
@@ -1457,7 +1457,7 @@ async function deleteUser(userId: number) {
v-model="newEmail"
type="email"
placeholder="you@example.com"
class="input"
class="fs-input input"
/>
</div>
<div class="field">
@@ -1467,7 +1467,7 @@ async function deleteUser(userId: number) {
v-model="emailPassword"
type="password"
autocomplete="current-password"
class="input"
class="fs-input input"
/>
<p class="field-hint">Required to confirm the change.</p>
</div>
@@ -1491,7 +1491,7 @@ async function deleteUser(userId: number) {
v-model="currentPassword"
type="password"
autocomplete="current-password"
class="input"
class="fs-input input"
/>
</div>
<div class="field">
@@ -1501,7 +1501,7 @@ async function deleteUser(userId: number) {
v-model="newPassword"
type="password"
autocomplete="new-password"
class="input"
class="fs-input input"
/>
<p class="field-hint">Must be at least 8 characters</p>
</div>
@@ -1512,7 +1512,7 @@ async function deleteUser(userId: number) {
v-model="confirmNewPassword"
type="password"
autocomplete="new-password"
class="input"
class="fs-input input"
:class="{ 'input-error': confirmNewPassword && newPassword !== confirmNewPassword }"
/>
<p v-if="confirmNewPassword && newPassword !== confirmNewPassword" class="error-hint">
@@ -1555,20 +1555,20 @@ async function deleteUser(userId: number) {
<div class="assistant-grid">
<div class="field">
<label>Display Name</label>
<input v-model="profile.display_name" type="text" class="input" placeholder="e.g. Alex" />
<input v-model="profile.display_name" type="text" class="fs-input input" placeholder="e.g. Alex" />
<p class="field-hint">How the assistant addresses you.</p>
</div>
<div class="field">
<label>Job Title</label>
<input v-model="profile.job_title" type="text" class="input" placeholder="e.g. Product Manager" />
<input v-model="profile.job_title" type="text" class="fs-input input" placeholder="e.g. Product Manager" />
</div>
<div class="field">
<label>Industry</label>
<input v-model="profile.industry" type="text" class="input" placeholder="e.g. Technology" />
<input v-model="profile.industry" type="text" class="fs-input input" placeholder="e.g. Technology" />
</div>
<div class="field">
<label>Expertise Level</label>
<select v-model="profile.expertise_level" class="input">
<select v-model="profile.expertise_level" class="fs-input input">
<option value="novice">Novice explain things simply</option>
<option value="intermediate">Intermediate balanced explanations</option>
<option value="expert">Expert assume deep knowledge</option>
@@ -1587,7 +1587,7 @@ async function deleteUser(userId: number) {
<div class="assistant-grid">
<div class="field">
<label>Response Style</label>
<select v-model="profile.response_style" class="input">
<select v-model="profile.response_style" class="fs-input input">
<option value="concise">Concise short and direct</option>
<option value="balanced">Balanced default</option>
<option value="detailed">Detailed thorough explanations</option>
@@ -1595,7 +1595,7 @@ async function deleteUser(userId: number) {
</div>
<div class="field">
<label>Tone</label>
<select v-model="profile.tone" class="input">
<select v-model="profile.tone" class="fs-input input">
<option value="casual">Casual friendly and relaxed</option>
<option value="professional">Professional formal and precise</option>
<option value="technical">Technical jargon-friendly</option>
@@ -1637,11 +1637,11 @@ async function deleteUser(userId: number) {
<div class="assistant-grid" style="margin-top:0.75rem">
<div class="field">
<label>Start Time</label>
<input v-model="profile.work_schedule.start" type="time" class="input" />
<input v-model="profile.work_schedule.start" type="time" class="fs-input input" />
</div>
<div class="field">
<label>End Time</label>
<input v-model="profile.work_schedule.end" type="time" class="input" />
<input v-model="profile.work_schedule.end" type="time" class="fs-input input" />
</div>
</div>
<div class="actions">
@@ -1697,7 +1697,7 @@ async function deleteUser(userId: number) {
<input
v-model="searchQuery"
type="text"
class="input"
class="fs-input input"
placeholder="Enter a search query..."
@keydown="onSearchKeydown"
/>
@@ -1763,17 +1763,17 @@ async function deleteUser(userId: number) {
<div v-if="connFormOpen" class="smtp-grid">
<div class="field">
<label for="conn-kind">Forge</label>
<select id="conn-kind" v-model="connForm.kind" class="input">
<select id="conn-kind" v-model="connForm.kind" class="fs-input input">
<option v-for="k in forgeKinds" :key="k" :value="k">{{ k }}</option>
</select>
</div>
<div class="field">
<label for="conn-base-url">Base URL</label>
<input id="conn-base-url" v-model="connForm.base_url" type="text" placeholder="https://git.example.com" class="input" />
<input id="conn-base-url" v-model="connForm.base_url" type="text" placeholder="https://git.example.com" class="fs-input input" />
</div>
<div class="field">
<label for="conn-token">API Token (read scope)</label>
<input id="conn-token" v-model="connForm.token" type="password" class="input" />
<input id="conn-token" v-model="connForm.token" type="password" class="fs-input input" />
</div>
</div>
<div class="actions">
@@ -2063,7 +2063,7 @@ async function deleteUser(userId: number) {
v-model="baseUrl"
type="url"
placeholder="https://notes.example.com"
class="input"
class="fs-input input"
/>
</div>
<div class="actions">
@@ -2088,7 +2088,7 @@ async function deleteUser(userId: number) {
v-model="adminMarketplaceUrl"
type="url"
placeholder="https://git.example.com/you/Scribe.git"
class="input"
class="fs-input input"
/>
</div>
<div class="actions">
@@ -2115,7 +2115,7 @@ async function deleteUser(userId: number) {
</div>
<div class="field url-field">
<label for="db-maint-hour">Run hour (UTC)</label>
<select id="db-maint-hour" v-model.number="dbMaintHour" class="input">
<select id="db-maint-hour" v-model.number="dbMaintHour" class="fs-input input">
<option v-for="h in 24" :key="h - 1" :value="h - 1">
{{ String(h - 1).padStart(2, '0') }}:00
</option>
@@ -2185,27 +2185,27 @@ async function deleteUser(userId: number) {
<div class="smtp-grid">
<div class="field">
<label for="smtp-host">SMTP Host</label>
<input id="smtp-host" v-model="smtp.smtp_host" type="text" placeholder="smtp.example.com" class="input" />
<input id="smtp-host" v-model="smtp.smtp_host" type="text" placeholder="smtp.example.com" class="fs-input input" />
</div>
<div class="field">
<label for="smtp-port">Port</label>
<input id="smtp-port" v-model="smtp.smtp_port" type="text" placeholder="587" class="input" />
<input id="smtp-port" v-model="smtp.smtp_port" type="text" placeholder="587" class="fs-input input" />
</div>
<div class="field">
<label for="smtp-username">Username</label>
<input id="smtp-username" v-model="smtp.smtp_username" type="text" class="input" />
<input id="smtp-username" v-model="smtp.smtp_username" type="text" class="fs-input input" />
</div>
<div class="field">
<label for="smtp-password">Password</label>
<input id="smtp-password" v-model="smtp.smtp_password" type="password" class="input" />
<input id="smtp-password" v-model="smtp.smtp_password" type="password" class="fs-input input" />
</div>
<div class="field">
<label for="smtp-from-address">From Address</label>
<input id="smtp-from-address" v-model="smtp.smtp_from_address" type="email" placeholder="noreply@example.com" class="input" />
<input id="smtp-from-address" v-model="smtp.smtp_from_address" type="email" placeholder="noreply@example.com" class="fs-input input" />
</div>
<div class="field">
<label for="smtp-from-name">From Name</label>
<input id="smtp-from-name" v-model="smtp.smtp_from_name" type="text" placeholder="Fabled Scribe" class="input" />
<input id="smtp-from-name" v-model="smtp.smtp_from_name" type="text" placeholder="Fabled Scribe" class="fs-input input" />
</div>
</div>
<div class="checkbox-field">
@@ -2228,7 +2228,7 @@ async function deleteUser(userId: number) {
v-model="testRecipient"
type="email"
placeholder="test@example.com"
class="input"
class="fs-input input"
/>
<button class="btn-primary" @click="sendTestEmail" :disabled="sendingTest || !testRecipient.trim()">
{{ sendingTest ? "Sending..." : "Send Test" }}
@@ -2249,7 +2249,7 @@ async function deleteUser(userId: number) {
<div class="smtp-grid">
<div class="field">
<label for="forge-webhook-secret">Webhook Secret</label>
<input id="forge-webhook-secret" v-model="forgeWebhookSecret" type="password" class="input" />
<input id="forge-webhook-secret" v-model="forgeWebhookSecret" type="password" class="fs-input input" />
</div>
</div>
<div class="actions">
@@ -2295,7 +2295,7 @@ async function deleteUser(userId: number) {
v-model="inviteEmail"
type="email"
placeholder="Email address"
class="input invite-input"
class="fs-input input invite-input"
required
:disabled="sendingInvite"
/>
+1 -7
View File
@@ -220,14 +220,8 @@ async function confirmDelete() {
color: var(--fs-accent);
}
.state-msg {
color: var(--fs-text-tertiary);
font-size: 0.9rem;
margin-top: 1rem;
}
.state-msg,
.error-msg {
color: var(--fs-error);
font-size: 0.9rem;
margin-top: 1rem;
}
+11 -33
View File
@@ -227,7 +227,7 @@ function cancel() {
ref="nameRef"
v-model="form.name"
type="text"
class="input mono"
class="fs-input input mono"
placeholder="useDebouncedRef"
@keydown.escape="cancel"
/>
@@ -239,7 +239,7 @@ function cancel() {
id="sn-when"
v-model="form.when_to_use"
type="text"
class="input"
class="fs-input input"
placeholder="Debounce a reactive ref that updates too often"
@keydown.escape="cancel"
/>
@@ -253,7 +253,7 @@ function cancel() {
id="sn-lang"
v-model="form.language"
type="text"
class="input"
class="fs-input input"
placeholder="typescript"
@keydown.escape="cancel"
/>
@@ -264,7 +264,7 @@ function cancel() {
id="sn-sig"
v-model="form.signature"
type="text"
class="input mono"
class="fs-input input mono"
placeholder="useDebouncedRef(value, ms)"
@keydown.escape="cancel"
/>
@@ -277,9 +277,9 @@ function cancel() {
<span class="hint-inline"> where the reference implementation(s) live; a merged snippet keeps every call site</span>
</legend>
<div v-for="(loc, i) in locations" :key="i" class="loc-row">
<input v-model="loc.repo" type="text" class="input mono" placeholder="repo" aria-label="Repo" @keydown.escape="cancel" />
<input v-model="loc.path" type="text" class="input mono" placeholder="path" aria-label="Path" @keydown.escape="cancel" />
<input v-model="loc.symbol" type="text" class="input mono" placeholder="symbol" aria-label="Symbol" @keydown.escape="cancel" />
<input v-model="loc.repo" type="text" class="fs-input input mono" placeholder="repo" aria-label="Repo" @keydown.escape="cancel" />
<input v-model="loc.path" type="text" class="fs-input input mono" placeholder="path" aria-label="Path" @keydown.escape="cancel" />
<input v-model="loc.symbol" type="text" class="fs-input input mono" placeholder="symbol" aria-label="Symbol" @keydown.escape="cancel" />
<button
type="button"
class="loc-remove"
@@ -296,7 +296,7 @@ function cancel() {
<textarea
id="sn-code"
v-model="form.code"
class="input mono code-area"
class="fs-input input mono code-area"
rows="14"
spellcheck="false"
placeholder="Paste the reusable implementation…"
@@ -309,7 +309,7 @@ function cancel() {
id="sn-tags"
v-model="tagsText"
type="text"
class="input"
class="fs-input input"
placeholder="composable, ui (comma-separated)"
@keydown.escape="cancel"
/>
@@ -383,14 +383,6 @@ function cancel() {
color: var(--fs-accent);
}
.state-msg {
color: var(--fs-text-tertiary);
font-size: 0.9rem;
}
.error-msg {
color: var(--fs-error);
font-size: 0.9rem;
}
.form {
display: flex;
@@ -417,9 +409,6 @@ function cancel() {
font-weight: 500;
color: var(--fs-text-primary);
}
.required {
color: var(--fs-error);
}
.hint {
font-size: 0.75rem;
color: var(--fs-text-tertiary);
@@ -430,21 +419,10 @@ function cancel() {
color: var(--fs-text-tertiary);
}
/* remainder over .fs-input (components.css, canon #2336; m302) */
.input {
padding: 0.5rem 0.7rem;
border: 1px solid var(--fs-border-color);
border-radius: var(--fs-radius-sm);
background: var(--fs-surface-page);
color: var(--fs-text-primary);
font-size: 0.9rem;
font-family: inherit;
box-sizing: border-box;
width: 100%;
}
.input:focus {
outline: none;
border-color: var(--fs-accent);
box-shadow: var(--fs-focus-ring);
box-sizing: border-box;
}
.mono {
font-family: var(--fs-font-mono);
+1 -17
View File
@@ -520,13 +520,7 @@ function usageTitle(s: SnippetListItem): string {
<style src="@/assets/dup-report.css" />
<style scoped>
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.35rem;
}
.page-header h1 {
margin: 0;
margin-bottom: 0.35rem; /* tighter than the shared recipe: .page-sub follows */
}
.page-sub {
margin: 0 0 1.25rem;
@@ -616,8 +610,6 @@ function usageTitle(s: SnippetListItem): string {
}
.error-msg {
color: var(--fs-error);
font-size: 0.9rem;
margin-top: 1rem;
}
@@ -632,15 +624,7 @@ function usageTitle(s: SnippetListItem): string {
margin-bottom: 0.75rem;
opacity: 0.35;
}
.empty-title {
font-size: 1rem;
font-weight: 500;
color: var(--fs-text-secondary);
margin: 0 0 0.35rem;
}
.empty-sub {
font-size: 0.85rem;
margin: 0 0 1rem;
max-width: 44ch;
margin-inline: auto;
line-height: 1.5;