this repo has no descr,ription vt3e.cat

feat: respect prefers-reduced-motion; improve screen reader experience; other small tweaks

vt3e.cat a803a33f 8b79e0ef

verified
+227 -202
-1
pkgs/web/src/stores/environment.ts
··· 28 28 _prefersReducedMotion.value = e.matches; 29 29 }); 30 30 31 - console.log(window.matchMedia("(prefers-color-scheme: dark)")) 32 31 const colourQuery = window.matchMedia("(prefers-color-scheme: dark)"); 33 32 _prefersDarkScheme.value = colourQuery.matches; 34 33 colourQuery.addEventListener("change", (e) => {
+214 -200
pkgs/web/src/stores/theme.ts
··· 1 - import { defineStore } from 'pinia' 2 - import { ref, computed, watch } from 'vue' 3 - import { useEnvironmentStore } from './environment' 1 + import { defineStore } from "pinia"; 2 + import { ref, computed, watch } from "vue"; 3 + import { useEnvironmentStore } from "./environment"; 4 4 5 - import KEYS from '@/utils/keys' 5 + import KEYS from "@/utils/keys"; 6 6 7 7 export interface ThemeDefinition { 8 - id: string 9 - name: string 10 - type: 'light' | 'dark' 11 - variables: Record<string, string> 8 + id: string; 9 + name: string; 10 + type: "light" | "dark"; 11 + variables: Record<string, string>; 12 12 } 13 13 14 14 const latte: ThemeDefinition = { 15 - id: 'latte', 16 - name: 'Latte', 17 - type: 'light', 15 + id: "latte", 16 + name: "Latte", 17 + type: "light", 18 18 variables: { 19 - rosewater: '10.8 58.824% 66.667%', 20 - flamingo: '0 59.763% 66.863%', 21 - pink: '316.034 73.418% 69.02%', 22 - mauve: '266.044 85.047% 58.039%', 23 - red: '347.077 86.667% 44.118%', 24 - maroon: '354.783 76.303% 58.627%', 25 - peach: '21.975 99.184% 51.961%', 26 - yellow: '34.948 76.984% 49.412%', 27 - green: '109.231 57.635% 39.804%', 28 - teal: '183.231 73.864% 34.51%', 29 - sky: '197.067 96.567% 45.686%', 30 - sapphire: '188.859 69.953% 41.765%', 31 - blue: '219.907 91.489% 53.922%', 32 - lavender: '230.935 97.203% 71.961%', 33 - text: '233.793 16.022% 35.49%', 34 - subtext1: '233.333 12.796% 41.373%', 35 - subtext0: '232.8 10.373% 47.255%', 36 - overlay2: '232.174 9.623% 53.137%', 37 - overlay1: '231.429 10.048% 59.02%', 38 - overlay0: '228 11.236% 65.098%', 39 - surface2: '226.667 12.162% 70.98%', 40 - surface1: '225 13.559% 76.863%', 41 - surface0: '222.857 15.909% 82.745%', 42 - base: '220 23.077% 94.902%', 43 - mantle: '220 21.951% 91.961%', 44 - crust: '220 20.69% 88.627%', 19 + rosewater: "10.8 58.824% 66.667%", 20 + flamingo: "0 59.763% 66.863%", 21 + pink: "316.034 73.418% 69.02%", 22 + mauve: "266.044 85.047% 58.039%", 23 + red: "347.077 86.667% 44.118%", 24 + maroon: "354.783 76.303% 58.627%", 25 + peach: "21.975 99.184% 51.961%", 26 + yellow: "34.948 76.984% 49.412%", 27 + green: "109.231 57.635% 39.804%", 28 + teal: "183.231 73.864% 34.51%", 29 + sky: "197.067 96.567% 45.686%", 30 + sapphire: "188.859 69.953% 41.765%", 31 + blue: "219.907 91.489% 53.922%", 32 + lavender: "230.935 97.203% 71.961%", 33 + text: "233.793 16.022% 35.49%", 34 + subtext1: "233.333 12.796% 41.373%", 35 + subtext0: "232.8 10.373% 47.255%", 36 + overlay2: "232.174 9.623% 53.137%", 37 + overlay1: "231.429 10.048% 59.02%", 38 + overlay0: "228 11.236% 65.098%", 39 + surface2: "226.667 12.162% 70.98%", 40 + surface1: "225 13.559% 76.863%", 41 + surface0: "222.857 15.909% 82.745%", 42 + base: "220 23.077% 94.902%", 43 + mantle: "220 21.951% 91.961%", 44 + crust: "220 20.69% 88.627%", 45 45 }, 46 - } 46 + }; 47 47 48 48 const frappe: ThemeDefinition = { 49 - id: 'frappe', 50 - name: 'Frappé', 51 - type: 'dark', 49 + id: "frappe", 50 + name: "Frappé", 51 + type: "dark", 52 52 variables: { 53 - rosewater: '10.286 57.377% 88.039%', 54 - flamingo: '0 58.537% 83.922%', 55 - pink: '316 73.171% 83.922%', 56 - mauve: '276.667 59.016% 76.078%', 57 - red: '358.812 67.785% 70.784%', 58 - maroon: '357.778 65.854% 75.882%', 59 - peach: '20.331 79.085% 70%', 60 - yellow: '39.529 62.044% 73.137%', 61 - green: '95.833 43.902% 67.843%', 62 - teal: '171.549 39.227% 64.51%', 63 - sky: '189.091 47.826% 72.941%', 64 - sapphire: '198.621 55.414% 69.216%', 65 - blue: '221.633 74.242% 74.118%', 66 - lavender: '238.909 66.265% 83.725%', 67 - text: '227.234 70.149% 86.863%', 68 - subtext1: '226.667 43.689% 79.804%', 69 - subtext0: '228.293 29.496% 72.745%', 70 - overlay2: '227.692 22.286% 65.686%', 71 - overlay1: '226.667 16.981% 58.431%', 72 - overlay0: '229.091 13.36% 51.569%', 73 - surface2: '228 13.274% 44.314%', 74 - surface1: '227.143 14.737% 37.255%', 75 - surface0: '230 15.584% 30.196%', 76 - base: '229.091 18.644% 23.137%', 77 - mantle: '230.526 18.812% 19.804%', 78 - crust: '229.412 19.54% 17.059%', 53 + rosewater: "10.286 57.377% 88.039%", 54 + flamingo: "0 58.537% 83.922%", 55 + pink: "316 73.171% 83.922%", 56 + mauve: "276.667 59.016% 76.078%", 57 + red: "358.812 67.785% 70.784%", 58 + maroon: "357.778 65.854% 75.882%", 59 + peach: "20.331 79.085% 70%", 60 + yellow: "39.529 62.044% 73.137%", 61 + green: "95.833 43.902% 67.843%", 62 + teal: "171.549 39.227% 64.51%", 63 + sky: "189.091 47.826% 72.941%", 64 + sapphire: "198.621 55.414% 69.216%", 65 + blue: "221.633 74.242% 74.118%", 66 + lavender: "238.909 66.265% 83.725%", 67 + text: "227.234 70.149% 86.863%", 68 + subtext1: "226.667 43.689% 79.804%", 69 + subtext0: "228.293 29.496% 72.745%", 70 + overlay2: "227.692 22.286% 65.686%", 71 + overlay1: "226.667 16.981% 58.431%", 72 + overlay0: "229.091 13.36% 51.569%", 73 + surface2: "228 13.274% 44.314%", 74 + surface1: "227.143 14.737% 37.255%", 75 + surface0: "230 15.584% 30.196%", 76 + base: "229.091 18.644% 23.137%", 77 + mantle: "230.526 18.812% 19.804%", 78 + crust: "229.412 19.54% 17.059%", 79 79 }, 80 - } 80 + }; 81 81 82 82 const macchiato: ThemeDefinition = { 83 - id: 'macchiato', 84 - name: 'Macchiato', 85 - type: 'dark', 83 + id: "macchiato", 84 + name: "Macchiato", 85 + type: "dark", 86 86 variables: { 87 - rosewater: '10 57.692% 89.804%', 88 - flamingo: '0 58.333% 85.882%', 89 - pink: '316.071 73.684% 85.098%', 90 - mauve: '266.512 82.692% 79.608%', 91 - red: '351.176 73.913% 72.941%', 92 - maroon: '355.059 71.429% 76.667%', 93 - peach: '21.356 85.507% 72.941%', 94 - yellow: '40.253 69.912% 77.843%', 95 - green: '105.217 48.252% 71.961%', 96 - teal: '171.081 46.835% 69.02%', 97 - sky: '188.78 59.42% 72.941%', 98 - sapphire: '198.641 65.605% 69.216%', 99 - blue: '220.189 82.813% 74.902%', 100 - lavender: '234.462 82.278% 84.51%', 101 - text: '227.442 68.254% 87.647%', 102 - subtext1: '228 39.216% 80%', 103 - subtext0: '227.368 26.761% 72.157%', 104 - overlay2: '228.333 20% 64.706%', 105 - overlay1: '227.647 15.455% 56.863%', 106 - overlay0: '230.323 12.351% 49.216%', 107 - surface2: '229.655 13.744% 41.373%', 108 - surface1: '231.111 15.607% 33.922%', 109 - surface0: '230.4 18.797% 26.078%', 110 - base: '231.818 23.404% 18.431%', 111 - mantle: '233.333 23.077% 15.294%', 112 - crust: '235.714 22.581% 12.157%', 87 + rosewater: "10 57.692% 89.804%", 88 + flamingo: "0 58.333% 85.882%", 89 + pink: "316.071 73.684% 85.098%", 90 + mauve: "266.512 82.692% 79.608%", 91 + red: "351.176 73.913% 72.941%", 92 + maroon: "355.059 71.429% 76.667%", 93 + peach: "21.356 85.507% 72.941%", 94 + yellow: "40.253 69.912% 77.843%", 95 + green: "105.217 48.252% 71.961%", 96 + teal: "171.081 46.835% 69.02%", 97 + sky: "188.78 59.42% 72.941%", 98 + sapphire: "198.641 65.605% 69.216%", 99 + blue: "220.189 82.813% 74.902%", 100 + lavender: "234.462 82.278% 84.51%", 101 + text: "227.442 68.254% 87.647%", 102 + subtext1: "228 39.216% 80%", 103 + subtext0: "227.368 26.761% 72.157%", 104 + overlay2: "228.333 20% 64.706%", 105 + overlay1: "227.647 15.455% 56.863%", 106 + overlay0: "230.323 12.351% 49.216%", 107 + surface2: "229.655 13.744% 41.373%", 108 + surface1: "231.111 15.607% 33.922%", 109 + surface0: "230.4 18.797% 26.078%", 110 + base: "231.818 23.404% 18.431%", 111 + mantle: "233.333 23.077% 15.294%", 112 + crust: "235.714 22.581% 12.157%", 113 113 }, 114 - } 114 + }; 115 115 116 116 const mocha: ThemeDefinition = { 117 - id: 'mocha', 118 - name: 'Mocha', 119 - type: 'dark', 117 + id: "mocha", 118 + name: "Mocha", 119 + type: "dark", 120 120 variables: { 121 - rosewater: '9.6 55.556% 91.176%', 122 - flamingo: '0 58.73% 87.647%', 123 - pink: '316.471 71.831% 86.078%', 124 - mauve: '267.407 83.505% 80.98%', 125 - red: '343.269 81.25% 74.902%', 126 - maroon: '350.4 65.217% 77.451%', 127 - peach: '22.957 92% 75.49%', 128 - yellow: '41.351 86.047% 83.137%', 129 - green: '115.455 54.098% 76.078%', 130 - teal: '170 57.353% 73.333%', 131 - sky: '189.184 71.014% 72.941%', 132 - sapphire: '198.5 75.949% 69.02%', 133 - blue: '217.168 91.87% 75.882%', 134 - lavender: '231.892 97.368% 85.098%', 135 - text: '226.154 63.934% 88.039%', 136 - subtext1: '226.667 35.294% 80%', 137 - subtext0: '227.647 23.611% 71.765%', 138 - overlay2: '228.387 16.757% 63.725%', 139 - overlay1: '229.655 12.775% 55.49%', 140 - overlay0: '230.769 10.744% 47.451%', 141 - surface2: '232.5 12% 39.216%', 142 - surface1: '234.286 13.208% 31.176%', 143 - surface0: '236.842 16.239% 22.941%', 144 - base: '240 21.053% 14.902%', 145 - mantle: '240 21.311% 11.961%', 146 - crust: '240 22.727% 8.627%', 121 + rosewater: "9.6 55.556% 91.176%", 122 + flamingo: "0 58.73% 87.647%", 123 + pink: "316.471 71.831% 86.078%", 124 + mauve: "267.407 83.505% 80.98%", 125 + red: "343.269 81.25% 74.902%", 126 + maroon: "350.4 65.217% 77.451%", 127 + peach: "22.957 92% 75.49%", 128 + yellow: "41.351 86.047% 83.137%", 129 + green: "115.455 54.098% 76.078%", 130 + teal: "170 57.353% 73.333%", 131 + sky: "189.184 71.014% 72.941%", 132 + sapphire: "198.5 75.949% 69.02%", 133 + blue: "217.168 91.87% 75.882%", 134 + lavender: "231.892 97.368% 85.098%", 135 + text: "226.154 63.934% 88.039%", 136 + subtext1: "226.667 35.294% 80%", 137 + subtext0: "227.647 23.611% 71.765%", 138 + overlay2: "228.387 16.757% 63.725%", 139 + overlay1: "229.655 12.775% 55.49%", 140 + overlay0: "230.769 10.744% 47.451%", 141 + surface2: "232.5 12% 39.216%", 142 + surface1: "234.286 13.208% 31.176%", 143 + surface0: "236.842 16.239% 22.941%", 144 + base: "240 21.053% 14.902%", 145 + mantle: "240 21.311% 11.961%", 146 + crust: "240 22.727% 8.627%", 147 147 }, 148 - } 148 + }; 149 149 150 150 export enum AccentColour { 151 - Rosewater = 'rosewater', 152 - Flamingo = 'flamingo', 153 - Pink = 'pink', 154 - Mauve = 'mauve', 155 - Red = 'red', 156 - Maroon = 'maroon', 157 - Peach = 'peach', 158 - Yellow = 'yellow', 159 - Green = 'green', 160 - Teal = 'teal', 161 - Sky = 'sky', 162 - Sapphire = 'sapphire', 163 - Blue = 'blue', 164 - Lavender = 'lavender', 151 + Rosewater = "rosewater", 152 + Flamingo = "flamingo", 153 + Pink = "pink", 154 + Mauve = "mauve", 155 + Red = "red", 156 + Maroon = "maroon", 157 + Peach = "peach", 158 + Yellow = "yellow", 159 + Green = "green", 160 + Teal = "teal", 161 + Sky = "sky", 162 + Sapphire = "sapphire", 163 + Blue = "blue", 164 + Lavender = "lavender", 165 165 } 166 166 export const AccentColours = Object.values(AccentColour); 167 - export const themes = [latte, frappe, macchiato, mocha] 167 + export const themes = [latte, frappe, macchiato, mocha]; 168 168 169 - const STORAGE_KEYS = KEYS.THEME 169 + const STORAGE_KEYS = KEYS.THEME; 170 170 171 - export const useThemeStore = defineStore('theme', () => { 172 - const env = useEnvironmentStore() 171 + export const useThemeStore = defineStore("theme", () => { 172 + const env = useEnvironmentStore(); 173 173 174 - const followSystem = ref(true) 175 - const preferredLight = ref<string>('latte') 176 - const preferredDark = ref<string>('mocha') 177 - const currentMode = ref<'light' | 'dark'>('dark') 178 - const preferredAccent = ref<AccentColour>(AccentColour.Mauve) 174 + const followSystem = ref(true); 175 + const preferredLight = ref<string>("latte"); 176 + const preferredDark = ref<string>("mocha"); 177 + const currentMode = ref<"light" | "dark">("dark"); 178 + const preferredAccent = ref<AccentColour>(AccentColour.Mauve); 179 179 180 180 const activeTheme = computed(() => { 181 - let targetId: string 181 + let targetId: string; 182 182 183 183 if (followSystem.value) { 184 - targetId = env.prefersDarkScheme ? preferredDark.value : preferredLight.value 184 + targetId = env.prefersDarkScheme 185 + ? preferredDark.value 186 + : preferredLight.value; 185 187 } else { 186 - targetId = currentMode.value === 'dark' ? preferredDark.value : preferredLight.value 188 + targetId = 189 + currentMode.value === "dark" 190 + ? preferredDark.value 191 + : preferredLight.value; 187 192 } 188 193 189 - return themes.find((t) => t.id === targetId) || mocha 190 - }) 194 + return themes.find((t) => t.id === targetId) || mocha; 195 + }); 191 196 192 197 function setFollowSystem(val: boolean) { 193 - followSystem.value = val 198 + followSystem.value = val; 194 199 } 195 200 196 201 function setPreferredLight(themeId: string) { 197 - if (themes.find((t) => t.id === themeId && t.type === 'light')) { 198 - preferredLight.value = themeId 199 - currentMode.value = 'light' 202 + if (themes.find((t) => t.id === themeId && t.type === "light")) { 203 + preferredLight.value = themeId; 204 + currentMode.value = "light"; 200 205 } 201 206 } 202 207 203 208 function setPreferredDark(themeId: string) { 204 - if (themes.find((t) => t.id === themeId && t.type === 'dark')) { 205 - preferredDark.value = themeId 206 - currentMode.value = 'dark' 209 + if (themes.find((t) => t.id === themeId && t.type === "dark")) { 210 + preferredDark.value = themeId; 211 + currentMode.value = "dark"; 207 212 } 208 213 } 209 214 210 215 function setAccent(colour: AccentColour) { 211 216 if (AccentColours.includes(colour)) { 212 - preferredAccent.value = colour 217 + preferredAccent.value = colour; 213 218 } 214 219 } 215 220 216 221 function applyTheme() { 217 - const root = document.documentElement 218 - const theme = activeTheme.value 219 - const accentKey = preferredAccent.value 222 + const root = document.documentElement; 223 + const theme = activeTheme.value; 224 + const accentKey = preferredAccent.value; 220 225 221 226 Object.entries(theme.variables).forEach(([key, value]) => { 222 - root.style.setProperty(`--${key}`, value) 223 - }) 227 + root.style.setProperty(`--${key}`, value); 228 + }); 224 229 225 - const accentValue = theme.variables[accentKey] 226 - if (accentValue) root.style.setProperty('--accent', accentValue) 230 + const accentValue = theme.variables[accentKey]; 231 + if (accentValue) root.style.setProperty("--accent", accentValue); 227 232 228 - root.setAttribute('data-theme', theme.id) 233 + root.setAttribute("data-theme", theme.id); 229 234 230 - const metaThemeColor = document.querySelector('meta[name="theme-colour"]') 235 + const metaThemeColor = document.querySelector('meta[name="theme-colour"]'); 231 236 if (metaThemeColor) { 232 - metaThemeColor.setAttribute('content', `hsl(${theme.variables.mantle})`) 237 + metaThemeColor.setAttribute("content", `hsl(${theme.variables.mantle})`); 233 238 } 234 239 } 235 240 236 241 function init() { 237 - const storedFollow = localStorage.getItem(STORAGE_KEYS.FOLLOW_SYSTEM_THEME) 238 - if (storedFollow !== null) { 239 - followSystem.value = storedFollow === 'true' 240 - console.log('Stored follow system theme:', followSystem.value) 241 - } 242 + const storedFollow = localStorage.getItem(STORAGE_KEYS.FOLLOW_SYSTEM_THEME); 243 + if (storedFollow !== null) followSystem.value = storedFollow === "true"; 242 244 243 - const storedLight = localStorage.getItem(STORAGE_KEYS.PREFERRED_LIGHT_THEME) 245 + const storedLight = localStorage.getItem( 246 + STORAGE_KEYS.PREFERRED_LIGHT_THEME, 247 + ); 244 248 if (storedLight && themes.some((t) => t.id === storedLight)) { 245 - preferredLight.value = storedLight 249 + preferredLight.value = storedLight; 246 250 } 247 251 248 - const storedDark = localStorage.getItem(STORAGE_KEYS.PREFERRED_DARK_THEME) 252 + const storedDark = localStorage.getItem(STORAGE_KEYS.PREFERRED_DARK_THEME); 249 253 if (storedDark && themes.some((t) => t.id === storedDark)) { 250 - preferredDark.value = storedDark 254 + preferredDark.value = storedDark; 251 255 } 252 256 253 - const storedMode = localStorage.getItem(STORAGE_KEYS.CURRENT_MODE) 254 - if (storedMode === 'light' || storedMode === 'dark') { 255 - currentMode.value = storedMode 257 + const storedMode = localStorage.getItem(STORAGE_KEYS.CURRENT_MODE); 258 + if (storedMode === "light" || storedMode === "dark") { 259 + currentMode.value = storedMode; 256 260 } else { 257 - currentMode.value = env.prefersDarkScheme ? 'dark' : 'light' 261 + currentMode.value = env.prefersDarkScheme ? "dark" : "light"; 258 262 } 259 263 260 - const storedAccent = localStorage.getItem(STORAGE_KEYS.ACCENT_COLOUR) as AccentColour 264 + const storedAccent = localStorage.getItem( 265 + STORAGE_KEYS.ACCENT_COLOUR, 266 + ) as AccentColour; 261 267 if (storedAccent && AccentColours.includes(storedAccent)) { 262 - preferredAccent.value = storedAccent 268 + preferredAccent.value = storedAccent; 263 269 } 264 270 265 271 watch(followSystem, (val) => { 266 - localStorage.setItem(STORAGE_KEYS.FOLLOW_SYSTEM_THEME, String(val)) 272 + localStorage.setItem(STORAGE_KEYS.FOLLOW_SYSTEM_THEME, String(val)); 267 273 if (!val) { 268 - currentMode.value = env.prefersDarkScheme ? 'dark' : 'light' 274 + currentMode.value = env.prefersDarkScheme ? "dark" : "light"; 269 275 } 270 - }) 271 - watch(preferredLight, (val) => localStorage.setItem(STORAGE_KEYS.PREFERRED_LIGHT_THEME, val)) 272 - watch(preferredDark, (val) => localStorage.setItem(STORAGE_KEYS.PREFERRED_DARK_THEME, val)) 273 - watch(currentMode, (val) => localStorage.setItem(STORAGE_KEYS.CURRENT_MODE, val)) 274 - watch(preferredAccent, (val) => localStorage.setItem(STORAGE_KEYS.ACCENT_COLOUR, val)) 276 + }); 277 + watch(preferredLight, (val) => 278 + localStorage.setItem(STORAGE_KEYS.PREFERRED_LIGHT_THEME, val), 279 + ); 280 + watch(preferredDark, (val) => 281 + localStorage.setItem(STORAGE_KEYS.PREFERRED_DARK_THEME, val), 282 + ); 283 + watch(currentMode, (val) => 284 + localStorage.setItem(STORAGE_KEYS.CURRENT_MODE, val), 285 + ); 286 + watch(preferredAccent, (val) => 287 + localStorage.setItem(STORAGE_KEYS.ACCENT_COLOUR, val), 288 + ); 275 289 276 290 watch( 277 291 [activeTheme, preferredAccent, () => env.prefersDarkScheme], 278 292 () => { 279 - applyTheme() 293 + applyTheme(); 280 294 }, 281 295 { immediate: true }, 282 - ) 296 + ); 283 297 } 284 298 285 299 return { ··· 295 309 setPreferredDark, 296 310 setAccent, 297 311 init, 298 - } 299 - }) 312 + }; 313 + });
+8
pkgs/web/src/styles/main.scss
··· 41 41 filter var(--transition); 42 42 } 43 43 44 + @media (prefers-reduced-motion: reduce) { 45 + *, 46 + *::before, 47 + *::after { 48 + transition: none !important; 49 + } 50 + } 51 + 44 52 *:focus-visible { 45 53 outline-color: hsl(var(--accent)); 46 54 outline-offset: 2px;
+3
pkgs/web/src/utils/links.ts
··· 6 6 export type Social = { 7 7 label: string; 8 8 icon: string; 9 + /** override for the aria-label attribute */ 10 + ariaLabel?: string; 9 11 /** displayed icon on the home page */ 10 12 homeIcon?: string; 11 13 href: string; ··· 25 27 export const SOCIALS: Social[] = [ 26 28 { 27 29 label: "Bluesky", 30 + ariaLabel: "Blue sky", 28 31 href: `https://bsky.app/profile/${DID}`, 29 32 handle: "vt3e.cat", 30 33 icon: BlueskyLogo,
+1
pkgs/web/src/views/HomeView.vue
··· 46 46 <a 47 47 v-for="link in socials" 48 48 :key="link.label" 49 + :aria-label="link.ariaLabel" 49 50 :href="link.href || '#'" 50 51 :target="link.href ? '_blank' : undefined" 51 52 class="social-pill"
+1 -1
pkgs/web/src/views/ProjectsView.vue
··· 75 75 ) 76 76 77 77 repoLanguages.value[repo.uri] = languages.languages 78 - .filter((lang) => lang.name !== '') 78 + .filter((lang) => lang.name !== '' && lang.name == 'Roff') 79 79 .slice(0, 3) 80 80 }), 81 81 )