From 54a426b4859e0bd71f0e9eb5ced242c83455f9d2 Mon Sep 17 00:00:00 2001 From: Runar Ingebrigtsen Date: Fri, 6 Feb 2026 01:29:59 +0100 Subject: [PATCH] responsive interface --- src/App.vue | 113 +++++++++++++++++++++++++++++++--------------- src/style.css | 122 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 168 insertions(+), 67 deletions(-) diff --git a/src/App.vue b/src/App.vue index 1954463..73e94d6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -25,9 +25,20 @@ const lastSyncAt = ref(null) const isOnline = ref(navigator.onLine) const isSyncing = ref(false) const syncError = ref(null) +const isMobile = ref(false) +const secondaryLanguage = ref('en') +let mediaQuery: MediaQueryList | null = null +let mediaHandler: ((event: MediaQueryListEvent) => void) | null = null const displayLanguages = computed(() => { const others = languages.filter((lang) => lang.code !== preferredLanguage.value) + if (isMobile.value) { + const secondary = secondaryLanguage.value + if (secondary === preferredLanguage.value) { + return others.slice(0, 1) + } + return others.filter((lang) => lang.code === secondary).slice(0, 1) + } if (preferredLanguage.value === 'en') { return others } @@ -127,6 +138,20 @@ const setLanguageFilter = (code: Language['code'] | null) => { startsWith.value = null } +const ensureSecondaryLanguage = () => { + if (secondaryLanguage.value === preferredLanguage.value) { + const fallback = languages.find((lang) => lang.code !== preferredLanguage.value) + secondaryLanguage.value = fallback ? fallback.code : preferredLanguage.value + } +} + +const swapLanguages = () => { + const current = preferredLanguage.value + preferredLanguage.value = secondaryLanguage.value + secondaryLanguage.value = current + ensureSecondaryLanguage() +} + const syncNow = async () => { if (!db.value) { return @@ -160,6 +185,13 @@ onMounted(async () => { window.addEventListener('online', handleOnline) window.addEventListener('offline', handleOffline) + mediaQuery = window.matchMedia('(max-width: 900px)') + isMobile.value = mediaQuery.matches + mediaHandler = (event: MediaQueryListEvent) => { + isMobile.value = event.matches + } + mediaQuery.addEventListener('change', mediaHandler) + ensureSecondaryLanguage() if (isOnline.value) { await syncNow() @@ -169,6 +201,9 @@ onMounted(async () => { onBeforeUnmount(() => { window.removeEventListener('online', handleOnline) window.removeEventListener('offline', handleOffline) + if (mediaQuery && mediaHandler) { + mediaQuery.removeEventListener('change', mediaHandler) + } }) @@ -238,30 +273,6 @@ onBeforeUnmount(() => { {{ category }} -
- Language - - -
-
- Primary column - -
-
@@ -286,14 +297,48 @@ onBeforeUnmount(() => {
-

Translation Table

-

- {{ filteredEntries.length }} of {{ entries.length }} entries -

+
+

Translation Table

+ + {{ filteredEntries.length }} of {{ entries.length }} entries + +
-
- Missing: {{ filteredMissingCount }} - Complete: {{ filteredEntries.length - filteredMissingCount }} +
+
+ +
+ + + +
+ +
@@ -331,12 +376,6 @@ onBeforeUnmount(() => { >
{{ entry[preferredLanguage] || 'Untitled' }}
- {{ entry[language.code] }} diff --git a/src/style.css b/src/style.css index 31e4f3d..8f0d6ba 100644 --- a/src/style.css +++ b/src/style.css @@ -32,6 +32,7 @@ body { min-height: 100vh; background: radial-gradient(circle at 10% 20%, #eef2ff 0%, #f8fafc 45%, #f1f5f9 100%); color: var(--slate-900); + overflow-x: hidden; } #app { @@ -147,6 +148,7 @@ body { .content { flex: 1; max-width: 1200px; + width: 100%; margin: 0 auto; padding: 24px 20px 40px; display: flex; @@ -154,10 +156,27 @@ body { gap: 24px; } +@media (orientation: landscape) and (max-width: 1023px) { + .header-inner, + .header-sub, + .content { + max-width: 500px; + } +} + +@media (min-width: 1024px) { + .header-inner, + .header-sub, + .content { + max-width: 1200px; + } +} + .search-area { display: flex; flex-direction: column; gap: 16px; + width: 100%; } .search-field { @@ -181,6 +200,7 @@ body { .search-input { width: 100%; + min-width: 0; padding: 18px 44px 18px 50px; border-radius: 18px; border: 1px solid var(--slate-200); @@ -212,6 +232,7 @@ body { flex-wrap: wrap; gap: 12px 20px; align-items: center; + width: 100%; } .filter-group { @@ -316,6 +337,49 @@ body { gap: 12px; } +.language-controls { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.language-control { + display: flex; + align-items: center; + gap: 6px; + justify-content: flex-start; +} + +.language-control.secondary { + margin-left: auto; + justify-content: flex-end; +} + +.swap-link { + border: 1px solid var(--slate-200); + background: transparent; + color: var(--slate-600); + border-radius: 10px; + width: 34px; + height: 34px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + text-decoration: none; +} + +.swap-link svg { + width: 16px; + height: 16px; + fill: none; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; +} + .section-title { margin: 0; font-size: 18px; @@ -323,8 +387,14 @@ body { color: var(--slate-900); } +.section-heading { + display: flex; + align-items: baseline; + gap: 8px; +} + .section-subtitle { - margin: 4px 0 0; + margin: 0; font-size: 12px; color: var(--slate-600); } @@ -356,11 +426,13 @@ body { border: 1px solid var(--slate-200); box-shadow: var(--shadow-sm); overflow: hidden; + width: 100%; } .table-scroll { max-height: 68vh; overflow: auto; + max-width: 100%; } .glossary { @@ -438,35 +510,6 @@ body { color: var(--slate-900); } -.entry-meta { - margin-top: 6px; - display: flex; - gap: 8px; - flex-wrap: wrap; - font-size: 11px; - color: var(--slate-600); -} - -.meta-tag { - text-transform: uppercase; - letter-spacing: 0.1em; - font-weight: 700; -} - -.meta-pill { - padding: 2px 8px; - border-radius: 999px; - font-weight: 700; -} - -.meta-pill.verified { - color: var(--emerald-600); -} - -.meta-pill.unverified { - color: var(--amber-500); -} - .empty-cell { padding: 24px; color: var(--slate-600); @@ -494,6 +537,25 @@ body { align-items: flex-start; } + .language-controls { + width: 100%; + display: grid; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + gap: 8px; + } + + .language-control { + width: 100%; + } + + .language-control.secondary { + margin-left: 0; + } + + .language-control .select { + width: 100%; + } + .table-scroll { max-height: none; }