@@ -26,7 +26,7 @@ const KAL_ERR_BUILD_ID_MISMATCH = -22
2626
2727// Base path résolu depuis <base href> — patché par index.html sur GitHub Pages projet.
2828// Vaut '/' dans tous les autres environnements.
29- const BASE_PATH = `/app/liturgical-calendar/`
29+ const BASE_PATH = new URL ( document . baseURI ) . pathname
3030
3131// ── Lookup tables (miroir de types.rs) ───────────────────────────────────────
3232
@@ -80,25 +80,40 @@ function formatDateLong(year, month, day) {
8080
8181// ── Routage ───────────────────────────────────────────────────────────────────
8282
83+ /** Vérifie qu'une date grégorienne existe (gère les années bissextiles). */
84+ function isValidDate ( year , month , day ) {
85+ const d = new Date ( year , month - 1 , day )
86+ return d . getFullYear ( ) === year && d . getMonth ( ) === month - 1 && d . getDate ( ) === day
87+ }
88+
8389function detectRoute ( ) {
8490 let raw = window . location . pathname
8591 if ( raw . startsWith ( BASE_PATH ) ) raw = raw . slice ( BASE_PATH . length )
8692 raw = raw . replace ( / \/ $ / , '' )
8793
94+ // Racine → date du jour
95+ if ( raw === '' ) {
96+ const now = new Date ( )
97+ return { type : 'day' , year : now . getFullYear ( ) , month : now . getMonth ( ) + 1 , day : now . getDate ( ) }
98+ }
99+
88100 const mYear = raw . match ( / ^ ( \d { 4 } ) $ / )
89- if ( mYear ) return { type : 'year' , year : parseInt ( mYear [ 1 ] ) }
101+ if ( mYear ) {
102+ const year = parseInt ( mYear [ 1 ] )
103+ if ( year < 1970 || year > 2399 ) return { type : 'not-found' }
104+ return { type : 'year' , year }
105+ }
90106
91107 const mDay = raw . match ( / ^ ( \d { 4 } ) \/ ( \d { 2 } ) \/ ( \d { 2 } ) $ / )
92- if ( mDay )
93- return {
94- type : 'day' ,
95- year : parseInt ( mDay [ 1 ] ) ,
96- month : parseInt ( mDay [ 2 ] ) ,
97- day : parseInt ( mDay [ 3 ] ) ,
98- }
108+ if ( mDay ) {
109+ const year = parseInt ( mDay [ 1 ] )
110+ const month = parseInt ( mDay [ 2 ] )
111+ const day = parseInt ( mDay [ 3 ] )
112+ if ( ! isValidDate ( year , month , day ) ) return { type : 'not-found' }
113+ return { type : 'day' , year , month , day }
114+ }
99115
100- const now = new Date ( )
101- return { type : 'day' , year : now . getFullYear ( ) , month : now . getMonth ( ) + 1 , day : now . getDate ( ) }
116+ return { type : 'not-found' }
102117}
103118
104119// ── Utilitaires WASM ──────────────────────────────────────────────────────────
@@ -295,7 +310,7 @@ function renderDay(year, month, day, exports, memory) {
295310
296311 // Rendu fête principale
297312 let html = `<section class="feast primary color-${ COLOR_CSS [ color ] ?? '' } ">
298- <h3 >${ label } </h3 >`
313+ <h2 >${ label } </h2 >`
299314 if ( annotation ) html += `<p class="annotation">${ renderMarkdown ( annotation ) } </p>`
300315 html += `<ul>
301316 <li>Feast ID: 0x${ feastId . toString ( 16 ) . toUpperCase ( ) . padStart ( 4 , '0' ) } </li>
@@ -312,7 +327,7 @@ function renderDay(year, month, day, exports, memory) {
312327 // Fêtes secondaires
313328 if ( secCount > 0 && exports . kal_wasm_read_secondary ( secOffset , secCount ) === KAL_ENGINE_OK ) {
314329 const sv = new DataView ( memory . buffer , exports . kal_wasm_secondary_ptr ( ) , secCount * 2 )
315- html += `<section class="secondaries"><h4 >Commémorations</h4 >`
330+ html += `<section class="secondaries"><h2 class="sr-only" >Commémorations</h2 >`
316331 for ( let i = 0 ; i < secCount ; i ++ ) {
317332 const ridx = sv . getUint16 ( i * 2 , true )
318333 if ( ridx === 0 ) continue
@@ -322,7 +337,7 @@ function renderDay(year, month, day, exports, memory) {
322337 const fv2 = new DataView ( memory . buffer , feastPtr , 4 ) // FEAST_BUF still set from resolveSecondary
323338 const secFeastId = fv2 . getUint16 ( 0 , true )
324339 html += `<div class="feast secondary color-${ COLOR_CSS [ sf . color ] ?? '' } ">
325- <strong >${ res . label } </strong >`
340+ <h3 >${ res . label } </h3 >`
326341 if ( res . annotation ) html += `<p class="annotation">${ renderMarkdown ( res . annotation ) } </p>`
327342 html += `<ul>
328343 <li>Feast ID: 0x${ secFeastId . toString ( 16 ) . toUpperCase ( ) . padStart ( 4 , '0' ) } </li>
@@ -347,6 +362,24 @@ function renderDay(year, month, day, exports, memory) {
347362 container . hidden = false
348363}
349364
365+ // ── Vue 404 ───────────────────────────────────────────────────────────────────
366+
367+ function renderNotFound ( ) {
368+ document . title = '404 — Page non trouvée'
369+ document . getElementById ( 'h1' ) . innerHTML = 'Calendarium Romanum Generale <span>. 404</span>'
370+ const container = document . getElementById ( 'day-content' )
371+ container . innerHTML = `<section class="not-found">
372+ <p>La ressource demandée n'existe pas.</p>
373+ <p>Routes valides :</p>
374+ <ul>
375+ <li><a href="${ BASE_PATH } ">Date du jour</a></li>
376+ <li><code>YYYY</code> — vue annuelle (ex. <a href="${ BASE_PATH } 2026">2026</a>)</li>
377+ <li><code>YYYY/MM/DD</code> — vue journalière (ex. <a href="${ BASE_PATH } 2026/12/25">2026/12/25</a>)</li>
378+ </ul>
379+ </section>`
380+ container . hidden = false
381+ }
382+
350383// ── Initialisation ────────────────────────────────────────────────────────────
351384
352385async function init ( ) {
@@ -377,7 +410,9 @@ async function init() {
377410
378411 status . hidden = true
379412 const route = detectRoute ( )
380- if ( route . type === 'year' ) {
413+ if ( route . type === 'not-found' ) {
414+ renderNotFound ( )
415+ } else if ( route . type === 'year' ) {
381416 renderYear ( route . year , exports , memory )
382417 } else {
383418 renderDay ( route . year , route . month , route . day , exports , memory )
0 commit comments