Show the "numolus" wordmark only when the toolbar has room for it

Instead of fixed breakpoints (wicom hidden below 720px, the wordmark below
520px -- while the toolbar already wrapped to two lines much earlier), the
wordmark and "wicom" are shown only while the whole toolbar still fits on
one line with them. Measured with a ResizeObserver; the wordmark's width is
remembered while hidden, so it doesn't flip-flop at the threshold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-28 02:38:14 +02:00
co-authored by Claude Opus 5.5
parent a0680c595e
commit 83f365ce3f
+56 -15
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onBeforeUnmount, ref, watch } from 'vue'; import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { useDocument } from '../store/document'; import { useDocument } from '../store/document';
import { useTheme } from '../composables/useTheme'; import { useTheme } from '../composables/useTheme';
import { useToasts } from '../composables/useToasts'; import { useToasts } from '../composables/useToasts';
@@ -32,6 +32,52 @@ const { mode: themeMode, cycle: cycleTheme } = useTheme();
const toasts = useToasts(); const toasts = useToasts();
const fileMenuOpen = ref(false); const fileMenuOpen = ref(false);
// ---- wordmark only when there's room ----------------------------------
// "numolus" + "wicom" next to the logo are shown only while the whole
// toolbar still fits on one line with them; otherwise just the logo. Measured
// rather than a fixed breakpoint, since what fits depends on the toolbar's
// actual contents. The wordmark's own width is remembered from the last time
// it was visible, so hiding it can't make it look like it would fit again
// (no flip-flopping at the threshold).
const toolbarEl = ref<HTMLElement | null>(null);
const brandExtra = ref<HTMLElement | null>(null);
const showWordmark = ref(true);
let wordmarkWidth = 0;
function fitWordmark() {
const bar = toolbarEl.value;
if (!bar) return;
const cs = getComputedStyle(bar);
const avail = bar.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight);
const gap = parseFloat(cs.columnGap) || 0;
let need = 0;
let items = 0;
for (const child of Array.from(bar.children) as HTMLElement[]) {
if (getComputedStyle(child).display === 'none') continue;
items++;
if (!child.classList.contains('spacer')) need += child.getBoundingClientRect().width;
}
need += gap * Math.max(0, items - 1);
if (showWordmark.value && brandExtra.value) {
// plus the gap to the logo, which goes away with it
const brandGap = parseFloat(getComputedStyle(brandExtra.value.parentElement!).columnGap) || 0;
wordmarkWidth = brandExtra.value.getBoundingClientRect().width + brandGap;
} else {
need += wordmarkWidth;
}
showWordmark.value = need <= avail + 0.5;
}
let resizeObs: ResizeObserver | null = null;
onMounted(() => {
resizeObs = new ResizeObserver(() => fitWordmark());
if (toolbarEl.value) resizeObs.observe(toolbarEl.value);
fitWordmark();
// web fonts arriving late change the measured widths
void document.fonts?.ready.then(fitWordmark);
});
onBeforeUnmount(() => resizeObs?.disconnect());
const fileMenuWrap = ref<HTMLElement | null>(null); const fileMenuWrap = ref<HTMLElement | null>(null);
// Close the Datei menu on Escape or on a press anywhere outside it. Capture // Close the Datei menu on Escape or on a press anywhere outside it. Capture
@@ -249,13 +295,13 @@ function onSample() {
</script> </script>
<template> <template>
<header class="toolbar"> <header ref="toolbarEl" class="toolbar">
<div class="brand" title="Numolus — wicom, Vaduz (FL)"> <div class="brand" title="Numolus — wicom, Vaduz (FL)">
<TheLogo class="brand-mark" /> <TheLogo class="brand-mark" />
<span v-show="showWordmark" ref="brandExtra" class="brand-extra">
<span class="wordmark">numolus</span> <span class="wordmark">numolus</span>
<button class="by" type="button" @click="aboutOpen = true"> <button class="by" type="button" @click="aboutOpen = true">wicom</button>
wicom </span>
</button>
</div> </div>
<button <button
class="gear" class="gear"
@@ -517,6 +563,11 @@ function onSample() {
height: 24px; height: 24px;
flex: none; flex: none;
} }
.brand-extra {
display: flex;
align-items: center;
gap: 9px;
}
.wordmark { .wordmark {
font-size: 17px; font-size: 17px;
font-weight: 600; font-weight: 600;
@@ -539,16 +590,6 @@ function onSample() {
.by:hover { .by:hover {
color: var(--accent); color: var(--accent);
} }
@media (max-width: 720px) {
.by {
display: none;
}
}
@media (max-width: 520px) {
.wordmark {
display: none;
}
}
/* --- Über-Dialog --- */ /* --- Über-Dialog --- */
.about-lead { .about-lead {