diff --git a/src/assets/layout.css b/src/assets/layout.css index 6ebc12c..b22ad95 100644 --- a/src/assets/layout.css +++ b/src/assets/layout.css @@ -92,7 +92,7 @@ header { width: 100%; color: var(--muted); cursor: ns-resize; - font-size: 1.2em; + font-size: 1.2rem; } .week-label { @@ -111,7 +111,7 @@ header { .month-name-label { grid-column: -2 / -1; - font-size: 2em; + font-size: 2rem; font-weight: 700; color: var(--muted); display: flex; diff --git a/src/components/BaseDialog.vue b/src/components/BaseDialog.vue index 2613cb7..f4788de 100644 --- a/src/components/BaseDialog.vue +++ b/src/components/BaseDialog.vue @@ -24,7 +24,6 @@ const modalPosition = ref({ x: 0, y: 0 }) const dialogWidth = ref(null) const dialogHeight = ref(null) const hasMoved = ref(false) -const margin = 8 // viewport margin in px to keep dialog from touching edges // Collect incoming non-prop attributes (e.g., class / style from usage site) const attrs = useAttrs() @@ -62,8 +61,8 @@ function handleDrag(event) { const h = dialogHeight.value || modalRef.value?.offsetHeight || 0 const vw = window.innerWidth const vh = window.innerHeight - x = clamp(x, margin, Math.max(margin, vw - w - margin)) - y = clamp(y, margin, Math.max(margin, vh - h - margin)) + x = clamp(x, 0, Math.max(0, vw - w - 0)) + y = clamp(y, 0, Math.max(0, vh - h - 0)) modalPosition.value = { x, y } event.preventDefault() } @@ -97,10 +96,14 @@ const modalStyle = computed(() => { // works even with fragment root. const modalAttrs = computed(() => { const { class: extClass, style: extStyle, ...rest } = attrs + // When dialog has been moved (dragged), internal positioning styles must override external ones + const mergedStyle = hasMoved.value + ? [extStyle, modalStyle.value].filter(Boolean) + : [modalStyle.value, extStyle].filter(Boolean) return { ...rest, class: ['ec-modal', extClass].filter(Boolean), - style: [modalStyle.value, extStyle].filter(Boolean), // external style overrides internal + style: mergedStyle, } }) @@ -120,7 +123,8 @@ function positionNearAnchor() { const anchor = props.anchorEl || anchorRef.value if (!anchor) return const rect = anchor.getBoundingClientRect() - const offsetY = 8 // vertical gap below the anchor + const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize) + const offsetY = 0.5 * rootFontSize // vertical gap below the anchor in rem converted to pixels const w = modalRef.value?.offsetWidth || dialogWidth.value || 320 const h = modalRef.value?.offsetHeight || dialogHeight.value || 200 const vw = window.innerWidth @@ -128,8 +132,8 @@ function positionNearAnchor() { let x = rect.left let y = rect.bottom + offsetY // If anchor is wider than dialog and would overflow right edge, clamp; otherwise keep left align - x = clamp(x, margin, Math.max(margin, vw - w - margin)) - y = clamp(y, margin, Math.max(margin, vh - h - margin)) + x = clamp(x, 0, Math.max(0, vw - w - 0)) + y = clamp(y, 0, Math.max(0, vh - h - 0)) modalPosition.value = { x, y } } @@ -172,8 +176,8 @@ function handleResize() { const vw = window.innerWidth const vh = window.innerHeight modalPosition.value = { - x: clamp(modalPosition.value.x, margin, Math.max(margin, vw - w - margin)), - y: clamp(modalPosition.value.y, margin, Math.max(margin, vh - h - margin)), + x: clamp(modalPosition.value.x, 0, Math.max(0, vw - w - 0)), + y: clamp(modalPosition.value.y, 0, Math.max(0, vh - h - 0)), } } } @@ -212,12 +216,12 @@ onUnmounted(() => { background: color-mix(in srgb, var(--panel) 85%, transparent); backdrop-filter: blur(0.625em); color: var(--ink); - border-radius: 0.6em; - min-height: 23em; - min-width: 26em; - max-width: min(34em, 90vw); - box-shadow: 0 0.6em 1.8em rgba(0, 0, 0, 0.35); - border: 0.0625em solid color-mix(in srgb, var(--muted) 40%, transparent); + border-radius: 0.6rem; + min-height: 23rem; + min-width: 26rem; + max-width: min(34rem, 90vw); + box-shadow: 0 0.6rem 1.8rem rgba(0, 0, 0, 0.35); + border: 0.0625rem solid color-mix(in srgb, var(--muted) 40%, transparent); z-index: 1000; overflow: hidden; } @@ -229,35 +233,35 @@ onUnmounted(() => { .ec-form { display: grid; grid-template-rows: auto 1fr auto; - min-height: 23em; + min-height: 23rem; height: 100%; width: 100%; } .ec-header { cursor: move; user-select: none; - padding: 0.75em 1em 0.5em 1em; + padding: 0.75rem 1rem 0.5rem 1rem; display: flex; justify-content: space-between; align-items: center; - gap: 1em; + gap: 1rem; } .ec-title { margin: 0; - font-size: 1.1em; + font-size: 1.1rem; } .ec-body { display: flex; flex-direction: column; - gap: 1em; - padding: 0 1em 0.5em 1em; + gap: 1rem; + padding: 0 1rem 0.5rem 1rem; overflow: auto; } .ec-footer { - padding: 0.5em 1em 1em 1em; + padding: 0.5rem 1rem 1rem 1rem; display: flex; justify-content: space-between; - gap: 1em; + gap: 1rem; flex-wrap: wrap; } diff --git a/src/components/CalendarDay.vue b/src/components/CalendarDay.vue index 772d0e6..ebff690 100644 --- a/src/components/CalendarDay.vue +++ b/src/components/CalendarDay.vue @@ -188,7 +188,7 @@ const formattedDate = computed(() => { overflow: hidden; max-width: 100%; color: var(--holiday); - font-size: 0.8em; + font-size: 0.8rem; font-weight: 400; line-height: 1.0; padding-inline: 0.15em; diff --git a/src/components/CalendarHeader.vue b/src/components/CalendarHeader.vue index 8954470..7d37870 100644 --- a/src/components/CalendarHeader.vue +++ b/src/components/CalendarHeader.vue @@ -135,7 +135,7 @@ const weekdayNames = computed(() => { text-transform: uppercase; text-align: center; font-weight: 600; - font-size: 1.2em; + font-size: 1.2rem; } .dow.weekend { color: var(--weekend); diff --git a/src/components/CalendarView.vue b/src/components/CalendarView.vue index c35ea05..f51f0b6 100644 --- a/src/components/CalendarView.vue +++ b/src/components/CalendarView.vue @@ -686,7 +686,7 @@ header h1 { .month-label { width: 100%; opacity: 0.8; - font-size: 2em; + font-size: 2rem; font-weight: 700; display: flex; align-items: center; diff --git a/src/components/CalendarWeek.vue b/src/components/CalendarWeek.vue index f9fade7..f51511d 100644 --- a/src/components/CalendarWeek.vue +++ b/src/components/CalendarWeek.vue @@ -67,7 +67,7 @@ const handleEventClick = (payload) => { place-items: center; width: 100%; color: var(--muted); - font-size: 1.2em; + font-size: 1.2rem; font-weight: 500; user-select: none; height: var(--row-h); diff --git a/src/components/EventDialog.vue b/src/components/EventDialog.vue index 727fe86..501188b 100644 --- a/src/components/EventDialog.vue +++ b/src/components/EventDialog.vue @@ -674,7 +674,7 @@ const recurrenceSummary = computed(() => { } .ec-field > span { - font-size: 0.85em; + font-size: 0.85rem; color: var(--muted); } @@ -688,6 +688,7 @@ const recurrenceSummary = computed(() => { width: 100%; background: transparent; color: var(--ink); + font-size: 1rem; } .ec-color-swatches { @@ -725,6 +726,7 @@ const recurrenceSummary = computed(() => { padding: 0.5em 0.8em; border-radius: 0.4em; cursor: pointer; + font-size: 1rem; transition: all 0.2s ease; } @@ -770,7 +772,7 @@ const recurrenceSummary = computed(() => { } .ec-field-label { - font-size: 0.85em; + font-size: 0.85rem; color: var(--muted); } @@ -800,7 +802,7 @@ const recurrenceSummary = computed(() => { } .ec-weekday-text { - font-size: 0.8em; + font-size: 0.8rem; font-weight: 500; text-align: center; } @@ -847,7 +849,7 @@ const recurrenceSummary = computed(() => { align-items: center; gap: 0.5em; flex-wrap: wrap; - font-size: 0.75em; + font-size: 0.75rem; } .freq-select { padding: 0.4rem 0.55rem; @@ -878,6 +880,7 @@ const recurrenceSummary = computed(() => { background: var(--panel-alt); border-radius: 0.45rem; padding: 0.4rem 0.5rem; + font-size: 1rem; transition: border-color 0.18s ease, background-color 0.18s ease, diff --git a/src/components/EventOverlay.vue b/src/components/EventOverlay.vue index cc01165..657fcc1 100644 --- a/src/components/EventOverlay.vue +++ b/src/components/EventOverlay.vue @@ -185,7 +185,7 @@ function segmentKey(seg) { function getSegmentRowHeight(seg) { const data = segmentCompression.value[segmentKey(seg)] - return data && typeof data.rowHeight === 'number' ? `${data.rowHeight}px` : '1.5em' + return data && typeof data.rowHeight === 'number' ? `${data.rowHeight}px` : '1.5rem' } function getSegmentTotalHeight(seg) { @@ -580,7 +580,7 @@ function applyRangeDuringDrag(st, startDate, endDate) { 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-size: calc(var(--segment-row-height, 1.5rem) / 1.15); font-weight: 500; cursor: grab; pointer-events: auto; diff --git a/src/components/HeaderControls.vue b/src/components/HeaderControls.vue index d1cbaf2..b1fc6a4 100644 --- a/src/components/HeaderControls.vue +++ b/src/components/HeaderControls.vue @@ -267,7 +267,7 @@ onBeforeUnmount(() => { padding: 0; margin: 0.5em; cursor: pointer; - font-size: 1em; + font-size: 1rem; font-weight: 700; line-height: 1; display: inline-flex; @@ -356,14 +356,14 @@ onBeforeUnmount(() => { } .today-date { - font-size: 1.5em; + font-size: 1.5rem; white-space: pre-line; text-align: center; } .current-time { font-family: ui-monospace, SF Mono, Consolas, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace; - font-size: 3.6em; + font-size: 3.6rem; white-space: nowrap; text-align: center; cursor: pointer; diff --git a/src/components/Numeric.vue b/src/components/Numeric.vue index c0af7bd..65b06ff 100644 --- a/src/components/Numeric.vue +++ b/src/components/Numeric.vue @@ -245,6 +245,7 @@ function onWheel(e) { justify-content: center; gap: 0.25rem; background: none; + font-size: 1rem; font-variant-numeric: tabular-nums; touch-action: none; } diff --git a/src/components/Search.vue b/src/components/Search.vue index 0cbed26..382b7e7 100644 --- a/src/components/Search.vue +++ b/src/components/Search.vue @@ -486,7 +486,7 @@ function parseGoToDateCandidate(input, refStr) { border-radius: 0.45rem; border: .1rem solid color-mix(in srgb, var(--muted) 35%, transparent); background: color-mix(in srgb, var(--panel) 88%, transparent); - font: inherit; + font-size: 1rem; line-height: 1.1; color: var(--ink); outline: none; diff --git a/src/components/SettingsDialog.vue b/src/components/SettingsDialog.vue index e98455a..52fb1c7 100644 --- a/src/components/SettingsDialog.vue +++ b/src/components/SettingsDialog.vue @@ -258,6 +258,7 @@ select { color: var(--ink); padding: 0.4rem 0.5rem; border-radius: 0.4rem; + font-size: 1rem; } .holiday-row { @@ -273,7 +274,7 @@ select { .state-select { flex: 0 0 auto; - min-width: 120px; + min-width: 4rem; } .footer-row { @@ -297,6 +298,7 @@ select { padding: 0.5rem 0.8rem; border-radius: 0.4rem; cursor: pointer; + font-size: 1rem; } .ec-btn.close-btn { background: var(--panel-alt);