From d37220d010885f5c1f8bcea145119fb22171d495 Mon Sep 17 00:00:00 2001 From: Konrad Kollnig <5175206+kasnder@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:56:51 +0200 Subject: [PATCH] Fix responsive iOS layout and user-facing search copy --- lib/i18n.js | 5 +---- public/css/styles.css | 25 +++++++++++++++++++------ routes/index.js | 6 +++--- test/websiteViews.test.js | 8 ++++++++ views/about.pug | 1 + views/directory.pug | 1 + views/form.pug | 4 ++-- views/layout.pug | 2 +- views/statistics.pug | 6 +++++- 9 files changed, 41 insertions(+), 17 deletions(-) diff --git a/lib/i18n.js b/lib/i18n.js index 409ad6c..988b7b3 100644 --- a/lib/i18n.js +++ b/lib/i18n.js @@ -19,7 +19,6 @@ const da = { 'Statistics': 'Statistik', 'About': 'Om', 'Search apps': 'Søg efter apps', - 'Search apps placeholder': 'Søg efter apps', 'Footer navigation': 'Sidefodsnavigation', 'TrackerControl project': 'TrackerControl-projektet', 'Contact': 'Kontakt', @@ -77,7 +76,6 @@ const da = { 'reports may be out of date, incomplete or incorrect. They describe an analysed app version on the date shown, and automated analysis makes mistakes. ': 'rapporter kan være forældede, ufuldstændige eller forkerte. De beskriver en analyseret appversion på den viste dato, og automatiske analyser kan tage fejl. ', 'Read the full disclaimer': 'Læs hele ansvarsfraskrivelsen', - 'Search apps heading': 'Søg efter apps', 'Search results': 'Søgeresultater', 'Search': 'Søg', 'No apps found for “{{term}}”.': 'Ingen apps fundet for “{{term}}”.', @@ -89,8 +87,6 @@ const da = { 'No matching free app was returned by the UK App Store search. Check the spelling or try a shorter name.': 'Søgningen i den britiske App Store gav ingen matchende gratis app. Kontrollér stavningen, eller prøv et kortere navn.', 'Free apps without a published report open a confirmation page first. A request is queued only after confirmation.': 'Gratis apps uden en udgivet rapport åbner først en bekræftelsesside. En anmodning kommer kun i kø efter bekræftelse.', 'Search again': 'Søg igen', - 'Search app': 'Søg efter app', - 'Search apps title': 'Søg efter apps', 'App report': 'App-rapport', 'Untitled app': 'Unavngiven app', @@ -260,6 +256,7 @@ const da = { '{{count}} entries': '{{count}} poster', 'Tracker directory table': 'Trackerkatalogtabel', 'Company directory table': 'Virksomhedskatalogtabel', + 'Scroll sideways to see all columns.': 'Rul sidelæns for at se alle kolonner.', 'Rank': 'Rang', 'Tracker': 'Tracker', 'Company': 'Virksomhed', diff --git a/public/css/styles.css b/public/css/styles.css index 196012c..d9c7803 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -69,7 +69,7 @@ button { cursor: pointer; } padding-bottom: 1rem; } -.platform-header { border-bottom: 1px solid var(--border); } +.platform-header { background: var(--warm); border-bottom: 1px solid var(--border); } .platform-nav { display: flex; flex-wrap: wrap; gap: .6rem 1.4rem; padding-top: .65rem; padding-bottom: .65rem; font-size: .78rem; } .platform-nav a { color: var(--muted); text-decoration: none; } .platform-nav a:hover { color: var(--brand); text-decoration: underline; } @@ -155,7 +155,7 @@ h3 { font-size: 1.35rem; line-height: 1.2; margin-bottom: 1rem; } text-transform: uppercase; } -.hero { padding: 5rem 0 5.4rem; } +.hero { padding: 3rem 0 5.4rem; } .hero h1 { max-width: 760px; } .hero-intro { color: #4b3d3d; font-size: clamp(1.08rem, 2vw, 1.3rem); line-height: 1.45; margin-bottom: 3.3rem; max-width: 540px; } @@ -217,6 +217,7 @@ input::placeholder { color: #796d6d; } .metrics .section-note { grid-column: 1 / -1; margin-top: .4rem; } .home-section, .stats-section, .about-section, .lookup-apps, .lookup-trackers { border-top: 1px solid var(--border); padding: 3.4rem 0 0; margin-top: 3.4rem; } +.metrics + .home-section { border-top: 0; margin-top: 0; } .section-heading { margin-bottom: 1.7rem; } .section-heading .eyebrow { margin-bottom: .55rem; } .section-heading h2 { margin-bottom: 0; } @@ -225,6 +226,8 @@ input::placeholder { color: #796d6d; } .bar-list { display: grid; gap: 1rem; } .bar-item { min-width: 0; } +.bar-item--tiny { position: relative; } +.bar-item--tiny::after { background: var(--brand); bottom: 0; content: ''; height: 8px; left: 0; pointer-events: none; position: absolute; width: 2px; } .bar-label { align-items: baseline; display: flex; font-size: .84rem; gap: 1rem; justify-content: space-between; margin-bottom: .3rem; } .bar-label span, .bar-label strong { overflow-wrap: anywhere; } .bar-label strong { font-weight: 750; text-align: right; } @@ -280,6 +283,7 @@ progress.bar-track::-moz-progress-bar { background: var(--brand); } .directory-controls input { min-width: 0; } .directory-controls .result-count { align-self: center; } .result-count { color: var(--muted); flex: 0 1 auto; font-size: .8rem; } +.table-scroll-hint { display: none; } .table-scroll { border: 1px solid var(--border); margin-top: 1.3rem; max-width: 100%; overflow-x: auto; } .table-scroll:focus { outline: 3px solid #b71c1c; outline-offset: 3px; } .data-table { border-collapse: collapse; min-width: 660px; width: 100%; } @@ -378,8 +382,9 @@ progress.bar-track::-moz-progress-bar { background: var(--brand); } @media (max-width: 1050px) { .site-header { align-items: flex-start; } - .site-nav { order: 3; width: 100%; } - .nav-search { flex: 1 1 230px; margin-left: auto; } + .language-toggle { order: 1; } + .site-nav { order: 2; width: 100%; } + .nav-search { flex: 1 1 100%; order: 3; } .search-form { grid-template-columns: 1fr; } .metadata { grid-template-columns: repeat(2, 1fr); } .report-summary { grid-template-columns: repeat(3, 1fr); } @@ -391,9 +396,9 @@ progress.bar-track::-moz-progress-bar { background: var(--brand); } .site-header { gap: .8rem 1rem; padding-bottom: 1.2rem; padding-top: 1.2rem; } .nav-search { flex-basis: 100%; margin-left: 0; } .nav-search input { min-height: 44px; } - .site-nav { gap: .5rem .9rem; } + .site-nav { display: grid; gap: .5rem .9rem; grid-template-columns: repeat(3, max-content); justify-content: space-between; } .site-nav a, .site-footer nav a { align-items: center; display: inline-flex; min-height: 44px; } - .hero { padding: 3.5rem 0 4.2rem; } + .hero { padding: 2rem 0 4.2rem; } h1 { font-size: clamp(2.45rem, 13vw, 3.5rem); } .hero-intro { margin-bottom: 2.5rem; } .search-form__controls { align-items: stretch; flex-direction: column; } @@ -409,6 +414,7 @@ progress.bar-track::-moz-progress-bar { background: var(--brand); } .directory-controls, .tracker-controls { align-items: stretch; grid-template-columns: 1fr; } .directory-controls .result-count { align-self: start; } .table-scroll { margin-left: 0; width: 100%; } + .table-scroll-hint { color: var(--muted); display: block; font-size: .8rem; margin: 1rem 0 -.7rem; } .report-page { padding-top: 1.4rem; } .report-header { gap: 1rem; grid-template-columns: 64px minmax(0, 1fr); padding: 2.2rem 0; } .app-icon--large { border-radius: 12px; font-size: 2rem; height: 64px; width: 64px; } @@ -429,3 +435,10 @@ progress.bar-track::-moz-progress-bar { background: var(--brand); } .site-footer__inner { align-items: flex-start; flex-direction: column; gap: .8rem; min-height: 140px; padding-bottom: 1.4rem; padding-top: 1.4rem; } .request-details { grid-template-columns: 110px minmax(0, 1fr); } } + +@media (max-width: 360px) { + .site-header { column-gap: .5rem; } + .site-header .wordmark { font-size: .95rem; } + .site-header .wordmark small { display: none; } + .site-header .wordmark__icon { height: 32px; width: 32px; } +} diff --git a/routes/index.js b/routes/index.js index 1437568..8192329 100644 --- a/routes/index.js +++ b/routes/index.js @@ -456,7 +456,7 @@ router.get('/search', })); res.render('form', { - title: res.locals.t('Search app'), + title: res.locals.t('Search results'), errors: errors.array(), data: req.query, searchResults @@ -464,14 +464,14 @@ router.get('/search', } catch (err) { console.log(err); res.status(502).render('form', { - title: res.locals.t('Search apps title'), + title: res.locals.t('Search apps'), data: req.query, errors: [{ msg: res.locals.t('Error while searching. Try again later.') }], }); } } else { res.render('form', { - title: res.locals.t('Search app'), + title: res.locals.t('Search apps'), errors: errors.array(), data: req.query, }); diff --git a/test/websiteViews.test.js b/test/websiteViews.test.js index 853c176..2b7cadb 100644 --- a/test/websiteViews.test.js +++ b/test/websiteViews.test.js @@ -161,9 +161,17 @@ test('search, directory, lookup, statistics, about, request and error states ren const emptySearch = render('form.pug', { currentPath: '/search', data: { search: 'nothing' }, searchResults: [] }); assertSingleH1(emptySearch, 'empty search'); assert.match(emptySearch, /id="nav-search"/); + assert.match(emptySearch, /placeholder="Search apps"/); assert.match(emptySearch, /No apps found for/); assert.match(emptySearch, /No matching free app/); + const failedSearch = render('form.pug', { + currentPath: '/search', + errors: [{ msg: 'Please enter a search term' }] + }); + assert.match(failedSearch, /

Search apps<\/h1>/); + assert.doesNotMatch(failedSearch, /Search apps (heading|placeholder|title)/); + const search = render('form.pug', { data: { search: 'example' }, searchResults: [ diff --git a/views/about.pug b/views/about.pug index 6d22826..8b75831 100644 --- a/views/about.pug +++ b/views/about.pug @@ -56,6 +56,7 @@ block content p.eyebrow= translate('Company footprint') h2#jurisdiction-title= translate('Who controls the tracking') p= translate('Each tracker is traced to the company that runs it, up to the parent company at the top, and then to that company’s home country. What counts is whose laws a company answers to, not where its servers are: a US company can be compelled to hand data over under the CLOUD Act wherever that data is stored. Every app gets one label:') + p.table-scroll-hint= translate('Scroll sideways to see all columns.') .table-scroll(role='region' tabindex='0' aria-label=translate('Jurisdiction classifications')) table.data-table caption= translate('Jurisdiction classifications') diff --git a/views/directory.pug b/views/directory.pug index 1b96de8..5b6be3c 100644 --- a/views/directory.pug +++ b/views/directory.pug @@ -26,6 +26,7 @@ block content input#directory-filter(type='search' placeholder=translate(isTracker ? 'Filter trackers or companies' : 'Filter companies') aria-controls='directory-table' data-filter-target='#directory-table' autocomplete='off') p#directory-count.result-count(role='status' aria-live='polite')= translate(directoryEntries.length === 1 ? '{{count}} entry' : '{{count}} entries', { count: directoryEntries.length }) + p.table-scroll-hint= translate('Scroll sideways to see all columns.') .table-scroll(role='region' tabindex='0' aria-label=translate(isTracker ? 'Tracker directory table' : 'Company directory table')) table#directory-table.data-table caption= translate(isTracker ? 'Tracker directory' : 'Company directory') diff --git a/views/form.pug b/views/form.pug index 9568518..8fd2066 100644 --- a/views/form.pug +++ b/views/form.pug @@ -54,7 +54,7 @@ block content - const pct = statsData.classificationPcts && statsData.classificationPcts[cls] - const meta = metaData[cls] || {} if count !== undefined && count !== null - .bar-item + .bar-item(class=Number(count) > 0 && Number(pct) < .5 ? 'bar-item--tiny' : undefined) .bar-label span= meta.label ? translate(meta.label) : cls.replace(/_/g, ' ') strong= translate('{{count}} apps ({{pct}}%)', { count, pct: pct === undefined ? '0' : pct }) @@ -92,7 +92,7 @@ block content if hasErrors section.search-state(aria-labelledby='search-title') p.eyebrow= translate('Search') - h1#search-title= translate('Search apps heading') + h1#search-title= translate('Search apps') .notice.notice--warning(role='alert') ul.error-list each error in errors diff --git a/views/layout.pug b/views/layout.pug index eb72f25..dfa368b 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -63,7 +63,7 @@ html(lang=localeCode === 'da' ? 'da-DK' : 'en-GB') if current !== '/' form.nav-search(action=localize('/search') method='GET' role='search') label.sr-only(for='nav-search')= translate('Search apps') - input#nav-search(type='search' name='search' value=searchData.search || '' placeholder=translate('Search apps placeholder') autocomplete='off') + input#nav-search(type='search' name='search' value=searchData.search || '' placeholder=translate('Search apps') autocomplete='off') button.button.button--small(type='submit')= translate('Search') nav.language-toggle(aria-label=translate('Language')) a(href=languageHref('en') class=localeCode === 'en' ? 'is-current' : undefined aria-current=localeCode === 'en' ? 'true' : undefined)= translate('English') diff --git a/views/statistics.pug b/views/statistics.pug index d107925..8611e3f 100644 --- a/views/statistics.pug +++ b/views/statistics.pug @@ -27,7 +27,7 @@ block content - const pct = stats.classificationPcts && stats.classificationPcts[cls] - const meta = metadata[cls] || {} if count !== undefined && count !== null - .bar-item + .bar-item(class=Number(count) > 0 && Number(pct) < .5 ? 'bar-item--tiny' : undefined) .bar-label span= meta.label ? translate(meta.label) : cls.replace(/_/g, ' ') strong= translate('{{count}} apps ({{pct}}%)', { count, pct: pct === undefined ? '0' : pct }) @@ -42,6 +42,7 @@ block content h2#stats-trackers-title= translate('Most prevalent trackers') if trackers.length > 0 p.section-note= translate('Most frequently embedded third-party tracker libraries, with parent company and jurisdiction.') + p.table-scroll-hint= translate('Scroll sideways to see all columns.') .table-scroll(role='region' tabindex='0' aria-label=translate('Most prevalent trackers')) table.data-table caption= translate('Most prevalent trackers') @@ -81,6 +82,7 @@ block content h2#stats-companies-title= translate('Most prevalent tracking companies') if stats.topCompaniesSorted && stats.topCompaniesSorted.length > 0 p.section-note= translate('Unique companies by ultimate parent, ranked by the number of analysed apps in which they appear.') + p.table-scroll-hint= translate('Scroll sideways to see all columns.') .table-scroll(role='region' tabindex='0' aria-label=translate('Most prevalent tracking companies')) table.data-table caption= translate('Most prevalent tracking companies') @@ -112,6 +114,7 @@ block content h2#stats-category-title= translate('Jurisdiction by App Store category') if stats.categoriesSorted && stats.categoriesSorted.length > 0 p.section-note= translate('US-only and European-only tracking percentages by category. Categories with fewer than three apps are excluded from this comparison.') + p.table-scroll-hint= translate('Scroll sideways to see all columns.') .table-scroll(role='region' tabindex='0' aria-label=translate('Jurisdiction by App Store category')) table.data-table caption= translate('Jurisdiction by App Store category') @@ -143,6 +146,7 @@ block content h2#stats-alternatives-title= translate('European alternatives') if alternatives.length > 0 p.section-note= translate('For each core tracking function, the dominant US provider and whether a viable European alternative appears in the reference data.') + p.table-scroll-hint= translate('Scroll sideways to see all columns.') .table-scroll(role='region' tabindex='0' aria-label=translate('European alternatives')) table.data-table caption= translate('European alternatives')