├── README.md ├── index.html ├── index.js ├── index.zip ├── lake.jpg ├── manifest.json ├── spacegrotesk.woff2 └── weatherCodes.js /README.md: -------------------------------------------------------------------------------- 1 | # startlake 2 | 3 | just another startpage. a weekend project that i kinda gave up on. 4 | 5 | ![image](https://github.com/user-attachments/assets/502148ac-9d75-461a-96cd-f274d969f274) 6 | 7 | ## installing 8 | 9 | ### firefox 10 | 11 | regular firefox requires extensions to be [signed](https://extensionworkshop.com/documentation/publish/signing-and-distribution-overview/). firefox nightly does not require signing. 12 | 13 | 1. go to `about:addons`. 14 | 2. click the gear icon in the top right. 15 | 3. click "Install Add-on From File..." 16 | 4. select `index.zip` from this repo (if you are using nightly) or the `.xpi` file (if you are using regular firefox and have signed the extension). 17 | 5. create a new tab. if it asks you, select "Keep Changes". 18 | 19 | bonus: install the firefox color extension and use [this theme](https://color.firefox.com/?theme=XQAAAAK6AgAAAAAAAABBqYhm849SCicxcUEYWXcGHf3p79Ffm1p9Wc4wq53dKzq9lNGpZo8BuIsCkVkhGB-b71b_bH2GAn3WyUogVaz_7oMq3PdWBi1tWXc0s4NIAQJS28Fxe8MjMBa4kcq36Ap2Us_AykwqbGWT3hsVv7qSFMrFjAsHv3iRAYPPHY3TpdofkQjV7e6OjSzNQH5yVQRuXnnFhwwrQDYia_UIdBvjbErAMtrQh1v2_ova4_704BscrUZgYcyMx7CH_oR3VhNm4jn-xIiWEHn19HoT-4Vb2lUkMOTCdqdi-K-tp_x3HLIaCHBxDqK2b-EHYv90tRBKTi-EHnrEJxop91Od_oa1FDlwy5_XEqu-sSjioq-zb94BuJ5Pr9S300fE_ZYk8z3_WJIjAA) for matching firefox colors. 20 | 21 | ### chrome 22 | 23 | currently it doesn't work on chrome, but it shouldn't be too hard to modify. 24 | 25 | ## notes 26 | 27 | you can open the settings by clicking the hidden gear button in the top right corner. settings are stored in localstorage. the settings will automatically be visible if you do not have anything set. 28 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ~ 7 | 8 | 9 | 187 | 188 | 189 | lake 190 |
191 |
192 |
193 |
12:00
194 |
195 |
00
196 |
am
197 |
198 |
199 |
200 |
12.00
201 |
wednesday
202 |
203 |
204 |
100°
205 |
partly cloudy
206 |
207 |
208 | 239 |
240 |
241 | 245 | 249 | 256 | 263 | 264 |
265 | 278 | 279 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { weatherCodes } from './weatherCodes.js' 2 | 3 | document.addEventListener('DOMContentLoaded', () => { 4 | const settingsForm = document.getElementById('settings-form') 5 | const toggleSettingsButton = document.getElementById('toggle-settings') 6 | 7 | const settings = JSON.parse(localStorage.getItem('settings')) 8 | 9 | if (!settings) { 10 | settingsForm.style.display = 'block' 11 | } else { 12 | settingsForm.style.display = 'none' 13 | toggleSettingsButton.style.display = 'block' 14 | } 15 | 16 | toggleSettingsButton.addEventListener('click', () => { 17 | if (settingsForm.style.display === 'none') { 18 | settingsForm.style.display = 'block' 19 | } else { 20 | settingsForm.style.display = 'none' 21 | } 22 | }) 23 | }) 24 | 25 | document.getElementById('settings-form').addEventListener('submit', (event) => { 26 | event.preventDefault() 27 | lat = document.getElementById('latitude').value 28 | lon = document.getElementById('longitude').value 29 | units = document.getElementById('units').value 30 | const timeFormat = document.getElementById('time-format').value 31 | twelveHour = timeFormat === '12' 32 | 33 | localStorage.setItem( 34 | 'settings', 35 | JSON.stringify({ lat, lon, units, timeFormat }) 36 | ) 37 | document.getElementById('settings-form').style.display = 'none' 38 | document.getElementById('toggle-settings').style.display = 'block' 39 | localStorage.removeItem('weatherData') 40 | }) 41 | 42 | const settings = JSON.parse(localStorage.getItem('settings')) || { 43 | lat: '0', 44 | lon: '0', 45 | units: 'fahrenheit', 46 | timeFormat: '12', 47 | } 48 | 49 | let lat = settings.lat 50 | let lon = settings.lon 51 | let units = settings.units 52 | let twelveHour = settings.timeFormat === '12' 53 | 54 | let prevSec = -1 55 | let isFetching = false 56 | 57 | async function updateClock() { 58 | const now = new Date() 59 | const currSec = now.getSeconds() 60 | 61 | if (currSec !== prevSec) { 62 | prevSec = currSec 63 | 64 | let hours = now.getHours() 65 | const ampm = hours >= 12 ? 'pm' : 'am' 66 | if (twelveHour) { 67 | hours = hours % 12 || 12 68 | } 69 | 70 | const minutes = now.getMinutes().toString().padStart(2, '0') 71 | const seconds = currSec.toString().padStart(2, '0') 72 | 73 | document.querySelector('.time').textContent = `${hours}:${minutes}` 74 | document.querySelector('.sec').textContent = seconds 75 | document.querySelector('.ampm').textContent = ampm 76 | document.querySelector('.date').textContent = `${ 77 | now.getMonth() + 1 78 | }.${now.getDate()}` 79 | document.querySelector('.day').textContent = now 80 | .toLocaleDateString('en-US', { weekday: 'long' }) 81 | .toLocaleLowerCase() 82 | 83 | checkWeather(now) 84 | } 85 | 86 | requestAnimationFrame(updateClock) 87 | } 88 | 89 | function checkWeather(now) { 90 | const cachedData = localStorage.getItem('weatherData') 91 | if (cachedData) { 92 | const data = JSON.parse(cachedData) 93 | const cachedTime = new Date(data.current.time) 94 | 95 | if (now - cachedTime < (15 * 60 + 10) * 1000) { 96 | updateWeather(data) 97 | return 98 | } 99 | } 100 | 101 | if (!isFetching) { 102 | fetchAPI() 103 | } 104 | } 105 | 106 | async function fetchAPI() { 107 | console.log(lat, lon, units) 108 | try { 109 | isFetching = true 110 | const response = await fetch( 111 | `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=temperature_2m,weather_code&temperature_unit=${units}` 112 | ) 113 | const data = await response.json() 114 | console.log('successful fetch', data) 115 | localStorage.setItem('weatherData', JSON.stringify(data)) 116 | updateWeather(data) 117 | isFetching = false 118 | } catch (error) { 119 | console.error('error fetching weather data:', error) 120 | isFetching = false 121 | } 122 | } 123 | 124 | function updateWeather(data) { 125 | const temperature = Math.round(data.current.temperature_2m) 126 | const weatherCode = data.current.weather_code 127 | const now = new Date() 128 | const isDaytime = now.getHours() >= 6 && now.getHours() < 18 129 | 130 | document.querySelector('.temp').textContent = `${temperature}°` 131 | document.querySelector('.weather').textContent = 132 | weatherCodes[weatherCode][ 133 | isDaytime ? 'dayDescription' : 'nightDescription' 134 | ].toLocaleLowerCase() 135 | } 136 | 137 | updateClock() 138 | -------------------------------------------------------------------------------- /index.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/refact0r/startlake/847d89eea9d599fd1a58afd26e9f67d0bad40567/index.zip -------------------------------------------------------------------------------- /lake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/refact0r/startlake/847d89eea9d599fd1a58afd26e9f67d0bad40567/lake.jpg -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "startlake", 3 | "version": "1.0", 4 | "manifest_version": 2, 5 | "chrome_url_overrides": { 6 | "newtab": "index.html" 7 | }, 8 | "browser_specific_settings": { 9 | "gecko": { 10 | "id": "startlake@refact0r.dev" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spacegrotesk.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/refact0r/startlake/847d89eea9d599fd1a58afd26e9f67d0bad40567/spacegrotesk.woff2 -------------------------------------------------------------------------------- /weatherCodes.js: -------------------------------------------------------------------------------- 1 | export const weatherCodes = { 2 | 0: { 3 | dayDescription: 'Sunny', 4 | nightDescription: 'Clear', 5 | }, 6 | 1: { 7 | dayDescription: 'Sunny', 8 | nightDescription: 'Clear', 9 | }, 10 | 2: { 11 | dayDescription: 'Partly Cloudy', 12 | nightDescription: 'Partly Cloudy', 13 | }, 14 | 3: { 15 | dayDescription: 'Cloudy', 16 | nightDescription: 'Cloudy', 17 | }, 18 | 45: { 19 | dayDescription: 'Foggy', 20 | nightDescription: 'Foggy', 21 | }, 22 | 48: { 23 | dayDescription: 'Foggy', 24 | nightDescription: 'Foggy', 25 | }, 26 | 51: { 27 | dayDescription: 'L Drizzle', 28 | nightDescription: 'L Drizzle', 29 | }, 30 | 53: { 31 | dayDescription: 'Drizzle', 32 | nightDescription: 'Drizzle', 33 | }, 34 | 55: { 35 | dayDescription: 'H Drizzle', 36 | nightDescription: 'H Drizzle', 37 | }, 38 | 56: { 39 | dayDescription: 'L Drizzle', 40 | nightDescription: 'L Drizzle', 41 | }, 42 | 57: { 43 | dayDescription: 'Drizzle', 44 | nightDescription: 'Drizzle', 45 | }, 46 | 61: { 47 | dayDescription: 'L Rain', 48 | nightDescription: 'L Rain', 49 | }, 50 | 63: { 51 | dayDescription: 'Rain', 52 | nightDescription: 'Rain', 53 | }, 54 | 65: { 55 | dayDescription: 'H Rain', 56 | nightDescription: 'H Rain', 57 | }, 58 | 66: { 59 | dayDescription: 'L Rain', 60 | nightDescription: 'L Rain', 61 | }, 62 | 67: { 63 | dayDescription: 'Rain', 64 | nightDescription: 'Rain', 65 | }, 66 | 71: { 67 | dayDescription: 'L Snow', 68 | nightDescription: 'L Snow', 69 | }, 70 | 73: { 71 | dayDescription: 'Snow', 72 | nightDescription: 'Snow', 73 | }, 74 | 75: { 75 | dayDescription: 'H Snow', 76 | nightDescription: 'H Snow', 77 | }, 78 | 77: { 79 | dayDescription: 'Snow Grains', 80 | nightDescription: 'Snow Grains', 81 | }, 82 | 80: { 83 | dayDescription: 'L Showers', 84 | nightDescription: 'L Showers', 85 | }, 86 | 81: { 87 | dayDescription: 'Showers', 88 | nightDescription: 'Showers', 89 | }, 90 | 82: { 91 | dayDescription: 'H Showers', 92 | nightDescription: 'H Showers', 93 | }, 94 | 85: { 95 | dayDescription: 'L Snow Showers', 96 | nightDescription: 'L Snow Showers', 97 | }, 98 | 86: { 99 | dayDescription: 'Snow Showers', 100 | nightDescription: 'Snow Showers', 101 | }, 102 | 95: { 103 | dayDescription: 'Thunderstorm', 104 | nightDescription: 'Thunderstorm', 105 | }, 106 | 96: { 107 | dayDescription: 'L Hailstorm', 108 | nightDescription: 'L Hailstorm', 109 | }, 110 | 99: { 111 | dayDescription: 'Hailstorm', 112 | nightDescription: 'Hailstorm', 113 | }, 114 | } 115 | --------------------------------------------------------------------------------