From f20a54da577cea2ab30a162db539edb099e7121c Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Tue, 23 Sep 2025 14:12:16 -0600 Subject: [PATCH] Event titles style tuning. Larger text etc. --- src/components/EventOverlay.vue | 115 ++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 35 deletions(-) diff --git a/src/components/EventOverlay.vue b/src/components/EventOverlay.vue index 9bf58f2..d0ac5f2 100644 --- a/src/components/EventOverlay.vue +++ b/src/components/EventOverlay.vue @@ -7,7 +7,6 @@ :style="{ ...segmentStyle(seg), '--segment-row-height': getSegmentRowHeight(seg), - '--segment-scale-factor': getSegmentScaleFactor(seg), height: getSegmentTotalHeight(seg) }" > @@ -61,6 +60,46 @@ const justDragged = ref(false) const weekOverlayRef = ref(null) const segmentCompression = ref({}) // key -> boolean +// Font metrics ratios (ascent/descent relative to 1em) to align baselines and fit ascenders/descenders +// Hardcoded from measured values: ascent: 0.9, descent: 0.25, total: 1.15 +const fmTotal = 1.15 + +/* +// Measurement function retained for reference; disabled in favor of hardcoded metrics +function measureFontMetrics() { + try { + const root = weekOverlayRef.value || document.documentElement + const cs = getComputedStyle(root) + const weight = '600' + const family = cs.fontFamily || 'system-ui, sans-serif' + const refSize = 100 + const font = `${weight} ${refSize}px ${family}` + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + if (!ctx) return + ctx.font = font + const test = 'HgypQgjpq' + const m = ctx.measureText(test) + const ascPx = (m.fontBoundingBoxAscent ?? m.actualBoundingBoxAscent ?? 0) + const descPx = (m.fontBoundingBoxDescent ?? m.actualBoundingBoxDescent ?? 0) + let asc = ascPx / refSize + let desc = descPx / refSize + if (!isFinite(asc) || asc <= 0 || !isFinite(desc) || desc <= 0) { + asc = 0.9 + desc = 0.25 + } + asc = Math.min(Math.max(asc, 0.6), 0.95) + desc = Math.min(Math.max(desc, 0.1), 0.4) + const total = asc + desc + console.log('[EventOverlay] Measured font metrics', { family, weight, ascent: asc, descent: desc, total }) + fm.value = { asc, desc, total } + } catch (e) { + fm.value = { asc: 0.9, desc: 0.25, total: 1.15 } + try { console.log('[EventOverlay] Using default font metrics fallback', fm.value) } catch {} + } +} +*/ + // Build event segments: each segment is a contiguous day range with at least one bridging event between any adjacent days within it. const eventSegments = computed(() => { // Construct spans across the week @@ -194,11 +233,6 @@ function getSegmentTotalHeight(seg) { return data && typeof data.totalHeight === 'number' ? `${data.totalHeight}px` : 'auto' } -function getSegmentScaleFactor(seg) { - const data = segmentCompression.value[segmentKey(seg)] - return data && typeof data.scaleFactor === 'number' ? data.scaleFactor : 1.0 -} - function recomputeCompression() { const el = weekOverlayRef.value if (!el) return @@ -210,32 +244,32 @@ function recomputeCompression() { const marginTop = 0 // already applied outside height const usable = Math.max(0, available - marginTop) const nextMap = {} + for (const seg of eventSegments.value) { const rowCount = seg.rowsCount || 1 - const gapPx = 2 // gap between grid rows - const totalGaps = Math.max(0, rowCount - 1) * gapPx - const desired = rowCount * baseRowPx + totalGaps + const desired = rowCount * baseRowPx const needsScaling = desired > usable + + // Row height may be reduced to fit segment within available vertical space + let finalRowHeight = baseRowPx if (needsScaling) { - // Calculate the scaled row height that fits within available space - const availableForRows = usable - totalGaps - const scaledRowHeight = Math.max(0, availableForRows) / rowCount - // Never scale larger than the base size - const finalRowHeight = Math.min(scaledRowHeight, baseRowPx) - const scaleFactor = finalRowHeight / baseRowPx - nextMap[segmentKey(seg)] = { - rowHeight: finalRowHeight, - totalHeight: finalRowHeight * rowCount + totalGaps, - scaleFactor: scaleFactor - } - } else { - // Use base size when there's enough space - nextMap[segmentKey(seg)] = { - rowHeight: baseRowPx, - totalHeight: desired, - scaleFactor: 1.0 - } + const scaledRowHeight = usable / rowCount + finalRowHeight = Math.min(scaledRowHeight, baseRowPx) } + + // Event-level scaling not applied for horizontal fitting in this task + const segmentData = { + rowHeight: finalRowHeight, + totalHeight: needsScaling ? usable : desired, + events: {} + } + + // Populate per-event map (reserved for future use) + for (const event of seg.events) { + segmentData.events[event.id + '-' + (event.n || 0)] = {} + } + + nextMap[segmentKey(seg)] = segmentData } segmentCompression.value = nextMap } @@ -576,7 +610,6 @@ function applyRangeDuringDrag(st, startDate, endDate) { } .segment-grid { display: grid; - gap: 2px; align-content: start; pointer-events: none; overflow: hidden; @@ -585,24 +618,32 @@ function applyRangeDuringDrag(st, startDate, endDate) { } .event-span { - padding: calc(0.1em * var(--segment-scale-factor, 1)) calc(0.3em * var(--segment-scale-factor, 1)); - border-radius: calc(1em * var(--segment-scale-factor, 1)); - font-size: calc(clamp(0.45em, 1.8vh, 0.75em) * var(--segment-scale-factor, 1)); + padding: 0; + border-radius: 1rem; + /* Font-size so that ascender+descender exactly fills the row height: + given total = asc+desc at 1em (hardcoded 1.15), font-size = rowHeight / total */ + font-size: calc(var(--segment-row-height, 1.5em) / 1.15); font-weight: 600; cursor: grab; pointer-events: auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + /* Use unitless 1 so line box = font-size; combined with computed font-size above, + this makes the text box (asc+desc) fill the available row height */ line-height: 1; display: flex; - align-items: center; + /* Vertically anchor to top so baselines align across rows; we'll center text vertically by + using cap/descender metrics inside the child */ + align-items: flex-start; + justify-content: center; position: relative; user-select: none; z-index: 10; text-align: center; touch-action: none; backdrop-filter: blur(.05rem); + max-width: 100%; } .event-span.cont-prev { @@ -615,17 +656,21 @@ function applyRangeDuringDrag(st, startDate, endDate) { border-bottom-right-radius: 0; } -/* Inner title wrapper ensures proper ellipsis within flex/grid constraints */ .event-title { display: block; - flex: 1 1 0%; + flex: 0 1 auto; min-width: 0; width: 100%; + height: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; pointer-events: none; + position: relative; + z-index: 1; + max-width: 100%; + line-height: inherit; } /* Resize handles */ @@ -633,7 +678,7 @@ function applyRangeDuringDrag(st, startDate, endDate) { position: absolute; top: 0; bottom: 0; - width: 6px; + width: 1rem; background: transparent; z-index: 2; cursor: ew-resize;