| 1 |
1 |
<!-- |
| 2 |
2 |
Author: Simon-Pierre Boucher <contact@spboucher.ai> |
| 3 |
3 |
File: index.html |
| 4 |
|
− Desc: Frontend Sorti-Ka — recherche + fiches événement (SPA sans build, |
| 5 |
|
− routeur History API, consomme /api). Design system Groupe KA |
| 6 |
|
− « éditorial sharp » via ka-ui (src/ka/tokens.css, chargé en premier) : |
| 7 |
|
− papier #f5f3ee · encre #141814 · vert #1c5c41 · ACCENT FRAMBOISE |
| 8 |
|
− #d6336c · Space Grotesk / Inter / JetBrains Mono · bordures encre + |
| 9 |
|
− ombres décalées + grain de film. Footer commun KaFooter et page |
|
4 |
+ Desc: Frontend Sorti-Ka v2 « guide culturel » — SPA sans build, routeur |
|
5 |
+ History API, consomme /api. Accueil éditorial par sections (À la une, |
|
6 |
+ Ce soir, Fin de semaine, Gratuit, catégories, À découvrir), recherche |
|
7 |
+ centrale + filtres en bottom sheet, timeline de jours, vue Liste|Carte |
|
8 |
+ (Leaflet chargé à la demande), fiches image-first, CTA sticky mobile. |
|
9 |
+ Design system Groupe KA via ka-ui (src/ka/tokens.css, chargé en |
|
10 |
+ premier) : papier #f5f3ee · encre #141814 · ACCENT FRAMBOISE #d6336c |
|
11 |
+ (signal culturel : dates, états actifs, CTA) · Space Grotesk / Inter / |
|
12 |
+ JetBrains Mono · grain de film. Footer commun KaFooter et page |
| 10 |
13 |
/contact : contenu issu de src/ka/ecosystem.json (source de vérité — |
| 11 |
14 |
resynchroniser le HTML si le JSON change). SSR : sortika/seo.py |
| 12 |
15 |
injecte <head> et contenu dans les placeholders SSR_HEAD/SSR_BODY. |
| 58 |
61 |
.btn-ghost{background:var(--surface);color:var(--ink);box-shadow:4px 4px 0 rgba(20,24,20,.12)} |
| 59 |
62 |
.btn-ghost:hover{background:var(--accent-soft)} |
| 60 |
63 |
|
| 61 |
|
−/* ---------- header / ticker ---------- */ |
|
64 |
+/* ---------- header / ticker (compact 2026-08-25) ---------- */ |
| 62 |
65 |
.ticker{background:var(--ink);color:rgba(245,243,238,.85);overflow:hidden; |
| 63 |
|
− font-family:var(--font-mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase; |
| 64 |
|
− padding:7px 0;white-space:nowrap} |
|
66 |
+ font-family:var(--font-mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase; |
|
67 |
+ padding:6px 0;white-space:nowrap} |
| 65 |
68 |
.ticker-track{display:inline-flex;animation:ticker 48s linear infinite;will-change:transform} |
| 66 |
69 |
.ticker span{padding:0 26px;position:relative} |
| 67 |
70 |
.ticker span::after{content:"◆";position:absolute;right:-6px;color:var(--lime);font-size:8px;top:3px} |
| 68 |
71 |
@keyframes ticker{from{transform:translateX(0)}to{transform:translateX(-50%)}} |
| 69 |
72 |
@media(prefers-reduced-motion:reduce){.ticker-track{animation:none}*{transition-duration:.01ms!important}} |
| 70 |
|
−/* header opaque, échelle --z-* (standard header/menu KA 2026-08-22) */ |
|
73 |
+/* header opaque, échelle --z-* (standard header/menu KA 2026-08-22) — |
|
74 |
+ v2 : une seule rangée mince ; sur mobile le contenu événementiel doit |
|
75 |
+ apparaître tout de suite → ticker masqué, badge/tag repliés dans le menu */ |
| 71 |
76 |
.header{position:sticky;top:0;z-index:var(--z-header,500);background:var(--paper); |
| 72 |
77 |
border-bottom:1.5px solid var(--ink)} |
| 73 |
|
−.header-inner{display:flex;align-items:center;flex-wrap:wrap;gap:6px 18px;height:auto;min-height:64px;padding:6px 0} |
|
78 |
+.header-inner{display:flex;align-items:center;gap:14px;min-height:58px;padding:4px 0} |
| 74 |
79 |
.header-inner .gk-badge{flex:none} |
|
80 |
+@media(max-width:980px){.header-inner .gk-badge{display:none}} |
| 75 |
81 |
@media(max-width:760px){ |
| 76 |
|
− .header-inner{min-height:56px;padding:8px 0;gap:6px 10px} |
| 77 |
|
− /* KA Nav v2 (2026-08-25) : la nav inline cède la place au hamburger + panneau |
| 78 |
|
− plein écran fixed — menu visible peu importe la position de scroll */ |
| 79 |
|
− .nav{display:none} |
| 80 |
|
− #auth{margin-left:auto} |
| 81 |
|
− .ka-mnav-btn{display:flex} |
|
82 |
+ .ticker{display:none} |
|
83 |
+ .header-inner{min-height:52px;padding:4px 0;gap:10px} |
| 82 |
84 |
} |
| 83 |
|
−.brand{font-family:var(--font-display);font-weight:700;font-size:24px;letter-spacing:-.03em; |
| 84 |
|
− display:flex;align-items:baseline;line-height:1;cursor:pointer;position:relative} |
|
85 |
+/* le reste des règles mobiles du header est déclaré APRÈS toutes les règles de |
|
86 |
+ base (nav, mnav, btn-ka, user-chip) — voir bloc « header mobile » plus bas : |
|
87 |
+ à spécificité égale, c'est la dernière déclaration qui gagne. */ |
|
88 |
+.brand{font-family:var(--font-display);font-weight:700;font-size:23px;letter-spacing:-.03em; |
|
89 |
+ display:flex;align-items:baseline;line-height:1;cursor:pointer;position:relative;flex:none} |
| 85 |
90 |
/* extension tactile : le logo (98×26) atteint >=44px de cible sans changer le rendu */ |
| 86 |
91 |
.brand::after{content:"";position:absolute;inset:-8px} |
| 87 |
92 |
.brand .ka{background:var(--accent);color:var(--on-accent);border-radius:6px;padding:0 6px 2px;margin-left:2px;transform:rotate(-1deg)} |
| 88 |
93 |
.brand-tag{font-family:var(--font-mono);font-size:10px;text-transform:uppercase; |
| 89 |
|
− letter-spacing:.08em;color:var(--ink-3);margin-left:8px} |
| 90 |
|
−@media(max-width:900px){.brand-tag{display:none}} |
| 91 |
|
−.nav{margin-left:auto;display:flex;gap:6px} |
| 92 |
|
−.nav a{display:inline-flex;align-items:center;min-height:44px;padding:8px 14px; |
| 93 |
|
− border:1.5px solid transparent;border-radius:var(--r-ctl); |
| 94 |
|
− font-family:var(--font-display);font-weight:700;font-size:13.5px;transition:all .15s ease} |
| 95 |
|
−.nav a:hover{border-color:var(--ink);background:var(--accent-soft)} |
|
94 |
+ letter-spacing:.08em;color:var(--ink-3)} |
|
95 |
+@media(max-width:1100px){.brand-tag{display:none}} |
|
96 |
+.nav{margin-left:auto;display:flex;gap:2px} |
|
97 |
+.nav a{display:inline-flex;align-items:center;min-height:44px;padding:8px 13px; |
|
98 |
+ border-radius:999px;font-family:var(--font-display);font-weight:700;font-size:13.5px; |
|
99 |
+ color:var(--ink-2);transition:all .15s ease} |
|
100 |
+.nav a:hover{background:var(--accent-soft);color:var(--ink)} |
| 96 |
101 |
|
| 97 |
102 |
/* ===== KA Nav v2 — menu mobile plein écran (standard 2026-08-25) ===== |
| 98 |
103 |
Panneau position:fixed inset:0 (z-modal) → visible peu importe le scroll. */ |
| 155 |
160 |
font-size:9.5px;font-weight:700;text-transform:uppercase;color:var(--danger);padding:2px} |
| 156 |
161 |
.user-chip .out:hover{text-decoration:underline} |
| 157 |
162 |
@media(max-width:640px){.user-chip .uname{max-width:80px}} |
| 158 |
|
− |
| 159 |
|
−/* ---------- hero ---------- */ |
| 160 |
|
−.hero{padding:56px 0 20px} |
| 161 |
|
−@media(max-width:640px){.hero{padding:34px 0 12px}} |
| 162 |
|
−.hero h1{font-size:clamp(34px,5.2vw,58px);font-weight:700;line-height:1.05;max-width:940px;margin-top:16px} |
| 163 |
|
−.hero p.lede{color:var(--ink-2);font-size:16.5px;max-width:660px;margin:18px 0 0} |
| 164 |
|
−.stat-row{display:flex;flex-wrap:wrap;gap:10px;margin-top:26px} |
| 165 |
|
−.stat-chip{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-ctl); |
| 166 |
|
− padding:8px 14px;font-family:var(--font-mono);font-size:11.5px;color:var(--ink-2); |
| 167 |
|
− display:flex;gap:8px;align-items:center;box-shadow:3px 3px 0 rgba(20,24,20,.12)} |
| 168 |
|
−.stat-chip b{color:var(--ink);font-weight:700} |
| 169 |
|
−.stat-chip .pulse{width:8px;height:8px;border-radius:50%;background:var(--green); |
| 170 |
|
− box-shadow:0 0 0 4px var(--lime-soft);animation:pulse 2.4s ease infinite} |
| 171 |
|
−@keyframes pulse{50%{box-shadow:0 0 0 7px rgba(28,92,65,.16)}} |
| 172 |
|
− |
| 173 |
|
−/* ---------- filtres ---------- */ |
| 174 |
|
−.filterbar{padding:14px;margin:28px 0 6px} |
| 175 |
|
−.f-primary{display:flex;gap:10px;align-items:stretch;flex-wrap:wrap} |
| 176 |
|
−.f-search{flex:1 1 240px;min-width:0;max-width:460px;display:flex;align-items:center;gap:8px; |
| 177 |
|
− border:1.5px solid var(--ink);border-radius:var(--r-ctl);background:var(--surface-2); |
| 178 |
|
− padding:0 14px;min-height:46px;transition:background .15s ease,box-shadow .15s ease} |
| 179 |
|
−.f-search:focus-within{background:var(--surface);box-shadow:3px 3px 0 var(--lime)} |
| 180 |
|
−.f-search input{border:none;background:none;outline:none;flex:1;min-width:0; |
| 181 |
|
− font-size:14px;color:var(--ink);font-family:inherit} |
| 182 |
|
−.f-ctl{display:flex;flex-direction:column;justify-content:center;gap:2px; |
| 183 |
|
− border:1.5px solid var(--ink);border-radius:var(--r-ctl);background:var(--surface); |
| 184 |
|
− padding:6px 14px 5px;min-height:46px;cursor:pointer;transition:box-shadow .15s ease} |
| 185 |
|
−.f-ctl:focus-within{box-shadow:3px 3px 0 var(--lime)} |
| 186 |
|
−.f-ctl select{border:none;background:none;outline:none;font-family:var(--font-display); |
| 187 |
|
− font-size:13.5px;font-weight:700;color:var(--ink);padding:0;margin-left:-2px;max-width:220px} |
| 188 |
|
−/* champs >=16px sur mobile : évite le zoom automatique d'iOS au focus */ |
|
163 |
+/* ---- header mobile (KA Nav v2) — déclaré après TOUTES les règles de base ---- */ |
| 189 |
164 |
@media(max-width:760px){ |
| 190 |
|
− .f-search input{font-size:16px} |
| 191 |
|
− .f-ctl select{font-size:16px} |
|
165 |
+ .nav{display:none} |
|
166 |
+ #auth{margin-left:auto} |
|
167 |
+ .ka-mnav-btn{display:flex} |
|
168 |
+ .ka-signup{display:none} |
|
169 |
+ .btn-ka .tx{display:none} |
|
170 |
+ .btn-ka{padding:8px 11px;min-height:36px} |
|
171 |
+ .user-chip .kaid{display:none} |
|
172 |
+ .user-chip .out{display:none} |
|
173 |
+} |
|
174 |
+ |
|
175 |
+/* ---------- hero éditorial (mosaïque d'images réelles en fond) ---------- */ |
|
176 |
+.hero{position:relative;padding:clamp(30px,6vw,72px) 0 8px;overflow:clip} |
|
177 |
+@media(max-width:640px){ |
|
178 |
+ .hero h1{font-size:clamp(30px,8.6vw,38px)} |
|
179 |
+ .hero-cats{font-size:10px;line-height:1.9;margin-top:12px} |
|
180 |
+ .hero-stats{font-size:13px;margin-top:10px;line-height:1.7} |
| 192 |
181 |
} |
|
182 |
+.hero-mosaic{position:absolute;inset:-24px -30px auto;height:130%;z-index:0; |
|
183 |
+ display:grid;grid-template-columns:repeat(6,1fr);grid-auto-rows:1fr;gap:8px; |
|
184 |
+ transform:rotate(-2deg);opacity:.3;pointer-events:none;filter:saturate(.85)} |
|
185 |
+@media(max-width:640px){.hero-mosaic{grid-template-columns:repeat(3,1fr)}} |
|
186 |
+.hero-mosaic .tile{border-radius:12px;overflow:hidden;background:var(--accent-soft)} |
|
187 |
+.hero-mosaic img{width:100%;height:100%;object-fit:cover} |
|
188 |
+.hero::after{content:"";position:absolute;inset:0;z-index:1; |
|
189 |
+ background:linear-gradient(180deg,rgba(245,243,238,.55) 0%,rgba(245,243,238,.86) 46%,var(--paper) 96%)} |
|
190 |
+.hero .container{position:relative;z-index:2} |
|
191 |
+.hero h1{font-size:clamp(34px,5.4vw,60px);font-weight:700;line-height:1.04;max-width:940px;margin-top:14px} |
|
192 |
+.hero-cats{font-family:var(--font-mono);font-size:11.5px;font-weight:500;text-transform:uppercase; |
|
193 |
+ letter-spacing:.13em;color:var(--ink-2);margin:16px 0 0;line-height:2} |
|
194 |
+.hero-cats i{font-style:normal;color:var(--accent);font-weight:700;padding:0 3px} |
|
195 |
+.hero-stats{font-size:14.5px;color:var(--ink-2);margin:14px 0 0;max-width:720px;line-height:1.8} |
|
196 |
+.hero-stats b{font-family:var(--font-display);font-size:1.35em;font-weight:700; |
|
197 |
+ letter-spacing:-.02em;color:var(--ink);padding-right:1px} |
|
198 |
+.hero-stats i{font-style:normal;color:var(--accent);font-weight:700;padding:0 4px} |
|
199 |
+ |
|
200 |
+/* ---------- recherche au centre + accès rapides + timeline de jours ---------- */ |
|
201 |
+.searchzone{margin-top:14px} |
|
202 |
+.searchbar{display:flex;align-items:center;gap:11px;background:var(--surface); |
|
203 |
+ border:1.5px solid var(--ink);border-radius:16px;min-height:56px;padding:0 8px 0 17px; |
|
204 |
+ box-shadow:var(--shadow-off-soft);transition:box-shadow .18s ease,transform .18s ease} |
|
205 |
+.searchbar:focus-within{box-shadow:4px 4px 0 var(--accent);transform:translate(-1px,-1px)} |
|
206 |
+.searchbar .sico{flex:none;width:19px;height:19px;color:var(--ink-3)} |
|
207 |
+.searchbar input{flex:1;min-width:0;border:0;background:none;outline:none; |
|
208 |
+ font-size:16px;font-family:inherit;color:var(--ink);padding:14px 0} |
|
209 |
+.searchbar input::placeholder{color:var(--ink-3)} |
|
210 |
+.searchbar input::-webkit-search-cancel-button{display:none} |
|
211 |
+.sclear{flex:none;width:38px;height:38px;border-radius:50%;border:0;background:var(--surface-2); |
|
212 |
+ color:var(--ink-2);font-size:13px;cursor:pointer;line-height:1} |
|
213 |
+.sclear:hover{background:var(--accent-soft);color:var(--ink)} |
|
214 |
+.quick{display:flex;gap:8px;margin-top:12px;overflow-x:auto;scrollbar-width:none;padding:2px} |
|
215 |
+.quick::-webkit-scrollbar{display:none} |
|
216 |
+@media(max-width:640px){.quick{margin-left:-16px;margin-right:-16px;padding:2px 16px}} |
|
217 |
+.qchip{flex:none;display:inline-flex;align-items:center;gap:7px;min-height:42px;padding:9px 16px; |
|
218 |
+ border:1.5px solid rgba(20,24,20,.35);border-radius:999px;background:var(--surface); |
|
219 |
+ font-family:var(--font-display);font-weight:700;font-size:13.5px;color:var(--ink); |
|
220 |
+ cursor:pointer;transition:all .14s ease;white-space:nowrap} |
|
221 |
+.qchip svg{width:15px;height:15px;flex:none} |
|
222 |
+.qchip:hover{border-color:var(--ink);background:var(--accent-soft)} |
|
223 |
+.qchip.on{background:var(--accent);border-color:var(--accent-deep);color:#fff; |
|
224 |
+ box-shadow:2px 2px 0 rgba(20,24,20,.22)} |
|
225 |
+.qchip .fb{min-width:19px;height:19px;border-radius:10px;background:var(--accent);color:#fff; |
|
226 |
+ font-family:var(--font-mono);font-size:10.5px;font-weight:700; |
|
227 |
+ display:inline-flex;align-items:center;justify-content:center;padding:0 5px} |
|
228 |
+.qchip.on .fb{background:#fff;color:var(--accent-deep)} |
|
229 |
+.days{display:flex;gap:8px;overflow-x:auto;margin-top:14px;padding:3px 2px 10px; |
|
230 |
+ scroll-snap-type:x proximity;scrollbar-width:none} |
|
231 |
+.days::-webkit-scrollbar{display:none} |
|
232 |
+@media(max-width:640px){.days{margin-left:-16px;margin-right:-16px;padding:3px 16px 10px}} |
|
233 |
+.day{flex:none;scroll-snap-align:start;min-width:60px;padding:8px 6px 7px; |
|
234 |
+ border:1.5px solid var(--line);border-radius:13px;background:var(--surface); |
|
235 |
+ text-align:center;cursor:pointer;transition:all .14s ease} |
|
236 |
+.day small{display:block;font-family:var(--font-mono);font-size:9px;font-weight:700; |
|
237 |
+ text-transform:uppercase;letter-spacing:.08em;color:var(--ink-3)} |
|
238 |
+.day b{display:block;font-family:var(--font-display);font-size:19px;line-height:1.2;color:var(--ink)} |
|
239 |
+.day i{display:block;font-style:normal;font-family:var(--font-mono);font-size:8.5px; |
|
240 |
+ text-transform:uppercase;letter-spacing:.05em;color:var(--ink-3)} |
|
241 |
+.day:hover{border-color:var(--ink)} |
|
242 |
+.day.on{background:var(--accent);border-color:var(--accent-deep); |
|
243 |
+ box-shadow:2px 2px 0 rgba(20,24,20,.22)} |
|
244 |
+.day.on small,.day.on i{color:rgba(255,255,255,.85)} |
|
245 |
+.day.on b{color:#fff} |
|
246 |
+.day.all b{font-size:13px;padding:6px 4px 5px;letter-spacing:0} |
|
247 |
+ |
|
248 |
+/* chips génériques (filtres du sheet + périodes de /stats) */ |
| 193 |
249 |
.chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px} |
| 194 |
|
−.chip{border:1.5px solid var(--ink);background:var(--surface);border-radius:var(--r-ctl); |
| 195 |
|
− padding:6px 12px;font-family:var(--font-display);font-size:12.5px;font-weight:700; |
| 196 |
|
− text-transform:none;letter-spacing:0;min-height:34px; |
|
250 |
+.chip{border:1.5px solid rgba(20,24,20,.4);background:var(--surface);border-radius:999px; |
|
251 |
+ padding:8px 14px;font-family:var(--font-display);font-size:12.5px;font-weight:700; |
|
252 |
+ text-transform:none;letter-spacing:0;min-height:38px;display:inline-flex;align-items:center;gap:6px; |
| 197 |
253 |
cursor:pointer;color:var(--ink);line-height:1;transition:all .12s ease} |
| 198 |
|
−.chip:hover{background:var(--accent-soft)} |
| 199 |
|
−.chip.on{background:var(--ink);color:var(--accent);box-shadow:3px 3px 0 rgba(20,24,20,.25)} |
|
254 |
+.chip:hover{background:var(--accent-soft);border-color:var(--ink)} |
|
255 |
+.chip.on{background:var(--ink);border-color:var(--ink);color:var(--accent);box-shadow:2px 2px 0 rgba(20,24,20,.25)} |
| 200 |
256 |
.chip.free.on{background:var(--green);border-color:var(--green);color:#fff} |
| 201 |
257 |
|
| 202 |
|
−/* ---------- résultats ---------- */ |
| 203 |
|
−.meta-line{display:flex;align-items:center;gap:10px;margin:20px 2px 10px; |
|
258 |
+/* ---------- sections éditoriales de l'accueil ---------- */ |
|
259 |
+.sec{margin:36px 0 6px} |
|
260 |
+.sec:first-child{margin-top:26px} |
|
261 |
+.sec-head{display:flex;align-items:flex-end;justify-content:space-between;gap:14px;margin:0 0 15px} |
|
262 |
+.sec-head .sk{display:block;font-family:var(--font-mono);font-size:10px;font-weight:700; |
|
263 |
+ text-transform:uppercase;letter-spacing:.12em;color:var(--accent-deep);margin-bottom:5px} |
|
264 |
+.sec-head h2{font-size:clamp(21px,2.7vw,31px);letter-spacing:-.025em;line-height:1.1;margin:0} |
|
265 |
+.sec-all{flex:none;font-family:var(--font-mono);font-size:11px;font-weight:700;text-transform:uppercase; |
|
266 |
+ letter-spacing:.06em;color:var(--accent-deep);cursor:pointer;background:none;border:0;padding:0; |
|
267 |
+ min-height:44px;display:inline-flex;align-items:center;gap:5px} |
|
268 |
+.sec-all:hover{text-decoration:underline;text-underline-offset:4px} |
|
269 |
+ |
|
270 |
+/* ---------- fiches événement v2 — l'image domine ---------- */ |
|
271 |
+.ev{display:flex;flex-direction:column;gap:10px;cursor:pointer;min-width:0} |
|
272 |
+.ev-img{position:relative;aspect-ratio:4/3;border-radius:15px;overflow:hidden; |
|
273 |
+ background:linear-gradient(135deg,var(--accent-soft) 0%,var(--surface-2) 70%)} |
|
274 |
+.ev-img::after{content:"";position:absolute;inset:0;border-radius:inherit; |
|
275 |
+ border:1px solid rgba(20,24,20,.16);pointer-events:none} |
|
276 |
+.ev-img img{width:100%;height:100%;object-fit:cover;transition:transform .4s ease} |
|
277 |
+.ev-img.noimg::before{content:"Sorti·Ka";position:absolute;inset:auto 10px 8px auto; |
|
278 |
+ font-family:var(--font-display);font-weight:700;font-size:13px;color:rgba(161,37,81,.4); |
|
279 |
+ transform:rotate(-2deg)} |
|
280 |
+@media(hover:hover){ |
|
281 |
+ .ev:hover .ev-img img{transform:scale(1.05)} |
|
282 |
+ .ev:hover h3{text-decoration:underline;text-decoration-color:var(--accent); |
|
283 |
+ text-decoration-thickness:2px;text-underline-offset:3px} |
|
284 |
+ .feat:hover img{transform:scale(1.03)} |
|
285 |
+} |
|
286 |
+.ev-body{display:flex;flex-direction:column;gap:4px;min-width:0} |
|
287 |
+.ev-date{font-family:var(--font-mono);font-size:11px;font-weight:700;text-transform:uppercase; |
|
288 |
+ letter-spacing:.05em;color:var(--accent-deep)} |
|
289 |
+.ev h3{font-family:var(--font-display);font-size:16.5px;font-weight:700;line-height:1.22; |
|
290 |
+ letter-spacing:-.015em;margin:0;display:-webkit-box;-webkit-line-clamp:2; |
|
291 |
+ -webkit-box-orient:vertical;overflow:hidden} |
|
292 |
+.ev-where{font-size:12.5px;color:var(--ink-2);margin:0; |
|
293 |
+ white-space:nowrap;overflow:hidden;text-overflow:ellipsis} |
|
294 |
+.ev-meta{display:flex;align-items:center;gap:10px;margin:2px 0 0;font-family:var(--font-mono); |
|
295 |
+ font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em; |
|
296 |
+ color:var(--ink-3);white-space:nowrap;overflow:hidden} |
|
297 |
+.ev-meta .src{color:rgba(139,146,140,.75);font-weight:500;overflow:hidden;text-overflow:ellipsis} |
|
298 |
+.ev-tag{position:absolute;left:10px;bottom:10px;z-index:2;font-family:var(--font-mono); |
|
299 |
+ font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.03em; |
|
300 |
+ background:rgba(20,24,20,.82);color:#fff;border-radius:9px;padding:5px 10px; |
|
301 |
+ backdrop-filter:blur(3px)} |
|
302 |
+.ev-tag.free{background:var(--green)} |
|
303 |
+.ev-tag.stat-cancelled{background:#b3261e} |
|
304 |
+.ev-tag.stat-soldout{background:#5f6368} |
|
305 |
+.ev-tag.stat-postponed{background:#a05a00} |
|
306 |
+ |
|
307 |
+/* grand événement vedette (À la une) */ |
|
308 |
+.feat{position:relative;display:block;border-radius:20px;overflow:hidden; |
|
309 |
+ aspect-ratio:16/8.5;min-height:300px;cursor:pointer;background:var(--ink); |
|
310 |
+ box-shadow:var(--shadow-off-soft);margin-bottom:16px} |
|
311 |
+@media(max-width:640px){.feat{aspect-ratio:4/4.6;min-height:0;border-radius:17px}} |
|
312 |
+.feat img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover; |
|
313 |
+ transition:transform .5s ease} |
|
314 |
+.feat::before{content:"";position:absolute;inset:0;z-index:1; |
|
315 |
+ background:linear-gradient(198deg,rgba(20,24,20,0) 34%,rgba(20,24,20,.55) 62%,rgba(20,24,20,.88) 96%)} |
|
316 |
+.feat-kick{position:absolute;top:14px;left:14px;z-index:2;font-family:var(--font-mono); |
|
317 |
+ font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.1em; |
|
318 |
+ background:var(--paper);color:var(--accent-deep);border-radius:999px;padding:6px 12px} |
|
319 |
+.feat .fav-btn{top:12px;right:12px} |
|
320 |
+.feat-body{position:absolute;inset:auto 0 0 0;z-index:2;padding:20px 22px 22px;color:#fff} |
|
321 |
+.feat-date{display:inline-flex;background:var(--accent);color:#fff;border-radius:9px; |
|
322 |
+ padding:6px 11px;font-family:var(--font-mono);font-size:11px;font-weight:700; |
|
323 |
+ text-transform:uppercase;letter-spacing:.05em;margin-bottom:11px} |
|
324 |
+.feat h3{color:#fff;font-size:clamp(22px,3.6vw,36px);line-height:1.07;letter-spacing:-.02em; |
|
325 |
+ margin:0 0 7px;max-width:760px;display:-webkit-box;-webkit-line-clamp:3; |
|
326 |
+ -webkit-box-orient:vertical;overflow:hidden} |
|
327 |
+.feat-where{font-size:13.5px;color:rgba(255,255,255,.85);margin:0; |
|
328 |
+ white-space:nowrap;overflow:hidden;text-overflow:ellipsis} |
|
329 |
+.feat-where b{color:#fff;font-weight:600} |
|
330 |
+ |
|
331 |
+/* carrousel horizontal (scroll-snap, débord plein écran sur mobile) */ |
|
332 |
+.hscroll{display:grid;grid-auto-flow:column;grid-auto-columns:min(44vw,238px);gap:15px; |
|
333 |
+ overflow-x:auto;scroll-snap-type:x mandatory;padding:4px 2px 12px;scrollbar-width:none} |
|
334 |
+.hscroll::-webkit-scrollbar{display:none} |
|
335 |
+.hscroll .ev{scroll-snap-align:start} |
|
336 |
+@media(max-width:640px){ |
|
337 |
+ .hscroll{grid-auto-columns:60vw;margin-left:-16px;margin-right:-16px;padding:4px 16px 12px} |
|
338 |
+} |
|
339 |
+ |
|
340 |
+/* liste compacte (À découvrir) */ |
|
341 |
+.compact{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:2px 26px} |
|
342 |
+.evc{display:flex;gap:13px;align-items:center;padding:11px 0;min-width:0;cursor:pointer; |
|
343 |
+ border-bottom:1px dashed var(--line)} |
|
344 |
+.evc-img{position:relative;flex:none;width:76px;height:76px;border-radius:13px;overflow:hidden; |
|
345 |
+ background:linear-gradient(135deg,var(--accent-soft),var(--surface-2))} |
|
346 |
+.evc-img::after{content:"";position:absolute;inset:0;border-radius:inherit; |
|
347 |
+ border:1px solid rgba(20,24,20,.14)} |
|
348 |
+.evc-img img{width:100%;height:100%;object-fit:cover} |
|
349 |
+.evc-body{min-width:0;flex:1} |
|
350 |
+.evc .ev-date{font-size:10px} |
|
351 |
+.evc h3{font-family:var(--font-display);font-size:14.5px;font-weight:700;line-height:1.25; |
|
352 |
+ letter-spacing:-.01em;margin:1px 0 2px;display:-webkit-box;-webkit-line-clamp:2; |
|
353 |
+ -webkit-box-orient:vertical;overflow:hidden} |
|
354 |
+.evc .ev-where{font-size:12px} |
|
355 |
+ |
|
356 |
+/* ---------- résultats : compteur, pilules actives, Liste|Carte ---------- */ |
|
357 |
+.rtool{display:flex;align-items:center;justify-content:space-between;gap:12px; |
|
358 |
+ flex-wrap:wrap;margin:20px 2px 8px} |
|
359 |
+.meta-line{display:flex;align-items:center;gap:10px; |
| 204 |
360 |
font-family:var(--font-mono);font-size:11.5px;text-transform:uppercase; |
| 205 |
361 |
letter-spacing:.08em;color:var(--ink-3)} |
| 206 |
362 |
.meta-line b{color:var(--ink)} |
| 207 |
|
−.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:20px;margin:8px 0 34px} |
| 208 |
|
−.card{overflow:hidden;display:flex;flex-direction:column;cursor:pointer} |
| 209 |
|
−.card-img{position:relative;height:150px;border-bottom:1.5px solid var(--ink); |
| 210 |
|
− background:repeating-linear-gradient(-45deg,var(--lime-soft),var(--lime-soft) 12px,var(--surface-2) 12px,var(--surface-2) 24px);overflow:hidden} |
| 211 |
|
−.card-img img{width:100%;height:100%;object-fit:cover} |
| 212 |
|
−.badge-date{position:absolute;left:10px;bottom:10px;font-family:var(--font-mono); |
| 213 |
|
− font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em; |
| 214 |
|
− color:var(--on-accent);background:var(--accent);border:1.5px solid var(--ink); |
| 215 |
|
− border-radius:var(--r-ctl);padding:4px 9px;white-space:nowrap;box-shadow:2px 2px 0 var(--ink)} |
| 216 |
|
−.card-body{padding:15px 16px 14px;display:flex;flex-direction:column;gap:8px;flex:1} |
| 217 |
|
−.card-top{display:flex;justify-content:space-between;gap:8px;align-items:flex-start} |
|
363 |
+.afilters{display:flex;flex-wrap:wrap;gap:7px;margin:2px 2px 6px} |
|
364 |
+.apill{display:inline-flex;align-items:center;gap:8px;min-height:34px;padding:5px 7px 5px 13px; |
|
365 |
+ border:1.5px solid var(--accent-deep);border-radius:999px;background:var(--accent-soft); |
|
366 |
+ font-family:var(--font-display);font-weight:700;font-size:12px;color:var(--accent-deep); |
|
367 |
+ cursor:pointer;transition:all .12s ease} |
|
368 |
+.apill i{font-style:normal;width:20px;height:20px;border-radius:50%;background:var(--accent-deep); |
|
369 |
+ color:#fff;font-size:10px;display:inline-flex;align-items:center;justify-content:center} |
|
370 |
+.apill:hover{background:var(--accent);color:#fff} |
|
371 |
+.vtoggle{display:inline-flex;border:1.5px solid var(--ink);border-radius:999px; |
|
372 |
+ background:var(--surface);padding:3px;box-shadow:2px 2px 0 rgba(20,24,20,.12)} |
|
373 |
+.vtoggle button{border:0;background:none;min-height:36px;padding:6px 16px;border-radius:999px; |
|
374 |
+ font-family:var(--font-display);font-weight:700;font-size:12.5px;cursor:pointer; |
|
375 |
+ display:inline-flex;gap:7px;align-items:center;color:var(--ink-2);transition:all .14s ease} |
|
376 |
+.vtoggle button svg{width:13px;height:13px} |
|
377 |
+.vtoggle button.on{background:var(--ink);color:var(--paper)} |
|
378 |
+.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(236px,1fr));gap:26px 20px;margin:12px 0 34px} |
|
379 |
+@media(max-width:640px){.grid{grid-template-columns:1fr 1fr;gap:22px 12px} |
|
380 |
+ .grid .ev h3{font-size:14.5px}} |
|
381 |
+ |
|
382 |
+/* ---------- carte (Leaflet, chargé à la demande) ---------- */ |
|
383 |
+.mapwrap{position:relative;isolation:isolate;border:1.5px solid var(--ink);border-radius:17px; |
|
384 |
+ overflow:hidden;height:min(68dvh,640px);margin:12px 0 44px;background:var(--surface-2)} |
|
385 |
+@media(max-width:760px){.mapwrap{height:calc(var(--vh100, 100dvh) - 190px);margin:10px 0 30px}} |
|
386 |
+#map{position:absolute;inset:0;z-index:0} |
|
387 |
+.map-note{position:absolute;left:12px;bottom:12px;z-index:1200;font-family:var(--font-mono); |
|
388 |
+ font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em; |
|
389 |
+ background:rgba(255,255,255,.92);border:1px solid rgba(20,24,20,.3);color:var(--ink-2); |
|
390 |
+ border-radius:999px;padding:6px 12px;pointer-events:none;max-width:70%} |
|
391 |
+.map-load{position:absolute;inset:0;display:flex;align-items:center;justify-content:center; |
|
392 |
+ z-index:1100;font-family:var(--font-mono);font-size:11px;text-transform:uppercase; |
|
393 |
+ letter-spacing:.08em;color:var(--ink-3);background:var(--surface-2)} |
|
394 |
+.leaflet-popup-content-wrapper{border-radius:14px;border:1.5px solid var(--ink); |
|
395 |
+ box-shadow:4px 4px 0 rgba(20,24,20,.18);font-family:var(--font-body)} |
|
396 |
+.leaflet-popup-content{margin:0;width:220px!important} |
|
397 |
+.mpop{display:block;color:var(--ink);text-decoration:none} |
|
398 |
+.mpop img{width:100%;height:104px;object-fit:cover;border-radius:12px 12px 0 0} |
|
399 |
+.mpop .mp-b{padding:10px 13px 12px} |
|
400 |
+.mpop .ev-date{font-size:10px} |
|
401 |
+.mpop h4{font-family:var(--font-display);font-size:13.5px;font-weight:700;line-height:1.25; |
|
402 |
+ margin:3px 0 3px;letter-spacing:-.01em} |
|
403 |
+.mpop p{font-size:11.5px;color:var(--ink-2);margin:0;white-space:nowrap; |
|
404 |
+ overflow:hidden;text-overflow:ellipsis} |
|
405 |
+ |
|
406 |
+/* badges conservés (fiche événement + bannières de statut) */ |
| 218 |
407 |
.badge-free{font-family:var(--font-mono);font-size:10px;font-weight:700;text-transform:uppercase; |
| 219 |
|
− color:#fff;background:var(--green);border-radius:var(--r-ctl);padding:4px 8px;flex:none} |
|
408 |
+ color:#fff;background:var(--green);border-radius:7px;padding:4px 8px;flex:none} |
| 220 |
409 |
.badge-price{font-family:var(--font-mono);font-size:10px;font-weight:700;text-transform:uppercase; |
| 221 |
410 |
color:var(--ink);background:var(--amber-soft);border:1px solid var(--amber); |
| 222 |
|
− border-radius:var(--r-ctl);padding:3px 8px;flex:none} |
|
411 |
+ border-radius:7px;padding:3px 8px;flex:none} |
| 223 |
412 |
.badge-status{font-family:var(--font-mono);font-size:10px;font-weight:700;text-transform:uppercase; |
| 224 |
|
− color:#fff;background:#b3261e;border:1px solid var(--ink); |
| 225 |
|
− border-radius:var(--r-ctl);padding:4px 8px;flex:none} |
|
413 |
+ color:#fff;background:#b3261e;border-radius:7px;padding:4px 8px;flex:none} |
| 226 |
414 |
.badge-status--soldout{background:#5f6368} |
| 227 |
415 |
.badge-status--postponed{background:#a05a00} |
| 228 |
|
−.status-banner{margin:0 0 14px;padding:10px 14px;border:1.5px solid var(--ink); |
| 229 |
|
− border-radius:var(--r-ctl);font-weight:700;color:#fff;background:#b3261e; |
| 230 |
|
− box-shadow:2px 2px 0 var(--ink)} |
|
416 |
+.status-banner{margin:0 0 14px;padding:11px 15px;border-radius:12px;font-weight:700; |
|
417 |
+ color:#fff;background:#b3261e} |
| 231 |
418 |
.status-banner--soldout{background:#5f6368} |
| 232 |
419 |
.status-banner--postponed{background:#a05a00} |
| 233 |
|
−.card h3{font-size:17px;font-weight:700;line-height:1.25} |
| 234 |
|
−.card .desc{font-size:12.5px;color:var(--ink-2);display:-webkit-box; |
| 235 |
|
− -webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} |
| 236 |
|
−.card .where{font-size:12.5px;color:var(--ink-3)} |
| 237 |
|
−.card .where b{color:var(--ink-2);font-weight:600} |
| 238 |
|
−.card .cats{display:flex;flex-wrap:wrap;gap:6px;margin-top:auto;padding-top:4px} |
| 239 |
420 |
.cat{font-family:var(--font-mono);font-size:9.5px;font-weight:700;letter-spacing:.06em; |
| 240 |
421 |
text-transform:uppercase;color:var(--ink-2);border:1px solid var(--line-strong); |
| 241 |
|
− border-radius:var(--r-ctl);padding:2px 7px} |
| 242 |
|
−.card .src{font-family:var(--font-mono);font-size:10px;text-transform:uppercase; |
| 243 |
|
− letter-spacing:.04em;color:var(--ink-3);border-top:1px dashed var(--line-strong); |
| 244 |
|
− padding-top:10px;margin-top:6px;display:flex;justify-content:space-between} |
| 245 |
|
−.card .src .go{color:var(--green);font-weight:700} |
|
422 |
+ border-radius:999px;padding:3px 9px} |
| 246 |
423 |
.more{display:block;margin:0 auto 54px} |
|
424 |
+.more[hidden]{display:none} |
| 247 |
425 |
.empty{padding:60px 10px;text-align:center;color:var(--ink-3);grid-column:1/-1} |
| 248 |
426 |
.empty b{font-family:var(--font-display);font-size:19px;color:var(--ink);display:block;margin-bottom:6px} |
| 249 |
427 |
|
| 250 |
|
−/* ---------- fiche événement ---------- */ |
| 251 |
|
−.detail{padding:34px 0 50px} |
|
428 |
+/* ---------- fiche événement v2 ---------- */ |
|
429 |
+.detail{padding-top:24px;padding-bottom:60px} |
| 252 |
430 |
.back{display:inline-flex;align-items:center;gap:8px;min-height:44px;font-family:var(--font-mono); |
| 253 |
431 |
font-size:11.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em; |
| 254 |
|
− color:var(--green);cursor:pointer;margin-bottom:8px} |
|
432 |
+ color:var(--accent-deep);cursor:pointer;margin-bottom:6px} |
| 255 |
433 |
@media(pointer:coarse){.chip{min-height:44px}.user-chip .out{min-height:32px;padding:2px 8px}} |
| 256 |
434 |
.back:hover{text-decoration:underline} |
| 257 |
|
−.detail-grid{display:grid;grid-template-columns:1.5fr 1fr;gap:26px;align-items:start} |
| 258 |
|
−@media(max-width:860px){.detail-grid{grid-template-columns:1fr}} |
| 259 |
|
−.detail-hero{overflow:hidden} |
| 260 |
|
−.detail-hero .img{height:340px;border-bottom:1.5px solid var(--ink); |
| 261 |
|
− background:repeating-linear-gradient(-45deg,var(--lime-soft),var(--lime-soft) 14px,var(--surface-2) 14px,var(--surface-2) 28px)} |
| 262 |
|
−.detail-hero .img img{width:100%;height:100%;object-fit:cover} |
| 263 |
|
−.detail-hero .body{padding:24px 26px 26px} |
| 264 |
|
−.detail-hero h1{font-size:clamp(26px,3.4vw,40px);line-height:1.08;margin:10px 0 14px} |
| 265 |
|
−.detail-hero .desc{color:var(--ink-2);font-size:15px;white-space:pre-line} |
| 266 |
|
−.detail-cats{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px} |
| 267 |
|
−.side{display:flex;flex-direction:column;gap:16px} |
| 268 |
|
−.side .box{padding:18px 20px} |
| 269 |
|
−.fact{display:flex;justify-content:space-between;gap:14px;padding:9px 0;border-bottom:1px dashed var(--line-strong);font-size:13.5px} |
| 270 |
|
−.fact:last-child{border-bottom:none} |
|
435 |
+.detail-grid{display:grid;grid-template-columns:1.55fr 1fr;gap:30px;align-items:start} |
|
436 |
+@media(max-width:860px){.detail-grid{grid-template-columns:1fr;gap:20px}} |
|
437 |
+.detail-main{min-width:0} |
|
438 |
+.d-imgwrap{position:relative;aspect-ratio:16/9;border-radius:19px;overflow:hidden; |
|
439 |
+ background:linear-gradient(135deg,var(--accent-soft),var(--surface-2))} |
|
440 |
+.d-imgwrap::after{content:"";position:absolute;inset:0;border-radius:inherit; |
|
441 |
+ border:1px solid rgba(20,24,20,.18);pointer-events:none} |
|
442 |
+.d-imgwrap img{width:100%;height:100%;object-fit:cover} |
|
443 |
+@media(max-width:640px){.d-imgwrap{aspect-ratio:4/3;border-radius:15px;margin:0 0 2px}} |
|
444 |
+.d-body{padding:20px 2px 0} |
|
445 |
+.d-body h1{font-size:clamp(26px,3.6vw,42px);line-height:1.07;margin:10px 0 14px} |
|
446 |
+.d-body .desc{color:var(--ink-2);font-size:15px;white-space:pre-line;max-width:70ch} |
|
447 |
+.detail-cats{display:flex;flex-wrap:wrap;gap:8px;margin-top:18px} |
|
448 |
+.side{display:flex;flex-direction:column;gap:14px;position:sticky;top:76px} |
|
449 |
+@media(max-width:860px){.side{position:static}} |
|
450 |
+.side .box{background:var(--surface);border:1px solid rgba(20,24,20,.25);border-radius:16px; |
|
451 |
+ padding:18px 20px;box-shadow:var(--shadow-flat)} |
|
452 |
+.fact{display:flex;justify-content:space-between;gap:14px;padding:10px 0; |
|
453 |
+ border-bottom:1px dashed var(--line);font-size:13.5px} |
|
454 |
+.fact:first-child{padding-top:2px} |
|
455 |
+.fact:last-child{border-bottom:none;padding-bottom:2px} |
| 271 |
456 |
.fact .k{font-family:var(--font-mono);font-size:10px;font-weight:700; |
| 272 |
457 |
text-transform:uppercase;letter-spacing:.08em;color:var(--ink-3);padding-top:3px;flex:none} |
| 273 |
458 |
.fact .v{text-align:right;font-weight:600} |
| 274 |
459 |
.side .btn{width:100%} |
| 275 |
|
−.maplink{font-family:var(--font-mono);font-size:11px;color:var(--green);font-weight:700;text-transform:uppercase} |
|
460 |
+.btn-cta{background:var(--accent);color:#fff;border-color:var(--accent-deep); |
|
461 |
+ box-shadow:4px 4px 0 rgba(161,37,81,.3)} |
|
462 |
+.btn-cta:hover{background:var(--accent-deep);color:#fff} |
|
463 |
+.maplink{font-family:var(--font-mono);font-size:11px;color:var(--accent-deep);font-weight:700;text-transform:uppercase} |
| 276 |
464 |
.maplink:hover{text-decoration:underline} |
| 277 |
|
−.similar h2{font-size:22px;margin:44px 0 4px} |
|
465 |
+.similar h2{font-size:22px;margin:46px 0 4px} |
|
466 |
+/* barre d'action collée en bas — mobile seulement (billets toujours à un pouce) */ |
|
467 |
+.d-ctabar{display:none} |
|
468 |
+@media(max-width:860px){ |
|
469 |
+ .d-ctabar{position:fixed;left:0;right:0;bottom:0;z-index:var(--z-bottombar,600); |
|
470 |
+ display:flex;align-items:center;gap:14px;background:var(--surface); |
|
471 |
+ border-top:1.5px solid var(--ink);padding:10px 16px calc(10px + env(safe-area-inset-bottom))} |
|
472 |
+ .d-ctabar .p{flex:1;min-width:0} |
|
473 |
+ .d-ctabar .p b{font-family:var(--font-display);font-size:15.5px;letter-spacing:-.01em; |
|
474 |
+ display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} |
|
475 |
+ .d-ctabar .p span{display:block;font-family:var(--font-mono);font-size:9.5px; |
|
476 |
+ text-transform:uppercase;letter-spacing:.05em;color:var(--accent-deep);margin-top:1px} |
|
477 |
+ .d-ctabar .btn{flex:none;min-width:132px} |
|
478 |
+ .detail{padding-bottom:120px} |
|
479 |
+ /* le bouton ka-agent remonte au-dessus de la barre CTA sur la fiche mobile */ |
|
480 |
+ body:has(#view-detail:not(.hidden)) .kaa-btn{ |
|
481 |
+ bottom:calc(86px + env(safe-area-inset-bottom,0px))!important} |
|
482 |
+} |
|
483 |
+.d-imgwrap.noimg{aspect-ratio:16/5} |
| 278 |
484 |
|
| 279 |
485 |
/* ---------- sources / footer ---------- */ |
| 280 |
|
−.sources{background:var(--surface-2);border-top:1.5px solid var(--ink);padding:44px 0 38px} |
|
486 |
+.sources{background:var(--surface-2);border-top:1px solid rgba(20,24,20,.35);padding:46px 0 40px;margin-top:26px} |
| 281 |
487 |
.sources h2{font-size:26px;margin-top:12px} |
| 282 |
488 |
.sources p.lede2{color:var(--ink-2);font-size:14.5px;max-width:720px;margin:10px 0 0} |
| 283 |
|
−.src-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;margin-top:22px} |
| 284 |
|
−.src-card{padding:16px 18px} |
| 285 |
|
−.src-card h4{font-size:15px;margin-bottom:5px} |
|
489 |
+.src-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;margin-top:24px} |
|
490 |
+.src-card{background:var(--surface);border:1px solid rgba(20,24,20,.18);border-radius:14px; |
|
491 |
+ padding:15px 17px;box-shadow:var(--shadow-flat)} |
|
492 |
+.src-card h4{font-size:14.5px;margin-bottom:5px} |
| 286 |
493 |
.src-card .lic{font-family:var(--font-mono);font-size:9.5px;font-weight:700; |
| 287 |
494 |
color:var(--green);text-transform:uppercase;letter-spacing:.06em} |
| 288 |
495 |
.src-card .n{font-size:12.5px;color:var(--ink-3);margin-top:6px} |
| 289 |
|
−.src-card .n b{color:var(--green-deep)} |
|
496 |
+.src-card .n b{color:var(--accent-deep)} |
| 290 |
497 |
.ka-footer{margin-top:0} |
| 291 |
498 |
|
| 292 |
499 |
/* ---------- page /contact ---------- */ |
| 293 |
|
−.contact-page{padding:44px 0 64px} |
|
500 |
+.contact-page{padding-top:44px;padding-bottom:64px} |
| 294 |
501 |
.contact-page h1{font-size:clamp(30px,4.2vw,48px);line-height:1.06;margin-top:16px;max-width:820px} |
| 295 |
502 |
.contact-page .lede{color:var(--ink-2);font-size:16px;max-width:660px;margin:16px 0 0} |
| 296 |
503 |
.contact-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr)); |
| 314 |
521 |
.contact-sites a small{font-family:var(--font-mono);font-weight:500;font-size:10px; |
| 315 |
522 |
color:var(--ink-3);margin-left:8px} |
| 316 |
523 |
/* ---------- page /stats (module ka-stats, vanilla + SVG) ---------- */ |
| 317 |
|
−.stats-page{padding:40px 0 60px} |
| 318 |
|
−@media(max-width:640px){.stats-page{padding:28px 0 44px}} |
|
524 |
+.stats-page{padding-top:40px;padding-bottom:60px} |
|
525 |
+@media(max-width:640px){.stats-page{padding-top:28px;padding-bottom:44px}} |
| 319 |
526 |
.st-head{display:flex;flex-wrap:wrap;gap:16px;align-items:flex-end;justify-content:space-between} |
| 320 |
527 |
.st-head h1{font-size:clamp(28px,4vw,44px);line-height:1.06;margin-top:12px} |
| 321 |
528 |
.st-fresh{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin:10px 0 0} |
| 455 |
662 |
.d-title-row h1{min-width:0} |
| 456 |
663 |
.fav-empty{grid-column:1/-1} |
| 457 |
664 |
.fav-empty .btn-ka{margin-top:12px} |
|
665 |
+/* ---------- panneau de filtres : bottom sheet mobile / modale centrée desktop ---------- */ |
|
666 |
+.sheet-ov{position:fixed;inset:0;z-index:var(--z-overlay,800);background:rgba(20,24,20,.45); |
|
667 |
+ opacity:0;transition:opacity .22s ease;pointer-events:none} |
|
668 |
+.sheet-ov.open{opacity:1;pointer-events:auto} |
|
669 |
+.sheet{position:fixed;left:0;right:0;bottom:0;z-index:var(--z-modal,900);background:var(--paper); |
|
670 |
+ border:1.5px solid var(--ink);border-bottom:0;border-radius:22px 22px 0 0; |
|
671 |
+ max-height:88dvh;display:flex;flex-direction:column; |
|
672 |
+ transform:translateY(103%);transition:transform .3s cubic-bezier(.32,.72,.28,1); |
|
673 |
+ visibility:hidden} |
|
674 |
+.sheet.open{transform:none;visibility:visible} |
|
675 |
+.sheet-grab{width:44px;height:5px;border-radius:3px;background:rgba(20,24,20,.22); |
|
676 |
+ margin:10px auto 2px;flex:none} |
|
677 |
+.sheet-head{display:flex;justify-content:space-between;align-items:center; |
|
678 |
+ padding:8px 20px 12px;border-bottom:1px solid var(--line);flex:none} |
|
679 |
+.sheet-head h2{font-size:17px;margin:0} |
|
680 |
+.sheet-x{width:38px;height:38px;border-radius:50%;border:1.5px solid var(--ink); |
|
681 |
+ background:var(--surface);cursor:pointer;font-size:14px;line-height:1;padding:0; |
|
682 |
+ display:inline-flex;align-items:center;justify-content:center} |
|
683 |
+.sheet-x:hover{background:var(--accent-soft)} |
|
684 |
+.sheet-body{overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain; |
|
685 |
+ padding:18px 20px 12px} |
|
686 |
+.sheet-sec{margin:0 0 22px} |
|
687 |
+.sheet-sec>.klabel{display:block;margin-bottom:10px} |
|
688 |
+.sheet-chips{display:flex;flex-wrap:wrap;gap:8px} |
|
689 |
+.sheet .select{max-width:420px} |
|
690 |
+.sheet-dates{display:flex;gap:8px;align-items:center;margin-top:12px;flex-wrap:wrap} |
|
691 |
+.sheet-dates .input{width:158px;padding:9px 11px;font-size:14px;border-radius:11px} |
|
692 |
+.sheet-foot{display:flex;gap:10px;padding:12px 20px calc(14px + env(safe-area-inset-bottom)); |
|
693 |
+ border-top:1.5px solid var(--ink);flex:none;background:var(--surface);border-radius:0} |
|
694 |
+.sheet-foot .btn{flex:1;border-radius:12px} |
|
695 |
+@media(min-width:761px){ |
|
696 |
+ .sheet{left:50%;right:auto;bottom:auto;top:50%;width:min(620px,94vw);max-height:82vh; |
|
697 |
+ border-radius:20px;border-bottom:1.5px solid var(--ink); |
|
698 |
+ box-shadow:12px 12px 0 rgba(20,24,20,.16); |
|
699 |
+ transform:translate(-50%,-46%) scale(.97);opacity:0; |
|
700 |
+ transition:opacity .18s ease,transform .18s ease} |
|
701 |
+ .sheet.open{opacity:1;transform:translate(-50%,-50%) scale(1)} |
|
702 |
+ .sheet-grab{display:none} |
|
703 |
+ .sheet-foot{border-radius:0 0 18px 18px} |
|
704 |
+} |
|
705 |
+ |
|
706 |
+/* ---------- toast ---------- */ |
|
707 |
+.toast{position:fixed;left:50%;bottom:calc(22px + env(safe-area-inset-bottom)); |
|
708 |
+ transform:translateX(-50%) translateY(10px);z-index:var(--z-toast,950); |
|
709 |
+ background:var(--ink);color:var(--paper);border-radius:13px;padding:12px 19px; |
|
710 |
+ font-size:13.5px;font-weight:600;opacity:0;transition:all .25s ease; |
|
711 |
+ pointer-events:none;max-width:92vw;text-align:center;box-shadow:0 8px 24px rgba(20,24,20,.3)} |
|
712 |
+.toast.show{opacity:1;transform:translateX(-50%)} |
|
713 |
+ |
| 458 |
714 |
#ssr{display:none} |
| 459 |
715 |
.hidden{display:none!important} |
| 460 |
716 |
</style> |
| 480 |
736 |
Un service <b>Groupe<span class="ka">KA</span></b></a> |
| 481 |
737 |
<span class="brand-tag">Sorties & événements — Québec</span> |
| 482 |
738 |
<nav class="nav"> |
| 483 |
|
− <a href="/#sources">Sources</a> |
|
739 |
+ <a data-nav href="/">Sorties</a> |
| 484 |
740 |
<a data-nav href="/favoris" title="Mes favoris — Mon univers Ka">♥ Favoris</a> |
| 485 |
741 |
<a data-nav href="/stats">Stats</a> |
| 486 |
742 |
<a data-nav href="/contact">Contact</a> |
| 487 |
|
− <a href="/docs" target="_blank" rel="noopener">API</a> |
| 488 |
743 |
</nav> |
| 489 |
744 |
<span id="auth"></span> |
| 490 |
745 |
<button id="ka-mnav-btn" class="ka-mnav-btn" aria-label="Ouvrir le menu" |
| 520 |
775 |
</div> |
| 521 |
776 |
</div> |
| 522 |
777 |
|
| 523 |
|
−<!-- ======== VUE LISTE ======== --> |
|
778 |
+<!-- ======== VUE LISTE : accueil éditorial + résultats Liste|Carte ======== --> |
| 524 |
779 |
<main id="view-list"> |
| 525 |
|
− <section class="hero container"> |
| 526 |
|
− <span class="kicker">Agrégateur indépendant · mise à jour horaire</span> |
| 527 |
|
− <h1>Toutes les sorties du Québec, <span class="hl">un seul endroit.</span></h1> |
| 528 |
|
− <p class="lede">Concerts, festivals, spectacles, expositions, sport, famille — agrégés |
| 529 |
|
− depuis des billetteries et des sources ouvertes officielles, dans les 17 régions. |
| 530 |
|
− Chaque fiche renvoie vers la billetterie ou la source originale.</p> |
| 531 |
|
− <div class="stat-row" id="stats"></div> |
|
780 |
+ <section class="hero"> |
|
781 |
+ <div class="hero-mosaic" id="hero-mosaic" aria-hidden="true"></div> |
|
782 |
+ <div class="container"> |
|
783 |
+ <span class="kicker">Agrégateur indépendant · mise à jour continue</span> |
|
784 |
+ <h1>Toutes les sorties du Québec, <span class="hl">un seul endroit.</span></h1> |
|
785 |
+ <p class="hero-cats">concerts <i>·</i> festivals <i>·</i> humour <i>·</i> théâtre |
|
786 |
+ <i>·</i> expos <i>·</i> famille <i>·</i> sport</p> |
|
787 |
+ <p class="hero-stats" id="stats"></p> |
|
788 |
+ </div> |
| 532 |
789 |
</section> |
| 533 |
790 |
|
| 534 |
|
− <section class="container"> |
| 535 |
|
− <div class="filterbar gk-card"> |
| 536 |
|
− <div class="f-primary"> |
| 537 |
|
− <label class="f-search">🔎<input type="text" id="q" placeholder="Artiste, événement, salle, ville…"></label> |
| 538 |
|
− <label class="f-ctl"><span class="klabel">Région</span> |
| 539 |
|
− <select id="region"><option value="">Toutes les régions</option></select> |
| 540 |
|
− </label> |
| 541 |
|
− <label class="f-ctl"><span class="klabel">Quand</span> |
| 542 |
|
− <select id="period"> |
| 543 |
|
− <option value="">À venir (tout)</option> |
| 544 |
|
− <option value="today">Aujourd'hui</option> |
| 545 |
|
− <option value="weekend">Ce week-end</option> |
| 546 |
|
− <option value="7">7 prochains jours</option> |
| 547 |
|
− <option value="30">30 prochains jours</option> |
| 548 |
|
− </select> |
| 549 |
|
− </label> |
| 550 |
|
− </div> |
| 551 |
|
− <div class="chips" id="cats"></div> |
|
791 |
+ <section class="container searchzone"> |
|
792 |
+ <div class="searchbar" role="search"> |
|
793 |
+ <svg class="sico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" |
|
794 |
+ stroke-linecap="round" aria-hidden="true"> |
|
795 |
+ <circle cx="11" cy="11" r="7"/><path d="m20 20-3.8-3.8"/></svg> |
|
796 |
+ <input type="search" id="q" placeholder="Artiste, événement, salle ou ville…" |
|
797 |
+ autocomplete="off" aria-label="Rechercher une sortie"> |
|
798 |
+ <button class="sclear hidden" id="q-clear" aria-label="Effacer la recherche">✕</button> |
| 552 |
799 |
</div> |
|
800 |
+ <div class="quick" id="quick"></div> |
|
801 |
+ <div class="days" id="days" aria-label="Naviguer par jour"></div> |
|
802 |
+ </section> |
| 553 |
803 |
|
| 554 |
|
− <div class="meta-line"><span id="count">Chargement…</span></div> |
|
804 |
+ <!-- accueil éditorial : sections composées par renderHome() --> |
|
805 |
+ <section class="container" id="home"></section> |
|
806 |
+ |
|
807 |
+ <!-- résultats de recherche / filtres : liste ou carte --> |
|
808 |
+ <section class="container hidden" id="results"> |
|
809 |
+ <div class="rtool"> |
|
810 |
+ <div class="meta-line"><span id="count">Chargement…</span></div> |
|
811 |
+ <div class="vtoggle" role="group" aria-label="Affichage"> |
|
812 |
+ <button type="button" id="v-list" class="on" aria-pressed="true"> |
|
813 |
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" |
|
814 |
+ stroke-linecap="round" aria-hidden="true"> |
|
815 |
+ <path d="M4 6h16M4 12h16M4 18h16"/></svg>Liste</button> |
|
816 |
+ <button type="button" id="v-map" aria-pressed="false"> |
|
817 |
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" |
|
818 |
+ stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> |
|
819 |
+ <path d="M12 21s-7-5.5-7-11a7 7 0 0 1 14 0c0 5.5-7 11-7 11z"/> |
|
820 |
+ <circle cx="12" cy="10" r="2.6"/></svg>Carte</button> |
|
821 |
+ </div> |
|
822 |
+ </div> |
|
823 |
+ <div class="afilters" id="afilters"></div> |
| 555 |
824 |
<section class="grid" id="grid"></section> |
| 556 |
825 |
<button class="more btn btn-primary" id="more" hidden>Afficher plus d'événements</button> |
|
826 |
+ <div class="mapwrap hidden" id="mapwrap"> |
|
827 |
+ <div id="map"></div> |
|
828 |
+ <div class="map-load" id="map-load">Chargement de la carte…</div> |
|
829 |
+ <span class="map-note hidden" id="map-note"></span> |
|
830 |
+ </div> |
| 557 |
831 |
</section> |
| 558 |
832 |
|
| 559 |
833 |
<section class="sources" id="sources"> |
| 568 |
842 |
</section> |
| 569 |
843 |
</main> |
| 570 |
844 |
|
|
845 |
+<!-- ======== PANNEAU DE FILTRES (bottom sheet mobile / modale desktop) ======== --> |
|
846 |
+<div class="sheet-ov" id="sheet-ov"></div> |
|
847 |
+<div class="sheet" id="sheet" role="dialog" aria-modal="true" aria-label="Filtres"> |
|
848 |
+ <div class="sheet-grab" aria-hidden="true"></div> |
|
849 |
+ <div class="sheet-head"> |
|
850 |
+ <h2>Filtres</h2> |
|
851 |
+ <button type="button" class="sheet-x" id="sheet-x" aria-label="Fermer les filtres">✕</button> |
|
852 |
+ </div> |
|
853 |
+ <div class="sheet-body"> |
|
854 |
+ <div class="sheet-sec"> |
|
855 |
+ <span class="klabel">Quand</span> |
|
856 |
+ <div class="sheet-chips" id="sh-when"></div> |
|
857 |
+ <div class="sheet-dates"> |
|
858 |
+ <input class="input" type="date" id="sh-from" aria-label="Du"> |
|
859 |
+ <span class="klabel">au</span> |
|
860 |
+ <input class="input" type="date" id="sh-to" aria-label="Au"> |
|
861 |
+ </div> |
|
862 |
+ </div> |
|
863 |
+ <div class="sheet-sec"> |
|
864 |
+ <span class="klabel">Région</span> |
|
865 |
+ <select class="select" id="region"><option value="">Toutes les régions</option></select> |
|
866 |
+ </div> |
|
867 |
+ <div class="sheet-sec"> |
|
868 |
+ <span class="klabel">Catégories</span> |
|
869 |
+ <div class="sheet-chips" id="cats"></div> |
|
870 |
+ </div> |
|
871 |
+ <div class="sheet-sec"> |
|
872 |
+ <span class="klabel">Prix</span> |
|
873 |
+ <div class="sheet-chips"> |
|
874 |
+ <button type="button" class="chip free" id="chip-free">✦ Gratuit seulement</button> |
|
875 |
+ </div> |
|
876 |
+ </div> |
|
877 |
+ </div> |
|
878 |
+ <div class="sheet-foot"> |
|
879 |
+ <button type="button" class="btn btn-ghost" id="sh-reset">Réinitialiser</button> |
|
880 |
+ <button type="button" class="btn btn-cta" id="sh-apply">Voir les sorties</button> |
|
881 |
+ </div> |
|
882 |
+</div> |
|
883 |
+ |
|
884 |
+<div class="toast" id="toast" role="status" aria-live="polite"></div> |
|
885 |
+ |
| 571 |
886 |
<!-- ======== VUE FICHE ÉVÉNEMENT ======== --> |
| 572 |
887 |
<main id="view-detail" class="hidden"> |
| 573 |
888 |
<div class="container detail"> |
| 574 |
889 |
<span class="back" id="back">← Toutes les sorties</span> |
| 575 |
890 |
<div class="detail-grid"> |
| 576 |
|
− <article class="gk-card detail-hero"> |
| 577 |
|
− <div class="img" id="d-img"></div> |
| 578 |
|
− <div class="body"> |
|
891 |
+ <article class="detail-main"> |
|
892 |
+ <div class="d-imgwrap" id="d-img"></div> |
|
893 |
+ <div class="d-body"> |
| 579 |
894 |
<span class="klabel" id="d-cats-label"></span> |
| 580 |
895 |
<div id="d-status"></div> |
| 581 |
896 |
<div class="d-title-row"><h1 id="d-title"></h1><span id="d-fav"></span></div> |
| 584 |
899 |
</div> |
| 585 |
900 |
</article> |
| 586 |
901 |
<aside class="side"> |
| 587 |
|
− <div class="gk-card box" id="d-facts"></div> |
| 588 |
|
− <a class="btn btn-primary" id="d-cta" target="_blank" rel="noopener">Billets · fiche originale ↗</a> |
| 589 |
|
− <div class="gk-card box" id="d-src"></div> |
|
902 |
+ <div class="box" id="d-facts"></div> |
|
903 |
+ <a class="btn btn-cta" id="d-cta" target="_blank" rel="noopener">Billets · fiche originale ↗</a> |
|
904 |
+ <div class="box" id="d-src"></div> |
| 590 |
905 |
</aside> |
| 591 |
906 |
</div> |
| 592 |
907 |
<section class="similar"> |
| 595 |
910 |
<div class="grid" id="d-similar"></div> |
| 596 |
911 |
</section> |
| 597 |
912 |
</div> |
|
913 |
+ <!-- CTA sticky mobile : le billet toujours à portée de pouce --> |
|
914 |
+ <div class="d-ctabar" id="d-ctabar"></div> |
| 598 |
915 |
</main> |
| 599 |
916 |
|
| 600 |
917 |
<!-- ======== VUE FAVORIS (/favoris) — ♥ « Mon univers Ka » (hub groupe-ka.com) ======== --> |
| 762 |
1079 |
/* Sorti-Ka — frontend + routeur. Auteur : Simon-Pierre Boucher <contact@spboucher.ai> */ |
| 763 |
1080 |
"use strict"; |
| 764 |
1081 |
const $ = id => document.getElementById(id); |
| 765 |
|
−const state = { q:"", region:"", category:"", free:false, from:"", to:"", offset:0, limit:30, total:0 }; |
|
1082 |
+const state = { q:"", region:"", category:"", free:false, from:"", to:"", near:"", |
|
1083 |
+ view:"list", offset:0, limit:30, total:0 }; |
| 766 |
1084 |
|
| 767 |
1085 |
const CAT_LABELS = { |
| 768 |
1086 |
"festival":"Festivals","musique":"Musique","arts-scene":"Arts de la scène", |
| 814 |
1132 |
if(!ev.status || !STATUS_LABELS[ev.status]) return ""; |
| 815 |
1133 |
return `<span class="badge-status badge-status--${esc(ev.status)}">${STATUS_LABELS[ev.status]}</span>`; |
| 816 |
1134 |
} |
| 817 |
|
−function priceBadge(ev){ |
| 818 |
|
− if(ev.is_free === true) return `<span class="badge-free">Gratuit</span>`; |
| 819 |
|
− if(ev.price_min != null) return `<span class="badge-price">Dès ${ev.price_min.toFixed(2).replace(".",",")} $</span>`; |
| 820 |
|
− if(ev.is_free === false) return `<span class="badge-price">Payant</span>`; |
|
1135 |
+function money(v){ |
|
1136 |
+ return `${v.toFixed(2).replace(".",",").replace(/,00$/,"")} $`; |
|
1137 |
+} |
|
1138 |
+function priceTxt(ev){ |
|
1139 |
+ if(ev.is_free === true) return "Gratuit"; |
|
1140 |
+ if(ev.price_min != null) return `Dès ${money(ev.price_min)}`; |
|
1141 |
+ return ""; |
|
1142 |
+} |
|
1143 |
+const WDAYS = ["dim","lun","mar","mer","jeu","ven","sam"]; |
|
1144 |
+/* date éditoriale des fiches : « Mer 27 août · 20 h » / « 27 août → 3 sept » */ |
|
1145 |
+function evDate(ev){ |
|
1146 |
+ if(!ev.start_date) return "Date à confirmer"; |
|
1147 |
+ const yNow = new Date().getFullYear(); |
|
1148 |
+ const [y,m,d] = ev.start_date.split("-").map(Number); |
|
1149 |
+ if(ev.end_date && ev.end_date !== ev.start_date){ |
|
1150 |
+ const [y2,m2,d2] = ev.end_date.split("-").map(Number); |
|
1151 |
+ return `${d} ${MONTHS[m-1]} → ${d2} ${MONTHS[m2-1]}${y2!==yNow?` ${y2}`:""}`; |
|
1152 |
+ } |
|
1153 |
+ const wd = WDAYS[new Date(y, m-1, d).getDay()]; |
|
1154 |
+ let s = `${wd} ${d} ${MONTHS[m-1]}${y!==yNow?` ${y}`:""}`; |
|
1155 |
+ if(ev.start_time && ev.start_time !== "00:00") s += ` · ${fmtTime(ev.start_time)}`; |
|
1156 |
+ return s; |
|
1157 |
+} |
|
1158 |
+/* étiquette posée sur l'image : statut sinon prix */ |
|
1159 |
+function evTag(ev){ |
|
1160 |
+ if(ev.status && STATUS_LABELS[ev.status]) |
|
1161 |
+ return `<span class="ev-tag stat-${esc(ev.status)}">${STATUS_LABELS[ev.status]}</span>`; |
|
1162 |
+ if(ev.is_free === true) return `<span class="ev-tag free">Gratuit</span>`; |
|
1163 |
+ if(ev.price_min != null) return `<span class="ev-tag">Dès ${money(ev.price_min)}</span>`; |
| 821 |
1164 |
return ""; |
| 822 |
1165 |
} |
|
1166 |
+function evImg(ev){ |
|
1167 |
+ return ev.image |
|
1168 |
+ ? `<img src="${esc(ev.image)}" alt="" loading="lazy" |
|
1169 |
+ onerror="this.parentElement.classList.add('noimg');this.remove()">` : ""; |
|
1170 |
+} |
| 823 |
1171 |
|
|
1172 |
+/* fiche standard (grilles + carrousels) — l'image domine, hiérarchie |
|
1173 |
+ DATE / titre / lieu · ville / prix (sur l'image) / catégorie · source */ |
| 824 |
1174 |
function card(ev){ |
| 825 |
|
− const where = [ev.venue, ev.city, ev.region].filter(Boolean) |
| 826 |
|
− .map((x,i)=> i===1?`<b>${esc(x)}</b>`:esc(x)).join(" · "); |
| 827 |
|
− const cats = (ev.categories||[]).filter(c=>c!=="autre").slice(0,3) |
| 828 |
|
− .map(c=>`<span class="cat">${esc(CAT_LABELS[c]||c)}</span>`).join(""); |
| 829 |
|
− const img = ev.image |
| 830 |
|
− ? `<img src="${esc(ev.image)}" alt="" loading="lazy" onerror="this.remove()">` : ""; |
| 831 |
|
− return `<a class="card gk-card gk-hover" data-nav href="/evenement/${esc(ev.uid)}"> |
| 832 |
|
− <div class="card-img">${img}<span class="badge-date">${dateBadge(ev)}</span>${favBtn(ev)}</div> |
| 833 |
|
− <div class="card-body"> |
| 834 |
|
− <div class="card-top"><h3>${esc(ev.title)}</h3>${statusBadge(ev)||priceBadge(ev)}</div> |
| 835 |
|
− ${ev.description?`<div class="desc">${esc(ev.description)}</div>`:""} |
| 836 |
|
− <div class="where">${where||"Lieu à confirmer"}</div> |
| 837 |
|
− <div class="cats">${cats}</div> |
| 838 |
|
− <div class="src"><span>${SRC_LABELS[ev.source]||ev.source}</span><span class="go">Fiche →</span></div> |
| 839 |
|
− </div> |
| 840 |
|
− </a>`; |
|
1175 |
+ const where = [ev.venue, ev.city].filter(Boolean).map(esc).join(" · "); |
|
1176 |
+ const cat = (ev.categories||[]).filter(c=>c!=="autre")[0]; |
|
1177 |
+ return `<a class="ev" data-nav href="/evenement/${esc(ev.uid)}"> |
|
1178 |
+ <div class="ev-img${ev.image?"":" noimg"}">${evImg(ev)}${evTag(ev)}${favBtn(ev)}</div> |
|
1179 |
+ <div class="ev-body"> |
|
1180 |
+ <span class="ev-date">${evDate(ev)}</span> |
|
1181 |
+ <h3>${esc(ev.title)}</h3> |
|
1182 |
+ <p class="ev-where">${where || esc(ev.region || "Québec")}</p> |
|
1183 |
+ <p class="ev-meta">${cat?`<span>${esc(CAT_LABELS[cat]||cat)}</span>`:""} |
|
1184 |
+ <span class="src">${esc(SRC_LABELS[ev.source]||ev.source)}</span></p> |
|
1185 |
+ </div></a>`; |
|
1186 |
+} |
|
1187 |
+ |
|
1188 |
+/* grand événement vedette — image pleine largeur, texte en surimpression */ |
|
1189 |
+function featCard(ev){ |
|
1190 |
+ const where = [ev.venue, ev.city].filter(Boolean).map(esc).join(" · "); |
|
1191 |
+ const p = priceTxt(ev); |
|
1192 |
+ return `<a class="feat" data-nav href="/evenement/${esc(ev.uid)}"> |
|
1193 |
+ ${evImg(ev)}<span class="feat-kick">À la une</span>${favBtn(ev)} |
|
1194 |
+ <div class="feat-body"> |
|
1195 |
+ <span class="feat-date">${evDate(ev)}</span> |
|
1196 |
+ <h3>${esc(ev.title)}</h3> |
|
1197 |
+ <p class="feat-where">${where}${p?` — <b>${p}</b>`:""}</p> |
|
1198 |
+ </div></a>`; |
| 841 |
1199 |
} |
| 842 |
1200 |
|
|
1201 |
+/* rangée compacte (À découvrir) */ |
|
1202 |
+function compactCard(ev){ |
|
1203 |
+ const where = [ev.venue, ev.city].filter(Boolean).map(esc).join(" · "); |
|
1204 |
+ return `<a class="evc" data-nav href="/evenement/${esc(ev.uid)}"> |
|
1205 |
+ <span class="evc-img${ev.image?"":" noimg"}">${evImg(ev)}</span> |
|
1206 |
+ <span class="evc-body"> |
|
1207 |
+ <span class="ev-date">${evDate(ev)}</span> |
|
1208 |
+ <h3>${esc(ev.title)}</h3> |
|
1209 |
+ <p class="ev-where">${where || esc(ev.region || "Québec")}</p> |
|
1210 |
+ </span></a>`; |
|
1211 |
+} |
|
1212 |
+ |
|
1213 |
+/* ---------- état des filtres ---------- */ |
|
1214 |
+function isoLocal(d){ |
|
1215 |
+ return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}`; |
|
1216 |
+} |
|
1217 |
+const todayISO = () => isoLocal(new Date()); |
|
1218 |
+/* fin de semaine : vendredi → dimanche (ou aujourd'hui si déjà entamée) */ |
|
1219 |
+function weekendRange(){ |
|
1220 |
+ const t = new Date(), day = t.getDay(); |
|
1221 |
+ const fri = new Date(t); |
|
1222 |
+ if(day >= 1 && day <= 4) fri.setDate(t.getDate() + (5 - day)); |
|
1223 |
+ const sun = new Date(fri); |
|
1224 |
+ sun.setDate(fri.getDate() + (fri.getDay() === 0 ? 0 : 7 - fri.getDay())); |
|
1225 |
+ return [isoLocal(fri), isoLocal(sun)]; |
|
1226 |
+} |
|
1227 |
+function plusDays(n){ |
|
1228 |
+ const d = new Date(); d.setDate(d.getDate()+n); return isoLocal(d); |
|
1229 |
+} |
|
1230 |
+function isFiltering(){ |
|
1231 |
+ return !!(state.q || state.region || state.category || state.free |
|
1232 |
+ || state.from || state.to || state.near); |
|
1233 |
+} |
| 843 |
1234 |
function params(){ |
| 844 |
1235 |
const p = new URLSearchParams(); |
| 845 |
1236 |
if(state.q) p.set("q", state.q); |
| 848 |
1239 |
if(state.free) p.set("free","true"); |
| 849 |
1240 |
if(state.from) p.set("from", state.from); |
| 850 |
1241 |
if(state.to) p.set("to", state.to); |
|
1242 |
+ if(state.near){ p.set("near", state.near); p.set("radius_km","30"); p.set("sort","near"); } |
| 851 |
1243 |
p.set("limit", state.limit); p.set("offset", state.offset); |
| 852 |
1244 |
return p.toString(); |
| 853 |
1245 |
} |
|
1246 |
+async function fetchEvents(qs){ |
|
1247 |
+ return await (await fetch(`/api/events?${qs}`)).json(); |
|
1248 |
+} |
|
1249 |
+ |
|
1250 |
+/* ---------- accueil éditorial : sections composées ---------- */ |
|
1251 |
+let HOME_DONE = false; |
|
1252 |
+const HOME_DEFS = [ |
|
1253 |
+ {id:"alaune", sk:"Sélection Sorti·Ka", t:"À la une", mode:"feat", |
|
1254 |
+ q:{limit:30}}, |
|
1255 |
+ {id:"cesoir", sk:"Aujourd'hui", t:"Ce soir", mode:"row", |
|
1256 |
+ q:()=>({from:todayISO(), to:todayISO(), limit:24}), all:()=>({from:todayISO(), to:todayISO()})}, |
|
1257 |
+ {id:"weekend", sk:"Vendredi → dimanche", t:"Cette fin de semaine", mode:"grid", |
|
1258 |
+ q:()=>{ const [a,b]=weekendRange(); return {from:a, to:b, limit:8}; }, |
|
1259 |
+ all:()=>{ const [a,b]=weekendRange(); return {from:a, to:b}; }}, |
|
1260 |
+ {id:"gratuit", sk:"0 $", t:"Gratuit", mode:"row", |
|
1261 |
+ q:{free:true, limit:14}, all:{free:true}}, |
|
1262 |
+ {id:"musique", sk:"Catégorie", t:"Concerts & musique", mode:"row", |
|
1263 |
+ q:{category:"musique", limit:14}, all:{category:"musique"}}, |
|
1264 |
+ {id:"festival", sk:"Catégorie", t:"Festivals", mode:"row", |
|
1265 |
+ q:{category:"festival", limit:14}, all:{category:"festival"}}, |
|
1266 |
+ {id:"famille", sk:"Catégorie", t:"Famille", mode:"row", |
|
1267 |
+ q:{category:"famille", limit:14}, all:{category:"famille"}}, |
|
1268 |
+ {id:"decouvrir", sk:"Fraîchement ajoutés", t:"À découvrir", mode:"compact", |
|
1269 |
+ q:{sort:"recent", limit:10}}, |
|
1270 |
+]; |
|
1271 |
+function secHead(d, total){ |
|
1272 |
+ const n = total ? `<button type="button" class="sec-all" data-all="${d.id}"> |
|
1273 |
+ Tout voir (${total.toLocaleString("fr-CA")}) →</button>` : ""; |
|
1274 |
+ return `<div class="sec-head"><div><span class="sk">${d.sk}</span><h2>${d.t}</h2></div>${n}</div>`; |
|
1275 |
+} |
|
1276 |
+function heroMosaic(evs){ |
|
1277 |
+ const tiles = evs.filter(e=>e.image).slice(0,12); |
|
1278 |
+ if(!tiles.length) return; |
|
1279 |
+ $("hero-mosaic").innerHTML = tiles.map(e => |
|
1280 |
+ `<span class="tile"><img src="${esc(e.image)}" alt="" loading="lazy" |
|
1281 |
+ onerror="this.remove()"></span>`).join(""); |
|
1282 |
+} |
|
1283 |
+/* durée d'un événement en jours (0 = un seul jour) */ |
|
1284 |
+function evSpan(e){ |
|
1285 |
+ if(!e.end_date || !e.start_date || e.end_date === e.start_date) return 0; |
|
1286 |
+ return (new Date(e.end_date) - new Date(e.start_date)) / 864e5; |
|
1287 |
+} |
|
1288 |
+async function renderHome(){ |
|
1289 |
+ if(HOME_DONE) return; |
|
1290 |
+ HOME_DONE = true; |
|
1291 |
+ const host = $("home"); |
|
1292 |
+ host.innerHTML = HOME_DEFS.map(d=>`<section class="sec" id="sec-${d.id}"></section>`).join(""); |
|
1293 |
+ const results = await Promise.all(HOME_DEFS.map(async d => { |
|
1294 |
+ try{ |
|
1295 |
+ const spec = typeof d.q === "function" ? d.q() : d.q; |
|
1296 |
+ const p = new URLSearchParams(); |
|
1297 |
+ Object.entries(spec).forEach(([k,v]) => p.set(k, v)); |
|
1298 |
+ return [d, await fetchEvents(p.toString())]; |
|
1299 |
+ }catch(e){ return [d, null]; } |
|
1300 |
+ })); |
|
1301 |
+ // rendu séquentiel avec dédoublonnage inter-sections : un événement déjà mis |
|
1302 |
+ // en vitrine plus haut ne se répète pas ; les expos/festivals au long cours |
|
1303 |
+ // cèdent la vedette aux événements datés (le rythme vient de la variété) |
|
1304 |
+ const seen = new Set(); |
|
1305 |
+ const take = (evs, n) => { |
|
1306 |
+ const out = evs.slice(0, n); |
|
1307 |
+ out.forEach(e => seen.add(e.uid)); |
|
1308 |
+ return out; |
|
1309 |
+ }; |
|
1310 |
+ for(const [d, data] of results){ |
|
1311 |
+ const el = $(`sec-${d.id}`); |
|
1312 |
+ if(!el) continue; |
|
1313 |
+ if(!data){ el.remove(); continue; } |
|
1314 |
+ let evs = (data.events || []).filter(e => !seen.has(e.uid)); |
|
1315 |
+ if(d.id === "alaune"){ |
|
1316 |
+ evs = evs.filter(e => e.image); |
|
1317 |
+ evs.sort((a,b) => (evSpan(a) > 3) - (evSpan(b) > 3)); |
|
1318 |
+ } else if(d.id === "cesoir" || d.id === "weekend"){ |
|
1319 |
+ evs.sort((a,b) => (evSpan(a) > 3) - (evSpan(b) > 3)); |
|
1320 |
+ } |
|
1321 |
+ if(!evs.length){ el.remove(); continue; } |
|
1322 |
+ if(d.mode === "feat"){ |
|
1323 |
+ const picked = take(evs, 10); |
|
1324 |
+ el.innerHTML = secHead(d, 0) + featCard(picked[0]) |
|
1325 |
+ + (picked.length > 1 ? `<div class="hscroll">${picked.slice(1).map(card).join("")}</div>` : ""); |
|
1326 |
+ heroMosaic(evs); |
|
1327 |
+ } else if(d.mode === "row"){ |
|
1328 |
+ el.innerHTML = secHead(d, data.total) + `<div class="hscroll">${take(evs, 14).map(card).join("")}</div>`; |
|
1329 |
+ } else if(d.mode === "grid"){ |
|
1330 |
+ el.innerHTML = secHead(d, data.total) + `<div class="grid">${take(evs, 8).map(card).join("")}</div>`; |
|
1331 |
+ } else { |
|
1332 |
+ el.innerHTML = secHead(d, 0) + `<div class="compact">${take(evs, 10).map(compactCard).join("")}</div>`; |
|
1333 |
+ } |
|
1334 |
+ bindNav(el); |
|
1335 |
+ const btn = el.querySelector("[data-all]"); |
|
1336 |
+ if(btn) btn.onclick = () => { |
|
1337 |
+ const f = typeof d.all === "function" ? d.all() : (d.all || {}); |
|
1338 |
+ resetFilters(false); |
|
1339 |
+ if(f.from){ state.from = f.from; state.to = f.to || ""; } |
|
1340 |
+ if(f.free) state.free = true; |
|
1341 |
+ if(f.category) state.category = f.category; |
|
1342 |
+ load(); |
|
1343 |
+ window.scrollTo({top: $("results").offsetTop - 130, behavior:"smooth"}); |
|
1344 |
+ }; |
|
1345 |
+ } |
|
1346 |
+ favPaint(); |
|
1347 |
+} |
|
1348 |
+ |
|
1349 |
+/* ---------- pilules de filtres actifs (résultats) ---------- */ |
|
1350 |
+function filterPills(){ |
|
1351 |
+ const pills = []; |
|
1352 |
+ if(state.q) pills.push(["q", `« ${state.q} »`]); |
|
1353 |
+ if(state.from && state.from === state.to && state.from === todayISO()) pills.push(["dates","Aujourd'hui"]); |
|
1354 |
+ else if(state.from || state.to){ |
|
1355 |
+ const w = weekendRange(); |
|
1356 |
+ pills.push(["dates", state.from===w[0]&&state.to===w[1] ? "Cette fin de semaine" |
|
1357 |
+ : [state.from&&fmtDate(state.from), state.to&&fmtDate(state.to)].filter(Boolean).join(" → ")]); |
|
1358 |
+ } |
|
1359 |
+ if(state.region) pills.push(["region", state.region]); |
|
1360 |
+ if(state.category) pills.push(["category", CAT_LABELS[state.category]||state.category]); |
|
1361 |
+ if(state.free) pills.push(["free", "Gratuit"]); |
|
1362 |
+ if(state.near) pills.push(["near", "Près de moi"]); |
|
1363 |
+ $("afilters").innerHTML = pills.map(([k,label]) => |
|
1364 |
+ `<button type="button" class="apill" data-clear="${k}">${esc(label)}<i>✕</i></button>`).join(""); |
|
1365 |
+ $("afilters").querySelectorAll("[data-clear]").forEach(b => b.onclick = () => { |
|
1366 |
+ const k = b.dataset.clear; |
|
1367 |
+ if(k==="q"){ state.q=""; $("q").value=""; } |
|
1368 |
+ else if(k==="dates"){ state.from=""; state.to=""; } |
|
1369 |
+ else if(k==="region") state.region=""; |
|
1370 |
+ else if(k==="category") state.category=""; |
|
1371 |
+ else if(k==="free") state.free=false; |
|
1372 |
+ else if(k==="near") state.near=""; |
|
1373 |
+ load(); |
|
1374 |
+ }); |
|
1375 |
+} |
| 854 |
1376 |
|
|
1377 |
+/* ---------- chargement : accueil éditorial OU résultats ---------- */ |
| 855 |
1378 |
async function load(reset=true){ |
| 856 |
|
− if(reset){ state.offset = 0; } |
| 857 |
|
− const data = await (await fetch(`/api/events?${params()}`)).json(); |
|
1379 |
+ const filtering = isFiltering(); |
|
1380 |
+ $("home").classList.toggle("hidden", filtering); |
|
1381 |
+ $("results").classList.toggle("hidden", !filtering); |
|
1382 |
+ syncControls(); |
|
1383 |
+ if(!filtering){ renderHome(); return; } |
|
1384 |
+ if(reset) state.offset = 0; |
|
1385 |
+ filterPills(); |
|
1386 |
+ let data; |
|
1387 |
+ try{ data = await fetchEvents(params()); } |
|
1388 |
+ catch(e){ $("count").textContent = "Erreur de chargement — réessayez."; return; } |
| 858 |
1389 |
state.total = data.total; |
| 859 |
1390 |
const html = data.events.map(card).join(""); |
| 860 |
1391 |
if(reset){ |
| 863 |
1394 |
} else { |
| 864 |
1395 |
$("grid").insertAdjacentHTML("beforeend", html); |
| 865 |
1396 |
} |
| 866 |
|
− $("count").innerHTML = `<b>${data.total.toLocaleString("fr-CA")}</b> événement${data.total>1?"s":""} à venir`; |
| 867 |
|
− $("more").hidden = state.offset + data.events.length >= data.total; |
|
1397 |
+ $("count").innerHTML = `<b>${data.total.toLocaleString("fr-CA")}</b> sortie${data.total>1?"s":""} trouvée${data.total>1?"s":""}`; |
|
1398 |
+ $("sh-apply").textContent = `Voir ${data.total.toLocaleString("fr-CA")} sortie${data.total>1?"s":""}`; |
|
1399 |
+ $("more").hidden = state.view === "map" || state.offset + data.events.length >= data.total; |
| 868 |
1400 |
bindNav($("grid")); |
|
1401 |
+ favPaint(); |
|
1402 |
+ if(state.view === "map") refreshMarkers(); |
|
1403 |
+} |
|
1404 |
+let loadT; |
|
1405 |
+function loadDebounced(){ clearTimeout(loadT); loadT = setTimeout(load, 220); } |
|
1406 |
+ |
|
1407 |
+/* ---------- accès rapides + timeline de jours ---------- */ |
|
1408 |
+const QUICK_DEFS = [ |
|
1409 |
+ ["cesoir","Ce soir"], ["weekend","Cette fin de semaine"], ["free","Gratuit"], |
|
1410 |
+]; |
|
1411 |
+const PIN_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" |
|
1412 |
+ stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> |
|
1413 |
+ <path d="M12 21s-7-5.5-7-11a7 7 0 0 1 14 0c0 5.5-7 11-7 11z"/><circle cx="12" cy="10" r="2.6"/></svg>`; |
|
1414 |
+const SLIDERS_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" |
|
1415 |
+ stroke-linecap="round" aria-hidden="true"> |
|
1416 |
+ <path d="M4 7h9M17 7h3M4 17h3M11 17h9"/><circle cx="15" cy="7" r="2.2"/><circle cx="9" cy="17" r="2.2"/></svg>`; |
|
1417 |
+function renderQuick(){ |
|
1418 |
+ $("quick").innerHTML = |
|
1419 |
+ QUICK_DEFS.map(([k,l]) => `<button type="button" class="qchip" data-qk="${k}">${l}</button>`).join("") |
|
1420 |
+ + `<button type="button" class="qchip" data-qk="near">${PIN_SVG}Près de moi</button>` |
|
1421 |
+ + `<button type="button" class="qchip" id="btn-filters">${SLIDERS_SVG}Filtres |
|
1422 |
+ <span class="fb hidden" id="fcount"></span></button>`; |
|
1423 |
+ $("quick").querySelectorAll("[data-qk]").forEach(b => b.onclick = () => { |
|
1424 |
+ const k = b.dataset.qk; |
|
1425 |
+ if(k === "cesoir"){ |
|
1426 |
+ const on = state.from === todayISO() && state.to === todayISO(); |
|
1427 |
+ state.from = on ? "" : todayISO(); state.to = on ? "" : todayISO(); |
|
1428 |
+ } else if(k === "weekend"){ |
|
1429 |
+ const [a,z] = weekendRange(); |
|
1430 |
+ const on = state.from === a && state.to === z; |
|
1431 |
+ state.from = on ? "" : a; state.to = on ? "" : z; |
|
1432 |
+ } else if(k === "free"){ |
|
1433 |
+ state.free = !state.free; |
|
1434 |
+ } else if(k === "near"){ |
|
1435 |
+ toggleNear(); return; |
|
1436 |
+ } |
|
1437 |
+ load(); |
|
1438 |
+ }); |
|
1439 |
+ $("btn-filters").onclick = openSheet; |
|
1440 |
+} |
|
1441 |
+function renderDays(){ |
|
1442 |
+ const t = new Date(); |
|
1443 |
+ let html = `<button type="button" class="day all" data-d=""><small>·Ka</small><b>Tout</b><i> </i></button>`; |
|
1444 |
+ for(let i=0; i<15; i++){ |
|
1445 |
+ const d = new Date(t); d.setDate(t.getDate()+i); |
|
1446 |
+ const iso = isoLocal(d); |
|
1447 |
+ const lab = i===0 ? "auj" : i===1 ? "dem" : WDAYS[d.getDay()]; |
|
1448 |
+ html += `<button type="button" class="day" data-d="${iso}"> |
|
1449 |
+ <small>${lab}</small><b>${d.getDate()}</b><i>${MONTHS[d.getMonth()]}</i></button>`; |
|
1450 |
+ } |
|
1451 |
+ $("days").innerHTML = html; |
|
1452 |
+ $("days").querySelectorAll(".day").forEach(b => b.onclick = () => { |
|
1453 |
+ const d = b.dataset.d; |
|
1454 |
+ if(!d){ state.from = ""; state.to = ""; } |
|
1455 |
+ else if(state.from === d && state.to === d){ state.from = ""; state.to = ""; } |
|
1456 |
+ else { state.from = d; state.to = d; } |
|
1457 |
+ load(); |
|
1458 |
+ }); |
|
1459 |
+} |
|
1460 |
+/* reflète l'état dans les accès rapides, la timeline et le panneau */ |
|
1461 |
+function syncControls(){ |
|
1462 |
+ const today = todayISO(), [wa,wz] = weekendRange(); |
|
1463 |
+ document.querySelectorAll("[data-qk]").forEach(b => { |
|
1464 |
+ const k = b.dataset.qk; |
|
1465 |
+ b.classList.toggle("on", |
|
1466 |
+ k==="cesoir" ? (state.from===today && state.to===today) |
|
1467 |
+ : k==="weekend" ? (state.from===wa && state.to===wz) |
|
1468 |
+ : k==="free" ? state.free |
|
1469 |
+ : k==="near" ? !!state.near : false); |
|
1470 |
+ }); |
|
1471 |
+ document.querySelectorAll(".day[data-d]").forEach(b => |
|
1472 |
+ b.classList.toggle("on", b.dataset.d ? (state.from===b.dataset.d && state.to===b.dataset.d) |
|
1473 |
+ : !state.from && !state.to)); |
|
1474 |
+ const n = ["q","region","category","from","near"].filter(k=>state[k]).length + (state.free?1:0); |
|
1475 |
+ const fc = $("fcount"); |
|
1476 |
+ if(fc){ fc.textContent = n; fc.classList.toggle("hidden", !n); } |
|
1477 |
+ $("q-clear").classList.toggle("hidden", !state.q); |
|
1478 |
+ // panneau de filtres |
|
1479 |
+ if($("region").value !== state.region) $("region").value = state.region; |
|
1480 |
+ document.querySelectorAll("#cats .chip[data-cat]").forEach(x => |
|
1481 |
+ x.classList.toggle("on", x.dataset.cat === state.category)); |
|
1482 |
+ $("chip-free").classList.toggle("on", state.free); |
|
1483 |
+ $("sh-from").value = state.from; $("sh-to").value = state.to; |
|
1484 |
+ const whenKey = (!state.from && !state.to) ? "" : |
|
1485 |
+ (state.from===today && state.to===today) ? "today" : |
|
1486 |
+ (state.from===wa && state.to===wz) ? "weekend" : |
|
1487 |
+ (state.from===today && state.to===plusDays(7)) ? "7" : |
|
1488 |
+ (state.from===today && state.to===plusDays(30)) ? "30" : "custom"; |
|
1489 |
+ document.querySelectorAll("#sh-when .chip").forEach(c => |
|
1490 |
+ c.classList.toggle("on", c.dataset.w === whenKey)); |
|
1491 |
+} |
|
1492 |
+function resetFilters(reload=true){ |
|
1493 |
+ state.region=""; state.category=""; state.free=false; |
|
1494 |
+ state.from=""; state.to=""; state.near=""; |
|
1495 |
+ if(reload) load(); |
|
1496 |
+} |
|
1497 |
+ |
|
1498 |
+/* ---------- géolocalisation « près de moi » ---------- */ |
|
1499 |
+function toast(msg, ms=2600){ |
|
1500 |
+ const t = $("toast"); |
|
1501 |
+ t.textContent = msg; |
|
1502 |
+ t.classList.add("show"); |
|
1503 |
+ clearTimeout(t._t); |
|
1504 |
+ t._t = setTimeout(()=>t.classList.remove("show"), ms); |
|
1505 |
+} |
|
1506 |
+function toggleNear(){ |
|
1507 |
+ if(state.near){ state.near = ""; load(); return; } |
|
1508 |
+ if(!navigator.geolocation){ toast("Géolocalisation non disponible sur cet appareil"); return; } |
|
1509 |
+ toast("Recherche de votre position…"); |
|
1510 |
+ navigator.geolocation.getCurrentPosition( |
|
1511 |
+ pos => { |
|
1512 |
+ state.near = `${pos.coords.latitude.toFixed(5)},${pos.coords.longitude.toFixed(5)}`; |
|
1513 |
+ load(); |
|
1514 |
+ toast("Sorties à moins de 30 km de vous"); |
|
1515 |
+ }, |
|
1516 |
+ () => toast("Position indisponible — vérifiez les permissions de localisation"), |
|
1517 |
+ {timeout: 9000, maximumAge: 600000}); |
|
1518 |
+} |
|
1519 |
+ |
|
1520 |
+/* ---------- panneau de filtres (bottom sheet / modale) ---------- */ |
|
1521 |
+const WHEN_DEFS = [["","À venir"],["today","Aujourd'hui"],["weekend","Fin de semaine"], |
|
1522 |
+ ["7","7 jours"],["30","30 jours"]]; |
|
1523 |
+function openSheet(){ |
|
1524 |
+ $("sh-apply").textContent = isFiltering() && state.total |
|
1525 |
+ ? `Voir ${state.total.toLocaleString("fr-CA")} sorties` : "Voir les sorties"; |
|
1526 |
+ $("sheet").classList.add("open"); |
|
1527 |
+ $("sheet-ov").classList.add("open"); |
|
1528 |
+ document.documentElement.classList.add("ka-scroll-lock"); |
|
1529 |
+ syncControls(); |
|
1530 |
+} |
|
1531 |
+function closeSheet(){ |
|
1532 |
+ $("sheet").classList.remove("open"); |
|
1533 |
+ $("sheet-ov").classList.remove("open"); |
|
1534 |
+ document.documentElement.classList.remove("ka-scroll-lock"); |
|
1535 |
+} |
|
1536 |
+function initSheet(){ |
|
1537 |
+ $("sh-when").innerHTML = WHEN_DEFS.map(([v,l]) => |
|
1538 |
+ `<button type="button" class="chip" data-w="${v}">${l}</button>`).join(""); |
|
1539 |
+ $("sh-when").querySelectorAll(".chip").forEach(c => c.onclick = () => { |
|
1540 |
+ const v = c.dataset.w; |
|
1541 |
+ if(v === "today"){ state.from = todayISO(); state.to = todayISO(); } |
|
1542 |
+ else if(v === "weekend"){ const [a,z] = weekendRange(); state.from = a; state.to = z; } |
|
1543 |
+ else if(v === "7"){ state.from = todayISO(); state.to = plusDays(7); } |
|
1544 |
+ else if(v === "30"){ state.from = todayISO(); state.to = plusDays(30); } |
|
1545 |
+ else { state.from = ""; state.to = ""; } |
|
1546 |
+ loadDebounced(); syncControls(); |
|
1547 |
+ }); |
|
1548 |
+ const onDate = () => { |
|
1549 |
+ state.from = $("sh-from").value; state.to = $("sh-to").value; |
|
1550 |
+ loadDebounced(); syncControls(); |
|
1551 |
+ }; |
|
1552 |
+ $("sh-from").onchange = onDate; $("sh-to").onchange = onDate; |
|
1553 |
+ $("region").onchange = e => { state.region = e.target.value; loadDebounced(); }; |
|
1554 |
+ $("chip-free").onclick = () => { state.free = !state.free; loadDebounced(); syncControls(); }; |
|
1555 |
+ $("sh-reset").onclick = () => { resetFilters(); }; |
|
1556 |
+ $("sh-apply").onclick = closeSheet; |
|
1557 |
+ $("sheet-x").onclick = closeSheet; |
|
1558 |
+ $("sheet-ov").onclick = closeSheet; |
|
1559 |
+ document.addEventListener("keydown", e => { if(e.key === "Escape") closeSheet(); }); |
| 869 |
1560 |
} |
| 870 |
1561 |
|
| 871 |
|
−function setPeriod(v){ |
| 872 |
|
− const today = new Date(); const iso = d => d.toISOString().slice(0,10); |
| 873 |
|
− state.from = ""; state.to = ""; |
| 874 |
|
− if(v==="today"){ state.from = iso(today); state.to = iso(today); } |
| 875 |
|
− else if(v==="weekend"){ |
| 876 |
|
− const day = today.getDay(); |
| 877 |
|
− const sat = new Date(today); |
| 878 |
|
− if(day !== 6 && day !== 0) sat.setDate(today.getDate() + (6 - day)); |
| 879 |
|
− const sun = new Date(sat); sun.setDate(sat.getDate() + (day === 0 ? 0 : 1)); |
| 880 |
|
− state.from = iso(day===6||day===0 ? today : sat); state.to = iso(sun); |
|
1562 |
+/* ---------- vue carte (Leaflet, chargé à la demande) ---------- */ |
|
1563 |
+const MAPS = {inst:null, layer:null, loading:false}; |
|
1564 |
+function ensureLeaflet(){ |
|
1565 |
+ if(window.L) return Promise.resolve(); |
|
1566 |
+ return new Promise((res, rej) => { |
|
1567 |
+ const c = document.createElement("link"); |
|
1568 |
+ c.rel = "stylesheet"; |
|
1569 |
+ c.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"; |
|
1570 |
+ document.head.appendChild(c); |
|
1571 |
+ const s = document.createElement("script"); |
|
1572 |
+ s.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"; |
|
1573 |
+ s.onload = res; s.onerror = rej; |
|
1574 |
+ document.body.appendChild(s); |
|
1575 |
+ }); |
|
1576 |
+} |
|
1577 |
+function setView(v){ |
|
1578 |
+ state.view = v; |
|
1579 |
+ $("v-list").classList.toggle("on", v === "list"); |
|
1580 |
+ $("v-list").setAttribute("aria-pressed", v === "list"); |
|
1581 |
+ $("v-map").classList.toggle("on", v === "map"); |
|
1582 |
+ $("v-map").setAttribute("aria-pressed", v === "map"); |
|
1583 |
+ $("grid").classList.toggle("hidden", v === "map"); |
|
1584 |
+ $("mapwrap").classList.toggle("hidden", v !== "map"); |
|
1585 |
+ $("more").hidden = v === "map" || state.offset + state.limit >= state.total; |
|
1586 |
+ if(v === "map") openMap(); |
|
1587 |
+} |
|
1588 |
+async function openMap(){ |
|
1589 |
+ try{ await ensureLeaflet(); } |
|
1590 |
+ catch(e){ $("map-load").textContent = "Carte indisponible — vérifiez la connexion."; return; } |
|
1591 |
+ if(!MAPS.inst){ |
|
1592 |
+ MAPS.inst = L.map("map", {zoomControl:true}).setView([46.83,-71.25], 6); |
|
1593 |
+ L.tileLayer("https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", { |
|
1594 |
+ attribution:"© <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> · © <a href='https://carto.com/attributions'>CARTO</a>", |
|
1595 |
+ maxZoom: 19, subdomains:"abcd"}).addTo(MAPS.inst); |
|
1596 |
+ MAPS.layer = L.layerGroup().addTo(MAPS.inst); |
|
1597 |
+ MAPS.inst.on("popupopen", e => bindNav(e.popup.getElement())); |
| 881 |
1598 |
} |
| 882 |
|
− else if(v){ const end = new Date(today); end.setDate(today.getDate()+Number(v)); state.from = iso(today); state.to = iso(end); } |
|
1599 |
+ $("map-load").classList.add("hidden"); |
|
1600 |
+ setTimeout(() => MAPS.inst.invalidateSize(), 80); |
|
1601 |
+ refreshMarkers(); |
|
1602 |
+} |
|
1603 |
+function popupHTML(ev){ |
|
1604 |
+ return `<a class="mpop" data-nav href="/evenement/${esc(ev.uid)}"> |
|
1605 |
+ ${ev.image?`<img src="${esc(ev.image)}" alt="" loading="lazy" onerror="this.remove()">`:""} |
|
1606 |
+ <span class="mp-b"><span class="ev-date">${evDate(ev)}</span> |
|
1607 |
+ <h4>${esc(ev.title)}</h4> |
|
1608 |
+ <p>${[ev.venue, ev.city].filter(Boolean).map(esc).join(" · ")}${priceTxt(ev)?` — ${priceTxt(ev)}`:""}</p> |
|
1609 |
+ </span></a>`; |
|
1610 |
+} |
|
1611 |
+/* jusqu'à 600 événements géolocalisés pour les filtres courants */ |
|
1612 |
+async function refreshMarkers(){ |
|
1613 |
+ if(!MAPS.inst || MAPS.loading) return; |
|
1614 |
+ MAPS.loading = true; |
|
1615 |
+ try{ |
|
1616 |
+ const evs = []; |
|
1617 |
+ for(let page=0; page<3; page++){ |
|
1618 |
+ const p = new URLSearchParams(params()); |
|
1619 |
+ p.set("limit","200"); p.set("offset", String(page*200)); |
|
1620 |
+ const data = await fetchEvents(p.toString()); |
|
1621 |
+ evs.push(...data.events); |
|
1622 |
+ if((page+1)*200 >= data.total) break; |
|
1623 |
+ } |
|
1624 |
+ MAPS.layer.clearLayers(); |
|
1625 |
+ const pts = []; |
|
1626 |
+ evs.filter(e => e.lat && e.lng).forEach(ev => { |
|
1627 |
+ pts.push([ev.lat, ev.lng]); |
|
1628 |
+ L.circleMarker([ev.lat, ev.lng], {radius:7, color:"#a12551", weight:1.5, |
|
1629 |
+ fillColor:"#d6336c", fillOpacity:.85}) |
|
1630 |
+ .bindPopup(popupHTML(ev), {closeButton:false}).addTo(MAPS.layer); |
|
1631 |
+ }); |
|
1632 |
+ if(state.near){ |
|
1633 |
+ const [la,ln] = state.near.split(",").map(Number); |
|
1634 |
+ L.circleMarker([la,ln], {radius:9, color:"#141814", weight:2.5, |
|
1635 |
+ fillColor:"#1c5c41", fillOpacity:1}).bindTooltip("Vous êtes ici").addTo(MAPS.layer); |
|
1636 |
+ MAPS.inst.setView([la,ln], 11); |
|
1637 |
+ } else if(pts.length){ |
|
1638 |
+ MAPS.inst.fitBounds(L.latLngBounds(pts).pad(0.12), {maxZoom: 13}); |
|
1639 |
+ } |
|
1640 |
+ const note = $("map-note"); |
|
1641 |
+ note.textContent = `${pts.length.toLocaleString("fr-CA")} sur ${state.total.toLocaleString("fr-CA")} sorties géolocalisées — affinez avec les filtres`; |
|
1642 |
+ note.classList.toggle("hidden", !pts.length); |
|
1643 |
+ }catch(e){} |
|
1644 |
+ MAPS.loading = false; |
| 883 |
1645 |
} |
| 884 |
1646 |
|
| 885 |
1647 |
/* ---------- fiche événement ---------- */ |
| 892 |
1654 |
document.title = `${ev.title} — ${ev.city || "Québec"} | Sorti·Ka`; |
| 893 |
1655 |
$("d-img").innerHTML = ev.image |
| 894 |
1656 |
? `<img src="${esc(ev.image)}" alt="" onerror="this.remove()">` : ""; |
|
1657 |
+ $("d-img").classList.toggle("noimg", !ev.image); |
| 895 |
1658 |
$("d-cats-label").textContent = (ev.categories||[]).filter(c=>c!=="autre") |
| 896 |
1659 |
.map(c=>CAT_LABELS[c]||c).join(" · ") || "Événement"; |
| 897 |
1660 |
$("d-status").innerHTML = ev.status && STATUS_LABELS[ev.status] |
| 925 |
1688 |
`<div class="fact"><span class="k">${k}</span><span class="v">${v}</span></div>`).join(""); |
| 926 |
1689 |
|
| 927 |
1690 |
$("d-cta").href = ev.url; |
|
1691 |
+ $("d-ctabar").innerHTML = `<div class="p"> |
|
1692 |
+ <b>${priceTxt(ev) || (ev.is_free === false ? "Payant — voir la billetterie" : "Voir la billetterie")}</b> |
|
1693 |
+ <span>${evDate(ev)}</span></div> |
|
1694 |
+ <a class="btn btn-cta" href="${esc(ev.url)}" target="_blank" rel="noopener">Billets ↗</a>`; |
| 928 |
1695 |
$("d-src").innerHTML = `<span class="klabel">Source & attribution</span> |
| 929 |
1696 |
<div style="margin-top:6px;font-size:13px">${SRC_LABELS[ev.source]||ev.source}</div> |
| 930 |
1697 |
<div style="font-family:var(--font-mono);font-size:10px;color:var(--ink-3);margin-top:4px"> |
| 1796 |
2563 |
function favCard(it){ |
| 1797 |
2564 |
FAV.meta[it.item_id] = it; |
| 1798 |
2565 |
const img = it.image_url |
| 1799 |
|
− ? `<img src="${esc(it.image_url)}" alt="" loading="lazy" onerror="this.remove()">` : ""; |
|
2566 |
+ ? `<img src="${esc(it.image_url)}" alt="" loading="lazy" |
|
2567 |
+ onerror="this.parentElement.classList.add('noimg');this.remove()">` : ""; |
| 1800 |
2568 |
const local = (it.url || "").replace(/^https?:\/\/[^/]*sorti-ka\.com/i, "") |
| 1801 |
2569 |
|| `/evenement/${encodeURIComponent(it.item_id)}`; |
| 1802 |
|
− return `<a class="card gk-card gk-hover" data-nav href="${esc(local)}"> |
| 1803 |
|
− <div class="card-img">${img} |
|
2570 |
+ return `<a class="ev" data-nav href="${esc(local)}"> |
|
2571 |
+ <div class="ev-img${it.image_url?"":" noimg"}">${img} |
|
2572 |
+ ${it.price_label?`<span class="ev-tag${it.price_label==="Gratuit"?" free":""}">${esc(it.price_label)}</span>`:""} |
| 1804 |
2573 |
<button type="button" class="fav-btn on" data-fav="${esc(it.item_id)}" aria-pressed="true" |
| 1805 |
2574 |
aria-label="Retirer de mes favoris (Mon univers Ka)" |
| 1806 |
2575 |
title="Retirer de mes favoris (Mon univers Ka)">${FAV_HEART}</button> |
| 1807 |
2576 |
</div> |
| 1808 |
|
− <div class="card-body"> |
| 1809 |
|
− <div class="card-top"><h3>${esc(it.title)}</h3>${it.price_label?`<span class="badge-price">${esc(it.price_label)}</span>`:""}</div> |
| 1810 |
|
− ${it.subtitle?`<div class="where">${esc(it.subtitle)}</div>`:""} |
| 1811 |
|
− <div class="src"><span>Mon univers Ka</span><span class="go">Fiche →</span></div> |
|
2577 |
+ <div class="ev-body"> |
|
2578 |
+ <h3>${esc(it.title)}</h3> |
|
2579 |
+ ${it.subtitle?`<p class="ev-where">${esc(it.subtitle)}</p>`:""} |
|
2580 |
+ <p class="ev-meta"><span class="src">Mon univers Ka</span></p> |
| 1812 |
2581 |
</div> |
| 1813 |
2582 |
</a>`; |
| 1814 |
2583 |
} |
| 1874 |
2643 |
else if(location.pathname === "/contact") showContact(); |
| 1875 |
2644 |
else if(location.pathname === "/favoris") showFavoris(); |
| 1876 |
2645 |
else if(location.pathname === "/stats") showStats(); |
| 1877 |
|
− else if(r){ |
| 1878 |
|
− // page SSR /region/{slug} : la SPA ouvre la recherche filtrée sur la région |
| 1879 |
|
− const name = allRegions.find(x => kaSlug(x) === decodeURIComponent(r[1])); |
| 1880 |
|
− if(name){ state.region = name; $("region").value = name; } |
| 1881 |
|
− showList(); load(); |
| 1882 |
|
− } |
| 1883 |
|
− else if(c){ |
| 1884 |
|
− // page SSR /categorie/{cat} : recherche filtrée sur la catégorie |
| 1885 |
|
− const cat = decodeURIComponent(c[1]); |
| 1886 |
|
− state.category = cat; |
| 1887 |
|
− document.querySelectorAll(".chip[data-cat]").forEach(x=>x.classList.toggle("on", x.dataset.cat===cat)); |
| 1888 |
|
− showList(); load(); |
|
2646 |
+ else { |
|
2647 |
+ if(r){ |
|
2648 |
+ // page SSR /region/{slug} : la SPA ouvre la recherche filtrée sur la région |
|
2649 |
+ const name = allRegions.find(x => kaSlug(x) === decodeURIComponent(r[1])); |
|
2650 |
+ if(name) state.region = name; |
|
2651 |
+ } else if(c){ |
|
2652 |
+ // page SSR /categorie/{cat} : recherche filtrée sur la catégorie |
|
2653 |
+ state.category = decodeURIComponent(c[1]); |
|
2654 |
+ } |
|
2655 |
+ showList(); |
|
2656 |
+ load(); |
| 1889 |
2657 |
} |
| 1890 |
|
− else showList(); |
| 1891 |
2658 |
} |
| 1892 |
2659 |
function go(path){ |
| 1893 |
2660 |
history.pushState({}, "", path); |
| 1922 |
2689 |
/* ---------- init ---------- */ |
| 1923 |
2690 |
async function init(){ |
| 1924 |
2691 |
favLoad(); // favoris ♥ — repeint les cœurs dès la réponse du hub |
| 1925 |
|
− const regions = await (await fetch("/api/regions")).json(); |
| 1926 |
|
− allRegions = regions; |
| 1927 |
|
− $("region").insertAdjacentHTML("beforeend", |
| 1928 |
|
− regions.map(r=>`<option>${esc(r)}</option>`).join("")); |
| 1929 |
|
− |
| 1930 |
|
− const cats = await (await fetch("/api/categories")).json(); |
| 1931 |
|
− $("cats").innerHTML = |
| 1932 |
|
− `<span class="chip free" id="chip-free">✦ Gratuit</span>` + |
| 1933 |
|
− cats.filter(c=>c!=="autre").map(c=>`<span class="chip" data-cat="${c}">${CAT_LABELS[c]||c}</span>`).join(""); |
| 1934 |
|
− $("chip-free").onclick = e => { state.free=!state.free; e.target.classList.toggle("on",state.free); load(); }; |
| 1935 |
|
− document.querySelectorAll(".chip[data-cat]").forEach(ch => ch.onclick = () => { |
| 1936 |
|
− const c = ch.dataset.cat; |
| 1937 |
|
− state.category = state.category===c ? "" : c; |
| 1938 |
|
− document.querySelectorAll(".chip[data-cat]").forEach(x=>x.classList.toggle("on", x.dataset.cat===state.category)); |
| 1939 |
|
− load(); |
|
2692 |
+ renderQuick(); |
|
2693 |
+ renderDays(); |
|
2694 |
+ initSheet(); |
|
2695 |
+ |
|
2696 |
+ // recherche au centre |
|
2697 |
+ let t; $("q").addEventListener("input", e=>{ |
|
2698 |
+ clearTimeout(t); t=setTimeout(()=>{ state.q=e.target.value.trim(); load(); }, 280); |
| 1940 |
2699 |
}); |
|
2700 |
+ $("q-clear").onclick = () => { $("q").value=""; state.q=""; load(); }; |
|
2701 |
+ |
|
2702 |
+ // bascule Liste | Carte + pagination |
|
2703 |
+ $("v-list").onclick = () => setView("list"); |
|
2704 |
+ $("v-map").onclick = () => setView("map"); |
|
2705 |
+ $("more").onclick = () => { state.offset += state.limit; load(false); }; |
|
2706 |
+ bindNav(document); |
|
2707 |
+ |
|
2708 |
+ // référentiels (avant route() : /region/{slug} en a besoin) |
|
2709 |
+ try{ |
|
2710 |
+ const regions = await (await fetch("/api/regions")).json(); |
|
2711 |
+ allRegions = regions; |
|
2712 |
+ $("region").insertAdjacentHTML("beforeend", |
|
2713 |
+ regions.map(r=>`<option>${esc(r)}</option>`).join("")); |
|
2714 |
+ }catch(e){} |
|
2715 |
+ try{ |
|
2716 |
+ const cats = await (await fetch("/api/categories")).json(); |
|
2717 |
+ $("cats").innerHTML = cats.filter(c=>c!=="autre") |
|
2718 |
+ .map(c=>`<button type="button" class="chip" data-cat="${c}">${CAT_LABELS[c]||c}</button>`).join(""); |
|
2719 |
+ document.querySelectorAll("#cats .chip[data-cat]").forEach(ch => ch.onclick = () => { |
|
2720 |
+ state.category = state.category===ch.dataset.cat ? "" : ch.dataset.cat; |
|
2721 |
+ loadDebounced(); syncControls(); |
|
2722 |
+ }); |
|
2723 |
+ }catch(e){} |
| 1941 |
2724 |
|
|
2725 |
+ // compteurs = preuve de richesse, intégrés au hero en phrase éditoriale |
| 1942 |
2726 |
try{ |
| 1943 |
2727 |
const s = await (await fetch("/api/stats")).json(); |
| 1944 |
2728 |
const nReg = Object.keys(s.by_region).filter(r=>r!=="Non rattachée").length; |
| 1945 |
|
− $("stats").innerHTML = [ |
| 1946 |
|
− `<span class="stat-chip"><span class="pulse"></span><b>${s.upcoming.toLocaleString("fr-CA")}</b> événements à venir</span>`, |
| 1947 |
|
− `<span class="stat-chip"><b>${s.free_upcoming.toLocaleString("fr-CA")}</b> gratuits</span>`, |
| 1948 |
|
− `<span class="stat-chip"><b>${nReg}</b> régions</span>`, |
| 1949 |
|
− `<span class="stat-chip"><b>${s.cities.toLocaleString("fr-CA")}</b> villes</span>`, |
| 1950 |
|
− ].join(""); |
|
2729 |
+ const f = n => n.toLocaleString("fr-CA"); |
|
2730 |
+ $("stats").innerHTML = |
|
2731 |
+ `<b>${f(s.upcoming)}</b> événements à venir <i>·</i> dans <b>${f(s.cities)}</b> villes ` |
|
2732 |
+ + `<i>·</i> sur <b>${nReg}</b> régions <i>·</i> dont <b>${f(s.free_upcoming)}</b> gratuits`; |
| 1951 |
2733 |
}catch(e){} |
| 1952 |
2734 |
|
| 1953 |
2735 |
try{ |
| 1954 |
2736 |
const reg = await (await fetch("/api/sources")).json(); |
| 1955 |
2737 |
$("src-grid").innerHTML = (reg.sources||[]).filter(s=>s.statut==="actif"||s.active_events>0).map(s=>` |
| 1956 |
|
− <div class="src-card gk-card"> |
|
2738 |
+ <div class="src-card"> |
| 1957 |
2739 |
<h4>${esc(s.name)}</h4> |
| 1958 |
2740 |
<div class="lic">${esc((s.acces_legal||"").split("—")[0])}</div> |
| 1959 |
2741 |
<div class="n"><b>${(s.active_events||0).toLocaleString("fr-CA")}</b> événements actifs · ${esc((s.extraction||"").split("(")[0])}</div> |
| 1978 |
2760 |
}; |
| 1979 |
2761 |
} else { |
| 1980 |
2762 |
$("auth").innerHTML = `<a class="btn-ka" href="/api/auth/ka/login"> |
| 1981 |
|
− <span class="kdot">·Ka</span> Se connecter avec KA</a> |
|
2763 |
+ <span class="kdot">·Ka</span><span class="tx">Se connecter avec KA</span></a> |
| 1982 |
2764 |
<a class="ka-signup" href="https://www.groupe-ka.com/connexion" |
| 1983 |
2765 |
target="_blank" rel="noopener">Créer un compte</a>`; |
| 1984 |
2766 |
} |
| 1985 |
2767 |
} |
| 1986 |
2768 |
}catch(e){} |
| 1987 |
2769 |
|
| 1988 |
|
− let t; $("q").addEventListener("input", e=>{ |
| 1989 |
|
− clearTimeout(t); t=setTimeout(()=>{ state.q=e.target.value.trim(); load(); }, 280); |
| 1990 |
|
− }); |
| 1991 |
|
− $("region").onchange = e => { state.region = e.target.value; load(); }; |
| 1992 |
|
− $("period").onchange = e => { setPeriod(e.target.value); load(); }; |
| 1993 |
|
− $("more").onclick = () => { state.offset += state.limit; load(false); }; |
| 1994 |
|
− bindNav(document); |
| 1995 |
|
− |
| 1996 |
|
− await load(); |
| 1997 |
2770 |
route(); |
| 1998 |
2771 |
} |
| 1999 |
2772 |
init(); |