Skip to content

Doc, test-modus og nytt tema #1

Merged
merged 9 commits into from
Nov 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Webclock

En vakker nettbasert klokkeapplikasjon med analog og digital visning for flere norske byer (Trondheim, Ålesund og Gjøvik). Klokken inkluderer dynamiske væranimasjoner og sesongbaserte temaer.

![screenshot](doc-screenshot.png)

## Temaer

Applikasjonen bytter automatisk mellom temaer basert på dagens dato

## Testmodus

Testmodus lar deg manuelt overstyre den automatiske temavelgeren og teste ulike temaer uten å vente på spesifikke datoer.

![Test mode theme selector](doc-testmode.png)

### Aktivere testmodus

Legg til `?mode=test` i URL-en:

```
http://localhost/index.html?mode=test
```

## Legge til nye temaer

Legg til nye temadefinisjoner i `themes`-objektet:

```javascript
mytheme: {
dateRange: { startMonth: 0, startDay: 1, endMonth: 0, endDay: 31 },
clockFace: 'clock-face-mytheme.png',
clockFaceScale: 'cover',
background: 'planks-mytheme.png',
backgroundSize: 'cover',
textColor: '#ffffff',
centerDotColor: 'radial-gradient(...)',
centerDotBorder: '#888',
handColors: {
hour: 'linear-gradient(..s.)',
minute: 'linear-gradient(...)',
second: 'linear-gradient(...)'
}
}
```

Legge til tema-valg i tema-velgeren (test-modus):

```Html
<div class="theme-selector">
<label for="theme-dropdown">Theme:</label>
<select id="theme-dropdown">
<option value="auto">Auto (Date-based)</option>
<option value="default">Default</option>
<option value="halloween">Halloween</option>
<option value="movember">Movember</option>
<option value="christmas">Christmas</option>
<option value="saami">Sámi</option>
<option value="mytheme">My Theme</option>
</select>
</div>
```

## Takk til

- Værdata levert av [yr.no](https://yr.no) / Meteorologisk institutt
- Måneberegninger basert på astronomiske algoritmer

Binary file added clock-face-saami.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc-testmode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
184 changes: 184 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,61 @@
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow-x: auto;
}

.theme-selector {
position: fixed;
top: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
padding: 15px 20px;
border-radius: 10px;
box-shadow: 0 4px 16px rgba(31, 38, 135, 0.37);
border: 1px solid rgba(255, 255, 255, 0.18);
z-index: 1000;
display: none; /* Hidden by default */
align-items: center;
gap: 10px;
}

.theme-selector.visible {
display: flex;
}

.theme-selector label {
color: white;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
font-size: 1rem;
}

.theme-selector select {
padding: 8px 12px;
border-radius: 5px;
border: 2px solid rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.9);
color: #333;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
outline: none;
}

.theme-selector select:hover {
background: rgba(255, 255, 255, 1);
border-color: rgba(255, 255, 255, 0.5);
}

.theme-selector select:focus {
border-color: #4CAF50;
box-shadow: 0 0 5px rgba(76, 175, 80, 0.5);
}

.clocks-container {
display: flex;
gap: 40px;
Expand Down Expand Up @@ -288,6 +338,24 @@
}

@media (max-width: 768px) {
.theme-selector {
position: relative;
top: 0;
right: 0;
margin-bottom: 20px;
width: calc(100% - 40px);
justify-content: center;
}

.theme-selector label {
font-size: 0.9rem;
}

.theme-selector select {
font-size: 0.9rem;
padding: 6px 10px;
}

.clocks-container {
flex-direction: column;
gap: 20px;
Expand Down Expand Up @@ -535,6 +603,18 @@
</style>
</head>
<body>
<div class="theme-selector">
<label for="theme-dropdown">Theme:</label>
<select id="theme-dropdown">
<option value="auto">Auto (Date-based)</option>
<option value="default">Default</option>
<option value="halloween">Halloween</option>
<option value="movember">Movember</option>
<option value="christmas">Christmas</option>
<option value="saami">Sámi</option>
</select>
</div>

<div class="clocks-container">
<!-- Trondheim Clock -->
<div class="clock-container">
Expand Down Expand Up @@ -1280,6 +1360,39 @@
second: 'linear-gradient(to bottom, #ffd700, #ffed4e, #ffd700)' // Gold
}
},
saami: {
/*
Sámi theme by on@ntnu.no
*/
dateRange: { startMonth: 1, startDay: 1, endMonth: 1, endDay: 28 }, // Feb 1-28
clockFace: 'clock-face-saami.png',
clockFaceScale: 'cover',
background: 'saami-background_16-9.png',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundAttachment: 'fixed',
textColor: '#d7ffa6',
centerDotColor: '#d7ffa6',
centerDotBorder: '#FFF',
handColors: {
hour: '#FFF',
minute: '#FFF',
second: '#FF3300'
},
clockContainer: {
background: 'rgba(0, 0, 0, 0.15)',
backdropFilter: 'blur(0px)',
borderRadius: '20px',
border: '1px solid rgba(215, 255, 166, 0.5)',
boxShadow: '0 8px 32px rgba(215, 255, 166, 0.3)'
},
cityNames: {
trondheim: 'Tråante',
alesund: 'Ålesund',
gjovik: 'Gjøvik'
}
},
default: {
clockFace: 'clock-face.png',
clockFaceScale: 'cover',
Expand All @@ -1299,6 +1412,13 @@
};

function getCurrentTheme() {
// Check if user has manually selected a theme
const selectedTheme = localStorage.getItem('selectedTheme');
if (selectedTheme && selectedTheme !== 'auto') {
return themes[selectedTheme] || themes.default;
}

// Otherwise use date-based automatic theme selection
const today = new Date();
const month = today.getMonth(); // 0-11
const day = today.getDate();
Expand Down Expand Up @@ -1362,6 +1482,45 @@
element.style.boxShadow = handGlow;
});

// Apply clock container styling if specified in theme
const defaultContainerStyles = {
background: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(10px)',
borderRadius: '20px',
padding: '30px',
boxShadow: '0 8px 32px rgba(31, 38, 135, 0.37)',
border: '1px solid rgba(255, 255, 255, 0.18)'
};

const containerStyles = theme.clockContainer || {};
document.querySelectorAll('.clock-container').forEach(element => {
// Apply each style, using theme value if provided, otherwise use default
element.style.background = containerStyles.background || defaultContainerStyles.background;
element.style.backdropFilter = containerStyles.backdropFilter || defaultContainerStyles.backdropFilter;
element.style.borderRadius = containerStyles.borderRadius || defaultContainerStyles.borderRadius;
element.style.padding = containerStyles.padding || defaultContainerStyles.padding;
element.style.boxShadow = containerStyles.boxShadow || defaultContainerStyles.boxShadow;
element.style.border = containerStyles.border || defaultContainerStyles.border;
});

// Apply city name overrides if specified in theme
if (theme.cityNames) {
Object.keys(theme.cityNames).forEach(cityKey => {
const cityNameElement = document.querySelector(`#clock-${cityKey}`).closest('.clock-container').querySelector('.city-name');
if (cityNameElement) {
cityNameElement.textContent = theme.cityNames[cityKey];
}
});
} else {
// Reset to default city names if no override
Object.keys(clocks).forEach(cityKey => {
const cityNameElement = document.querySelector(`#clock-${cityKey}`).closest('.clock-container').querySelector('.city-name');
if (cityNameElement) {
cityNameElement.textContent = clocks[cityKey].cityName.toUpperCase();
}
});
}

// Update CSS for clock face image (pseudo-element)
const existingStyle = document.getElementById('theme-style');
if (existingStyle) {
Expand All @@ -1384,6 +1543,31 @@
// Check for theme change every hour
setInterval(applyTheme, 1000 * 60 * 60);

// Initialize theme dropdown
document.addEventListener('DOMContentLoaded', function() {
const themeDropdown = document.getElementById('theme-dropdown');
const themeSelector = document.querySelector('.theme-selector');

// Check if mode=test is in the URL
const urlParams = new URLSearchParams(window.location.search);
const mode = urlParams.get('mode');

if (mode === 'test') {
themeSelector.classList.add('visible');
}

// Load saved theme preference
const savedTheme = localStorage.getItem('selectedTheme') || 'auto';
themeDropdown.value = savedTheme;

// Handle theme selection
themeDropdown.addEventListener('change', function() {
const selectedTheme = this.value;
localStorage.setItem('selectedTheme', selectedTheme);
applyTheme();
});
});

// Initialize weather effects with default values first
initializeWeather();

Expand Down
Binary file added saami-background_16-9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.