forked from d33mobile/dday
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathindex.html
More file actions
345 lines (323 loc) · 17.4 KB
/
Copy pathindex.html
File metadata and controls
345 lines (323 loc) · 17.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>D-Day · Unconference · Hakierspejs Łódź</title>
<meta name="description" content="D-Day — unconference w Hakierspejsie w Łodzi. Sobota 8 sierpnia 2026, 14:00–22:00. Wstęp darmowy, zapisy obowiązkowe.">
<meta property="og:title" content="D-Day · Unconference · Hakierspejs Łódź">
<meta property="og:description" content="Sobota 8 sierpnia 2026, 14:00–22:00. Zielona 30 LU3, Łódź. Wstęp darmowy, zapisy obowiązkowe.">
<meta property="og:type" content="website">
<style>
:root{
--bg:#0a0e14;
--bg2:#0d1420;
--fg:#e6edf3;
--muted:#8b98a5;
--accent:#39d353;
--accent2:#2dd4bf;
--card:rgba(255,255,255,.04);
--border:rgba(255,255,255,.09);
}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:'Courier New',ui-monospace,SFMono-Regular,Menlo,monospace;
background:radial-gradient(1100px 700px at 50% -15%,#12203a 0%,var(--bg) 60%);
color:var(--fg);
min-height:100vh;
line-height:1.45;
-webkit-font-smoothing:antialiased;
overflow-x:hidden;
}
.wrap{max-width:900px;margin:0 auto;padding:clamp(1.5rem,4vw,2.75rem) 1.25rem 2.5rem}
/* header row: title left, badge right — compact */
.head{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin-bottom:1.5rem}
.brand{display:flex;align-items:baseline;gap:.7rem;flex-wrap:wrap}
h1{
font-size:clamp(2.2rem,7vw,3.4rem);
font-weight:900;letter-spacing:-.03em;line-height:1;
background:linear-gradient(120deg,var(--accent),var(--accent2));
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
}
.brand .sub{font-size:.9rem;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}
.badge{
font-size:.72rem;letter-spacing:.22em;text-transform:uppercase;color:var(--accent);
border:1px solid var(--border);padding:.35rem .8rem;border-radius:999px;background:var(--card);white-space:nowrap;
}
/* countdown — first, prominent */
.live{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:1.4rem 1.25rem 1.5rem;margin-bottom:1.25rem}
.count-label{font-size:.72rem;letter-spacing:.24em;text-transform:uppercase;color:var(--muted);text-align:center;margin-bottom:1rem}
.count-label b{color:var(--accent)}
.countdown{display:grid;grid-template-columns:repeat(4,1fr);gap:.55rem;max-width:440px;margin:0 auto}
.unit{background:var(--bg2);border:1px solid var(--border);border-radius:12px;padding:.9rem .35rem;min-width:0;text-align:center}
.unit .num{font-size:clamp(1.6rem,8vw,2.7rem);font-weight:900;line-height:1;font-variant-numeric:tabular-nums}
.unit .lbl{font-size:.6rem;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-top:.45rem}
.cta{text-align:center;margin-top:1.25rem}
.btn{
display:inline-block;font-family:inherit;font-size:1rem;font-weight:700;letter-spacing:.04em;
padding:.85rem 2rem;border-radius:11px;border:1px solid transparent;cursor:pointer;text-decoration:none;
transition:transform .12s ease,box-shadow .12s ease;
}
.btn-primary{background:linear-gradient(120deg,var(--accent),var(--accent2));color:#04140a;box-shadow:0 8px 26px rgba(57,211,83,.22)}
.btn-primary:hover{transform:translateY(-2px);box-shadow:0 12px 36px rgba(57,211,83,.32)}
.btn-primary[disabled]{background:var(--card);color:var(--muted);border:1px solid var(--border);box-shadow:none;cursor:not-allowed;transform:none}
/* registered-seats counter — big, eye-catching, separate from countdown */
.seats{
background:linear-gradient(160deg,rgba(57,211,83,.08),rgba(45,212,191,.05));
border:1px solid var(--border);border-radius:16px;
padding:1.6rem 1.25rem 1.7rem;margin-bottom:1.25rem;text-align:center;
}
.seats-head{font-size:.72rem;letter-spacing:.24em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}
.seats-num{
font-size:clamp(3.6rem,18vw,6.5rem);font-weight:900;line-height:.9;letter-spacing:-.04em;
font-variant-numeric:tabular-nums;
background:linear-gradient(120deg,var(--accent),var(--accent2));
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
}
.seats.full .seats-num{
font-size:clamp(2.4rem,11vw,4rem);
background:linear-gradient(120deg,#f85149,#ff8a3d);
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
}
.seats-of{font-size:1.02rem;font-weight:700;color:var(--fg);margin-top:.4rem}
.seats-of b{color:var(--accent);font-size:1.15rem}
.seats.full .seats-of b{color:#f85149}
.bar{height:12px;border-radius:999px;background:var(--bg2);border:1px solid var(--border);
overflow:hidden;max-width:440px;margin:1.05rem auto .85rem}
.bar-fill{height:100%;width:0;border-radius:999px;
background:linear-gradient(90deg,var(--accent),var(--accent2));
transition:width .9s cubic-bezier(.22,1,.36,1)}
.seats.full .bar-fill{background:linear-gradient(90deg,#f85149,#ff8a3d)}
.seats-note{font-size:.78rem;letter-spacing:.06em;color:var(--muted)}
.seats.full .seats-note{color:#f85149;font-weight:700;letter-spacing:.12em;text-transform:uppercase}
.seats-waitlist{font-size:.9rem;letter-spacing:.04em;color:var(--accent2);margin:-.35rem 0 .7rem;font-weight:700}
.seats-waitlist b{color:var(--accent2);font-variant-numeric:tabular-nums}
/* how-to-register instructions — visible before and after registration opens */
.howto{background:var(--card);border:1px solid var(--border);border-radius:16px;
padding:1.25rem 1.35rem;margin-bottom:1.25rem}
.howto-head{font-size:.72rem;letter-spacing:.24em;text-transform:uppercase;color:var(--accent);margin-bottom:.7rem}
.howto ol{list-style:none;counter-reset:step;display:grid;gap:.6rem}
.howto li{counter-increment:step;position:relative;padding-left:2.1rem;font-size:.9rem;line-height:1.5;color:var(--fg)}
.howto li::before{content:counter(step);position:absolute;left:0;top:0;
width:1.5rem;height:1.5rem;border-radius:50%;display:flex;align-items:center;justify-content:center;
font-size:.78rem;font-weight:700;color:#04140a;background:linear-gradient(120deg,var(--accent),var(--accent2))}
.howto code{font-family:inherit;font-weight:700;color:var(--accent);background:var(--bg2);
border:1px solid var(--border);border-radius:6px;padding:.05rem .4rem}
.howto a{color:var(--accent2);text-decoration:none;border-bottom:1px solid rgba(45,212,191,.35)}
.howto a:hover{color:var(--accent)}
.howto .limit{margin-top:.75rem;font-size:.78rem;letter-spacing:.06em;color:var(--muted)}
/* info grid — dense */
.meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.7rem;margin-bottom:1.25rem}
.cell{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:.85rem .95rem}
.cell .k{font-size:.65rem;letter-spacing:.2em;text-transform:uppercase;color:var(--muted);margin-bottom:.35rem}
.cell .v{font-size:1.02rem;font-weight:700}
.cell .v small{display:block;font-size:.8rem;font-weight:400;color:var(--muted);margin-top:.15rem}
/* footer / chat row */
.foot{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;
padding:.95rem 1.1rem;background:var(--card);border:1px solid var(--border);border-radius:12px}
.foot .desc{font-size:.85rem;color:var(--muted);line-height:1.5;flex:1 1 260px}
.chat{display:inline-flex;align-items:center;gap:.5rem;white-space:nowrap;font-size:.95rem;font-weight:700;
color:var(--accent2);text-decoration:none;border:1px solid var(--border);border-radius:10px;padding:.7rem 1.1rem;background:var(--bg2)}
.chat:hover{border-color:var(--accent2)}
.tags{margin-top:1rem;text-align:center;font-size:.75rem;color:var(--muted);opacity:.6;letter-spacing:.05em}
.legal{margin-top:.55rem;text-align:center;font-size:.8rem}
.legal a{color:var(--accent2);text-decoration:none;border-bottom:1px solid rgba(45,212,191,.35)}
.legal a:hover{color:var(--accent)}
@media (max-width:480px){.unit{padding:.75rem .3rem}}
</style>
</head>
<body>
<main class="wrap">
<header class="head">
<div class="brand">
<h1>D-Day</h1>
<span class="sub">Unconference · Hakierspejs Łódź</span>
</div>
<span class="badge">8 sierpnia 2026</span>
</header>
<!-- Registration-open date below (26 lipca 2026, 15:00) mirrors
internal/regwindow.OpenMoment / OpenStartText — keep them in sync;
the JS constant REG_OPEN further down encodes the same instant. -->
<section class="live">
<p class="count-label" id="count-label">Zapisy startują za</p>
<div class="countdown" id="countdown">
<div class="unit"><div class="num" id="d">--</div><div class="lbl">dni</div></div>
<div class="unit"><div class="num" id="h">--</div><div class="lbl">godz</div></div>
<div class="unit"><div class="num" id="m">--</div><div class="lbl">min</div></div>
<div class="unit"><div class="num" id="s">--</div><div class="lbl">sek</div></div>
</div>
<div class="cta">
<a class="btn btn-primary" id="register" href="#" aria-disabled="true" disabled>Zapisy wkrótce</a>
</div>
</section>
<section class="seats" id="seats">
<div class="seats-head">Zapisanych na D-Day</div>
<div class="seats-num" id="seat-count">—</div>
<div class="seats-of" id="seat-of"><b>—</b> / 20 miejsc</div>
<div class="bar"><div class="bar-fill" id="bar-fill"></div></div>
<div class="seats-waitlist" id="seat-waitlist" hidden>Lista rezerwowa: <b id="wait-count">0</b> / 20</div>
<div class="seats-note" id="seat-note">20 miejsc + 20 rezerwowych</div>
</section>
<section class="howto" id="howto">
<div class="howto-head">Jak się zapisać</div>
<ol>
<li>Napisz <code>!register</code> do bota <a href="https://matrix.to/#/@ddaybot:matrix.org" target="_blank" rel="noopener">@ddaybot:matrix.org</a> (albo na czacie <a href="https://matrix.to/#/%23hs-ldz-dday:matrix.org" target="_blank" rel="noopener">#hs-ldz-dday</a>).</li>
<li>Bot odeśle Ci prywatny link do formularza (miejscowość + e-mail).</li>
<li>Wypełnij formularz — dostaniesz swój numer uczestnika.</li>
</ol>
<p class="limit">20 miejsc podstawowych + 20 na liście rezerwowej. Zapisy startują w niedzielę 26 lipca, 15:00 czasu PL.</p>
</section>
<section class="meta">
<div class="cell"><div class="k">Kiedy</div><div class="v">Sob, 8 sierpnia<small>14:00 – 22:00</small></div></div>
<div class="cell"><div class="k">Gdzie</div><div class="v">Hakierspejs<small>Zielona 30 LU3, Łódź</small></div></div>
<div class="cell"><div class="k">Wstęp</div><div class="v">Darmowy<small>zapisy obowiązkowe</small></div></div>
<div class="cell" id="regfrom"><div class="k">Zapisy od</div><div class="v">Nd 26 lipca<small>15:00 czasu PL</small></div></div>
</section>
<div class="foot">
<div class="desc">Nieformalny format — agenda tworzona na miejscu przez uczestników. Przyjdź z tematem albo po prostu posłuchaj.</div>
<a class="chat" href="https://matrix.to/#/%23hs-ldz-dday:matrix.org" target="_blank" rel="noopener">💬 Czat na Matrix</a>
</div>
<p class="tags">#dday · #unconference · #hakierspejs</p>
<p class="legal"><a href="/privacy">Polityka prywatności / RODO</a></p>
</main>
<script>
// Two countdown targets. Both mirror internal/regwindow (OpenMoment) and the
// event date shown in the meta grid — keep them in sync when either changes.
// REG_OPEN — registration opens: niedziela 26 lipca 2026, 15:00 Europe/Warsaw
// EVENT — D-Day starts: sobota 8 sierpnia 2026, 14:00 Europe/Warsaw
// Both moments fall in CEST (UTC+2), so the UTC instants are 2h earlier.
var REG_OPEN = Date.UTC(2026, 6, 26, 13, 0, 0); // 15:00 Warsaw == 13:00 UTC
var EVENT = Date.UTC(2026, 7, 8, 12, 0, 0); // 14:00 Warsaw == 12:00 UTC
// apiOpen stays null until /api/count answers, then holds its `open` flag.
// On static hosting (e.g. GitHub Pages, no API) it never resolves and the
// clock alone (now >= REG_OPEN) decides whether registration is open.
var apiOpen = null;
var d=document.getElementById('d'),h=document.getElementById('h'),
m=document.getElementById('m'),s=document.getElementById('s'),
label=document.getElementById('count-label'),
btn=document.getElementById('register'),
cd=document.getElementById('countdown'),
ctaWired=false;
function pad(n){return (n<10?'0':'')+n}
// Flip the CTA from the disabled "Zapisy wkrótce" placeholder to an active
// link to the Matrix bot. Idempotent: safe to call on every tick.
function wireCTA(){
if(ctaWired)return;
btn.textContent='Zapisz się przez Matrix';
btn.setAttribute('href','https://matrix.to/#/@ddaybot:matrix.org');
btn.setAttribute('target','_blank');
btn.setAttribute('rel','noopener');
btn.removeAttribute('disabled');
btn.removeAttribute('aria-disabled');
ctaWired=true;
}
function fmt(diff){
if(diff<0)diff=0;
d.textContent=Math.floor(diff/86400);
h.textContent=pad(Math.floor(diff%86400/3600));
m.textContent=pad(Math.floor(diff%3600/60));
s.textContent=pad(diff%60);
}
function tick(){
var now=Date.now();
// Prefer the API's open flag; fall back to the clock when it's absent.
var isOpen = apiOpen===true || now>=REG_OPEN;
// Once registration is open the "Zapisy od" tile is redundant — hide it.
// The "Jak się zapisać" steps only make sense once open — show them then.
var regFrom=document.getElementById('regfrom');
if(regFrom) regFrom.style.display=isOpen?'none':'';
var howto=document.getElementById('howto');
if(howto) howto.style.display=isOpen?'':'none';
// No signups yet before opening — hide the registered counter too.
var seats=document.getElementById('seats');
if(seats) seats.style.display=isOpen?'':'none';
if(!isOpen){
// Before registration opens — count down to REG_OPEN, CTA stays disabled.
label.textContent='Zapisy startują za';
cd.style.display='';
fmt(Math.floor((REG_OPEN-now)/1000));
return;
}
// Registration is open — CTA becomes an active Matrix link.
wireCTA();
if(now<EVENT){
// Count down to the start of the event.
label.innerHTML='Do startu <b>D-Day</b>';
cd.style.display='';
fmt(Math.floor((EVENT-now)/1000));
}else{
// Event under way — drop the countdown grid.
label.innerHTML='<b>D-Day trwa!</b>';
cd.style.display='none';
}
}
tick();
setInterval(tick,1000);
// Registered-seats counter — pulls same-origin /api/count (allowed by CSP
// connect-src 'self'). On static hosting (e.g. GitHub Pages) the endpoint is
// absent: the block degrades to a "—" placeholder, never an error in the UI.
(function(){
var seats=document.getElementById('seats'),
numEl=document.getElementById('seat-count'),
ofEl=document.getElementById('seat-of'),
fill=document.getElementById('bar-fill'),
note=document.getElementById('seat-note'),
waitEl=document.getElementById('seat-waitlist'),
waitNum=document.getElementById('wait-count');
function animateTo(target){
var dur=900,t0=null;
function step(ts){
if(t0===null)t0=ts;
var p=Math.min((ts-t0)/dur,1);
numEl.textContent=Math.round(target*(1-Math.pow(1-p,3)));
if(p<1)requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
fetch('/api/count',{headers:{'Accept':'application/json'}})
.then(function(r){if(!r.ok)throw 0;return r.json();})
.then(function(j){
// Let the countdown react to the authoritative open flag, then re-tick.
if(j.open!=null){apiOpen=!!j.open;tick();}
var c=j.count|0,
lim=(j.limit|0)||20,
wl=(j.waitlist|0)||20,
// Confirmed + waiting-list counts; fall back to deriving them from
// count when an older API without the fields answers.
confirmed=(j.confirmed!=null)?(j.confirmed|0):Math.min(c,lim),
wcount=(j.waitlistCount!=null)?(j.waitlistCount|0):Math.max(0,c-lim),
full=(j.full!=null)?!!j.full:(c>=lim+wl);
ofEl.innerHTML='<b>'+confirmed+'</b> / '+lim+' miejsc';
var pct=lim?Math.max(0,Math.min(100,confirmed/lim*100)):0;
// Waiting-list line appears only once the confirmed seats are gone.
if(wcount>0){
waitNum.textContent=wcount;
waitEl.innerHTML='Lista rezerwowa: <b>'+wcount+'</b> / '+wl;
waitEl.hidden=false;
}else{
waitEl.hidden=true;
}
if(full){
seats.classList.add('full');
numEl.textContent='KOMPLET';
fill.style.width='100%';
note.textContent='Brak miejsc — '+lim+' uczestników + '+wl+' rezerwowych wyczerpane';
}else{
seats.classList.remove('full');
animateTo(confirmed);
fill.style.width=pct+'%';
note.textContent=(confirmed>=lim)
? 'Miejsca podstawowe zajęte — trwają zapisy na listę rezerwową'
: lim+' miejsc + '+wl+' rezerwowych';
}
})
.catch(function(){
numEl.textContent='—';
ofEl.innerHTML='<b>—</b> / 20 miejsc';
fill.style.width='0';
waitEl.hidden=true;
});
})();
</script>
</body>
</html>