homepage: ensure perfect circular favicon via wrapper chip (20x20) and center 16px icon; align history row center to avoid distortion
- commit
- d3c3c1d989a4cd9f46ec97cde28ebc8a1f9d5c4e
- author
- bamen · · 2025-09-09 16:05 UTC
- committer
- bamen · · 2025-09-09 16:41 UTC
- parent
- c914750
- signature
- unsigned
2 files · +16 -10
M frontend/src/utils/dom.ts +7 -4
| @@ -78,14 +78,17 @@ export class DOMRenderer { | |||
| 78 | 78 | const line = document.createElement('div'); | |
| 79 | 79 | line.className = 'history-line'; | |
| 80 | 80 | ||
| 81 | - | // Favicon chip | |
| 81 | + | // Favicon chip (wrapper ensures perfect circle sizing) | |
| 82 | + | const iconWrap = document.createElement('div'); | |
| 83 | + | iconWrap.className = 'history-favicon-chip'; | |
| 82 | 84 | const icon = document.createElement('img'); | |
| 83 | - | icon.className = 'history-favicon'; | |
| 85 | + | icon.className = 'history-favicon-img'; | |
| 84 | 86 | icon.width = 16; icon.height = 16; | |
| 85 | 87 | icon.loading = 'lazy'; | |
| 86 | 88 | icon.referrerPolicy = 'no-referrer'; | |
| 87 | 89 | icon.src = this.buildFaviconURL(item.url); | |
| 88 | - | icon.addEventListener('error', () => { icon.style.display = 'none'; }); | |
| 90 | + | icon.addEventListener('error', () => { iconWrap.style.display = 'none'; }); | |
| 91 | + | iconWrap.appendChild(icon); | |
| 89 | 92 | ||
| 90 | 93 | const titleEl = document.createElement('span'); | |
| 91 | 94 | titleEl.className = 'history-title'; | |
| @@ -107,7 +110,7 @@ export class DOMRenderer { | |||
| 107 | 110 | urlEl.className = 'history-url'; | |
| 108 | 111 | urlEl.textContent = item.url; | |
| 109 | 112 | ||
| 110 | - | line.appendChild(icon); | |
| 113 | + | line.appendChild(iconWrap); | |
| 111 | 114 | line.appendChild(titleEl); | |
| 112 | 115 | line.appendChild(sep1); | |
| 113 | 116 | line.appendChild(domainEl); | |
| @@ -78,14 +78,17 @@ export class DOMRenderer { | |||||
| 78 | const line = document.createElement('div'); | 78 | const line = document.createElement('div'); | ||
| 79 | line.className = 'history-line'; | 79 | line.className = 'history-line'; | ||
| 80 | 80 | ||||
| 81 | - | // Favicon chip | 81 | + | // Favicon chip (wrapper ensures perfect circle sizing) |
| 82 | + | const iconWrap = document.createElement('div'); | |||
| 83 | + | iconWrap.className = 'history-favicon-chip'; | |||
| 82 | const icon = document.createElement('img'); | 84 | const icon = document.createElement('img'); | ||
| 83 | - | icon.className = 'history-favicon'; | 85 | + | icon.className = 'history-favicon-img'; |
| 84 | icon.width = 16; icon.height = 16; | 86 | icon.width = 16; icon.height = 16; | ||
| 85 | icon.loading = 'lazy'; | 87 | icon.loading = 'lazy'; | ||
| 86 | icon.referrerPolicy = 'no-referrer'; | 88 | icon.referrerPolicy = 'no-referrer'; | ||
| 87 | icon.src = this.buildFaviconURL(item.url); | 89 | icon.src = this.buildFaviconURL(item.url); | ||
| 88 | - | icon.addEventListener('error', () => { icon.style.display = 'none'; }); | 90 | + | icon.addEventListener('error', () => { iconWrap.style.display = 'none'; }); |
| 91 | + | iconWrap.appendChild(icon); | |||
| 89 | 92 | ||||
| 90 | const titleEl = document.createElement('span'); | 93 | const titleEl = document.createElement('span'); | ||
| 91 | titleEl.className = 'history-title'; | 94 | titleEl.className = 'history-title'; | ||
| @@ -107,7 +110,7 @@ export class DOMRenderer { | |||||
| 107 | urlEl.className = 'history-url'; | 110 | urlEl.className = 'history-url'; | ||
| 108 | urlEl.textContent = item.url; | 111 | urlEl.textContent = item.url; | ||
| 109 | 112 | ||||
| 110 | - | line.appendChild(icon); | 113 | + | line.appendChild(iconWrap); |
| 111 | line.appendChild(titleEl); | 114 | line.appendChild(titleEl); | ||
| 112 | line.appendChild(sep1); | 115 | line.appendChild(sep1); | ||
| 113 | line.appendChild(domainEl); | 116 | line.appendChild(domainEl); | ||
M frontend/src/utils/html-builder.ts +9 -6
| @@ -109,21 +109,24 @@ export class HTMLBuilder { | |||
| 109 | 109 | display: flex; | |
| 110 | 110 | gap: 0.5rem; | |
| 111 | 111 | white-space: nowrap; | |
| 112 | - | align-items: baseline; | |
| 112 | + | align-items: center; | |
| 113 | 113 | overflow: hidden; | |
| 114 | 114 | width: 100%; | |
| 115 | 115 | min-width: 0; /* allow flex children to shrink */ | |
| 116 | 116 | } | |
| 117 | - | ||
| 118 | - | .history-favicon { | |
| 117 | + | .history-favicon-chip { | |
| 119 | 118 | flex: 0 0 20px; | |
| 120 | - | width: 16px; height: 16px; | |
| 121 | - | padding: 2px; | |
| 122 | - | box-sizing: content-box; | |
| 119 | + | width: 20px; height: 20px; | |
| 123 | 120 | border-radius: 50%; | |
| 124 | 121 | background: #ccc; | |
| 125 | 122 | border: 1px solid rgba(0,0,0,.12); | |
| 126 | 123 | box-shadow: 0 1px 2px rgba(0,0,0,.12); | |
| 124 | + | display: flex; | |
| 125 | + | align-items: center; | |
| 126 | + | justify-content: center; | |
| 127 | + | } | |
| 128 | + | .history-favicon-img { | |
| 129 | + | width: 16px; height: 16px; | |
| 127 | 130 | filter: brightness(1.06) contrast(1.03); | |
| 128 | 131 | image-rendering: -webkit-optimize-contrast; | |
| 129 | 132 | } | |
| @@ -109,21 +109,24 @@ export class HTMLBuilder { | |||||
| 109 | display: flex; | 109 | display: flex; | ||
| 110 | gap: 0.5rem; | 110 | gap: 0.5rem; | ||
| 111 | white-space: nowrap; | 111 | white-space: nowrap; | ||
| 112 | - | align-items: baseline; | 112 | + | align-items: center; |
| 113 | overflow: hidden; | 113 | overflow: hidden; | ||
| 114 | width: 100%; | 114 | width: 100%; | ||
| 115 | min-width: 0; /* allow flex children to shrink */ | 115 | min-width: 0; /* allow flex children to shrink */ | ||
| 116 | } | 116 | } | ||
| 117 | - | 117 | + | .history-favicon-chip { | |
| 118 | - | .history-favicon { | |||
| 119 | flex: 0 0 20px; | 118 | flex: 0 0 20px; | ||
| 120 | - | width: 16px; height: 16px; | 119 | + | width: 20px; height: 20px; |
| 121 | - | padding: 2px; | |||
| 122 | - | box-sizing: content-box; | |||
| 123 | border-radius: 50%; | 120 | border-radius: 50%; | ||
| 124 | background: #ccc; | 121 | background: #ccc; | ||
| 125 | border: 1px solid rgba(0,0,0,.12); | 122 | border: 1px solid rgba(0,0,0,.12); | ||
| 126 | box-shadow: 0 1px 2px rgba(0,0,0,.12); | 123 | box-shadow: 0 1px 2px rgba(0,0,0,.12); | ||
| 124 | + | display: flex; | |||
| 125 | + | align-items: center; | |||
| 126 | + | justify-content: center; | |||
| 127 | + | } | |||
| 128 | + | .history-favicon-img { | |||
| 129 | + | width: 16px; height: 16px; | |||
| 127 | filter: brightness(1.06) contrast(1.03); | 130 | filter: brightness(1.06) contrast(1.03); | ||
| 128 | image-rendering: -webkit-optimize-contrast; | 131 | image-rendering: -webkit-optimize-contrast; | ||
| 129 | } | 132 | } | ||