display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1;overflow:hidden;
}
-/* Badge (Public/Home/etc.) with gradient fill */
+/* Badge */
.chip{
- position:absolute;top:10px;right:10px;
- font-size:.75rem;
- padding:.12rem .5rem;
- border-radius:999px;
- background:linear-gradient(135deg,var(--accent) 0%,var(--accent-alt) 100%);
+ position:absolute; top:10px; right:10px;
+ font-size:.75rem; padding:.12rem .5rem;
+ border-radius:999px; font-weight:500;
+ background: var(--accent); /* solid green */
color:#fff;
- border:none;
}
-/* Neutral stripe overlay for LAN-only / offline cards */
-.card.is-lan::after,
-.card.is-offline::after {
- content: '';
- position: absolute;
- inset: 0;
- pointer-events: none;
- border-radius: 10px;
- background: repeating-linear-gradient(
- 45deg,
- transparent 0 12px,
- rgba(255, 255, 255, 0.08) 12px 24px
- );
-}
+/* When away from LAN: mark card as LAN-only -> red chip */
+.card.is-lan .chip { background:#ef4444; } /* red-500 */
+
+/* hide chips until we decide color */
+.chip--pending { visibility: hidden; }
/* Links */
a{color:var(--accent)}
.card{background:var(--panel);border:1px solid var(--ring);box-shadow:none}
.card:hover{box-shadow:0 4px 10px rgba(0,0,0,.10)}
-
- /* Light mode adjustment: stronger contrast */
- .card.is-lan::after,
- .card.is-offline::after {
- background: repeating-linear-gradient(
- 45deg,
- transparent 0 12px,
- rgba(0, 0, 0, 0.08) 12px 24px
- );
- }
}
/* ---- Logo tuned to match favicon proportions ---- */
<h2 style="margin:0 0 .5rem 0;">Services</h2>
<main class="grid" id="grid">
- {% for s in services %}
- <a
- class="card"
- href="{{ s.href }}"
- rel="noopener noreferrer"
- {% if s.lan %} data-lan="true"{% endif %}
- >
- {% if s.tag %}<span class="chip">{{ s.tag }}</span>{% endif %}
- <h3>{{ s.name }}</h3>
- <p>{{ s.desc }}</p>
- </a>
- {% endfor %}
+{% for s in services %}
+ <a
+ class="card{% if s.lan %} is-lan{% endif %}"
+ href="{{ s.href }}"
+ rel="noopener noreferrer"
+ {% if s.lan %} data-lan="true"{% endif %}
+ >
+ {% if s.tag %}<span class="chip chip--pending">{{ s.tag }}</span>{% endif %}
+ <h3>{{ s.name }}</h3>
+ <p>{{ s.desc }}</p>
+ </a>
+{% endfor %}
</main>
-
<script>
(function () {
- const PING_URL = "https://lan.zndr.dk/ping"; // your HTTPS LAN endpoint
+ const PING_URL = "https://lan.zndr.dk/ping"; // 200 only on LAN
const TIMEOUT_MS = 1500;
const lanCards = Array.from(document.querySelectorAll('.card[data-lan="true"]'));
- if (!lanCards.length) return;
+ const allChips = Array.from(document.querySelectorAll('.chip'));
- function stripe() {
- lanCards.forEach(c => c.classList.add('is-lan'));
+ function reveal() {
+ // show all chips at once (no flash before this)
+ allChips.forEach(c => c.classList.remove('chip--pending'));
}
- // Try a fast CORS fetch: only a readable 200 at home should count as "LAN"
+ // If there are no LAN-only items, just reveal chips (all green)
+ if (!lanCards.length) { reveal(); return; }
+
+ // Decide home/away by CORS-validated fetch
const ctrl = new AbortController();
- const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
-
- fetch(PING_URL, {
- signal: ctrl.signal,
- credentials: "omit",
- cache: "no-store"
- })
- .then(res => {
- clearTimeout(timer);
- if (!res.ok) stripe(); // away → stripe
- // home (res.ok) → do nothing
- })
- .catch(() => { clearTimeout(timer); stripe(); }); // error/timeout → stripe
+ const to = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
+
+ fetch(PING_URL, { signal: ctrl.signal, credentials: "omit", cache: "no-store" })
+ .then(res => {
+ clearTimeout(to);
+ if (res.ok) {
+ // Home: ensure LAN-only cards are NOT red
+ lanCards.forEach(card => card.classList.remove('is-lan'));
+ } else {
+ // Away: keep LAN-only cards red (leave is-lan on)
+ }
+ reveal();
+ })
+ .catch(() => {
+ clearTimeout(to);
+ // Timeout / away: keep LAN-only cards red
+ reveal();
+ });
})();
</script>