{"id":22,"date":"2026-05-20T04:30:05","date_gmt":"2026-05-20T02:30:05","guid":{"rendered":"https:\/\/ecoleouzoud.ac.ma\/?page_id=22"},"modified":"2026-06-08T01:33:29","modified_gmt":"2026-06-07T23:33:29","slug":"espace-parents","status":"publish","type":"page","link":"https:\/\/ecoleouzoud.ac.ma\/?page_id=22","title":{"rendered":"Espace Parents"},"content":{"rendered":"<p>[et_pb_section fb_built=\u00a0\u00bb1&Prime; fullwidth=\u00a0\u00bbon\u00a0\u00bb _builder_version=\u00a0\u00bb4.27.6&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb custom_margin=\u00a0\u00bb0px|20px||20px|false|true\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][et_pb_fullwidth_code _builder_version=\u00a0\u00bb4.27.6&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb]<style>\n:root {\n  --blue:        #3B82F6;\n  --blue-dark:   #1D4ED8;\n  --blue-mid:    #60A5FA;\n  --blue-light:  #EFF6FF; \n  --blue-xlight: #F8FAFC;\n  \n  --orange:       #F97316;\n  --orange-dark:  #C2410C;\n  --orange-light: #FFF7ED;\n  \n  --red:         #EF4444;\n  --red-dark:    #B91C1C;\n  --red-light:   #FEE2E2;\n  \n  --amber:       #F59E0B;\n  --amber-light: #FEF3C7;\n  --purple:      #8B5CF6;\n  --purple-light:#F5F3FF;\n\n  --text:        #1E293B; \n  --text-2:      #475569;\n  --muted:       #64748B;\n  --border:      #E2E8F0;\n  --bg:          #F8FAFC;\n  --white:       #FFFFFF;\n\n  --sidebar:     260px;\n  --r:           12px;\n  --r-lg:        16px;\n  --s1: 0 1px 3px 0 rgba(15,23,42,0.03), 0 1px 2px 0 rgba(15,23,42,0.02);\n  --s2: 0 4px 6px -1px rgba(15,23,42,0.05), 0 2px 4px -1px rgba(15,23,42,0.03);\n  --s3: 0 20px 25px -5px rgba(15,23,42,0.08), 0 10px 10px -5px rgba(15,23,42,0.03);\n  --s-blue: 0 4px 14px 0 rgba(59,130,246,0.15);\n}\n\n.ouzoud-scope * { box-sizing:border-box; margin:0; padding:0; }\n.ouzoud-scope {\n  font-family:'Inter',sans-serif;\n  color:var(--text);\n  background:var(--bg);\n  min-height:82vh;\n  position:relative;\n  overflow:hidden;\n  border-radius:16px;\n  border:1px solid var(--border);\n}\n.ouzoud-scope h1,.ouzoud-scope h2,.ouzoud-scope h3,.ouzoud-scope h4 {\n  font-family:'Plus Jakarta Sans',sans-serif;\n  line-height:1.2;\n}\n.ouzoud-scope button { cursor:pointer; font-family:'Inter',sans-serif; }\n.ouzoud-scope input,.ouzoud-scope select,.ouzoud-scope textarea { font-family:'Inter',sans-serif; }\n\n.login-page { min-height:82vh; display:flex; background:#fff; }\n.login-left { flex:0 0 560px; display:flex; flex-direction:column; justify-content:center; padding:48px 52px; max-width:560px; }\n.login-left h1 { font-size:28px; font-weight:800; color:var(--text); margin-bottom:8px; letter-spacing:-.5px; }\n.login-left > p { color:var(--muted); font-size:14.5px; margin-bottom:28px; line-height:1.6; }\n.lf-tabs { display:flex; gap:4px; margin-bottom:24px; background:#F1F5F9; padding:4px; border-radius:10px; }\n.lf-tab { flex:1; padding:10px 8px; border:none; border-radius:8px; background:transparent; font-size:13px; font-weight:600; color:var(--muted); transition:all .2s cubic-bezier(0.4, 0, 0.2, 1); white-space:nowrap; }\n.lf-tab.active { background:#fff; color:var(--blue-dark); box-shadow:var(--s1); }\n.lf-tab.admin-tab-btn.active { color:var(--purple); }\n.lf-tab.teacher-tab-btn.active { color:#059669; }\n.lf-btn.teacher { background:linear-gradient(135deg,#10B981,#059669); box-shadow:0 4px 14px rgba(16,185,129,.25); }\n.lf-group { margin-bottom:20px; }\n.lf-grid { margin-bottom:20px; }\n.lf-label { display:block; font-size:13px; font-weight:600; color:var(--text-2); margin-bottom:8px; }\n\n.lf-input { width:100%; border:1.5px solid var(--border); border-radius:12px; padding:14px 18px; font-size:15px; color:var(--text); background:#fff; transition:all .2s; height:52px; }\n.lf-input:focus { outline:none; border-color:var(--blue); box-shadow:0 0 0 4px rgba(59,130,246,.1); }\n.lf-input-wrap { position:relative; }\n.lf-input-wrap .lf-input { padding-right:48px; }\n.lf-eye { position:absolute; right:14px; top:50%; transform:translateY(-50%); background:none; border:none; padding:4px; cursor:pointer; color:var(--muted); line-height:1; display:flex; align-items:center; justify-content:center; transition:color .15s; }\n.lf-eye:hover { color:var(--blue-dark); }\n\n.lf-btn { width:100%; padding:15px; background:linear-gradient(135deg, var(--blue), var(--blue-dark)); color:#fff; border:none; border-radius:12px; font-size:14.5px; font-weight:700; margin-top:6px; transition:all .2s cubic-bezier(0.4, 0, 0.2, 1); box-shadow:var(--s-blue); height:52px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n.lf-btn:hover { transform:translateY(-1px); background:linear-gradient(135deg, #2563EB, #1E40AF); box-shadow:0 6px 20px rgba(59,130,246,0.3); }\n.lf-btn.admin { background:linear-gradient(135deg,var(--purple),#5B21B6); box-shadow:0 4px 20px rgba(124,58,237,.25); }\n.lf-btn.admin:hover { background:linear-gradient(135deg, #7C3AED, #4C1D95); box-shadow:0 6px 22px rgba(124,58,237,.4); }\n\n.login-right { \n  flex:1; \n  background:linear-gradient(135deg, #EFF6FF 0%, #FFF7ED 100%); \n  display:flex; \n  flex-direction:column; \n  justify-content:center; \n  padding:48px 40px; \n  color:var(--text); \n  position:relative; \n  overflow:hidden; \n  border-left:1px solid var(--border);\n}\n.login-right::before { content:''; position:absolute; width:460px; height:400px; border-radius:50%; background:linear-gradient(135deg, rgba(59,130,246,0.05), rgba(249,115,22,0.05)); top:-100px; right:-100px; }\n.login-right-inner { position:relative; z-index:1; max-width:480px; }\n\n.login-right h2 { \n  font-size:26px; \n  font-weight:800; \n  color:var(--blue-dark);\n  margin-top: 15px;\n  margin-bottom:14px; \n  line-height:1.2; \n}\n.login-right > .login-right-inner > p { \n  font-size:13px; \n  color:var(--text-2); \n  margin-bottom:32px; \n  line-height:1.6;\n  white-space:nowrap;\n}\n\n.lr-feat { \n  display:flex; \n  align-items:flex-start; \n  gap:16px; \n  margin-bottom:20px; \n  background:#fff; \n  padding:16px; \n  border-radius:12px; \n  border:1px solid rgba(226, 232, 240, 0.8); \n  box-shadow:0 4px 6px -1px rgba(15,23,42,0.02);\n}\n.lr-feat-icon { width:36px; height:36px; border-radius:8px; display:flex; align-items:center; justify-content:center; font-size:16px; flex-shrink:0; line-height:1; text-align:center; }\n\n.lr-feat:nth-child(3) .lr-feat-icon { background:#FEE2E2; color:var(--red); }\n.lr-feat:nth-child(4) .lr-feat-icon { background:var(--blue-light); color:var(--blue-dark); }\n.lr-feat:nth-child(5) .lr-feat-icon { background:var(--orange-light); color:var(--orange); }\n\n.lr-feat h4 { font-size:14.5px; font-weight:700; color:var(--text); margin-bottom:4px; }\n.lr-feat p { font-size:13px; color:var(--muted); line-height:1.4; }\n\n.app-workspace { display:none; min-height:82vh; }\n.app-workspace.active { display:flex; }\n\n.sidebar { width:var(--sidebar); border-right:1px solid var(--border); display:flex; flex-direction:column; padding:0; overflow:hidden; background:#fff; }\n.sb-header { padding:18px 16px 16px; border-bottom:1px solid var(--border); display:flex; align-items:center; gap:10px; }\n.sb-brand-text .name { font-family:'Plus Jakarta Sans',sans-serif; font-size:16px; font-weight:800; color:var(--blue-dark); }\n.sb-user { margin:12px; padding:12px; background:var(--blue-light); border:1px solid rgba(59,130,246,0.12); border-radius:10px; display:flex; align-items:center; gap:10px; }\n.sb-user.admin-mode { background:var(--purple-light); border-color:rgba(124,58,237,0.1); }\n.sb-av { width:36px; height:36px; border-radius:50%; background:linear-gradient(135deg,var(--blue),var(--blue-dark)); display:flex; align-items:center; justify-content:center; color:#fff; font-weight:800; font-size:13px; flex-shrink:0; }\n.sb-av.admin-mode { background:linear-gradient(135deg,var(--purple),#5B21B6); }\n.sb-un { font-size:13px; font-weight:700; color:var(--text); }\n.sb-ur { font-size:11px; color:var(--muted); margin-top:1px; }\n.sb-nav { padding:8px 10px; flex:1; overflow-y:auto; }\n.sb-sec { font-size:11px; font-weight:700; text-transform:uppercase; color:var(--muted); padding:12px 8px 6px; letter-spacing:.05em; }\n.sb-link { display:flex; align-items:center; gap:10px; padding:10px 12px; border-radius:8px; font-size:13.5px; font-weight:500; color:var(--text-2); width:100%; text-align:left; border:none; background:none; margin-bottom:2px; transition:all .15s ease; }\n.sb-link:hover { background:#F1F5F9; color:var(--text); }\n.sb-link.active { background:var(--blue-light); color:var(--blue-dark); font-weight:600; }\n.sb-link.admin-active.active { background:var(--purple-light); color:var(--purple); }\n.sb-link .sb-icon { width:24px; height:24px; display:flex; align-items:center; justify-content:center; font-size:14px; flex-shrink:0; }\n.sb-footer { padding:12px; border-top:1px solid var(--border); }\n.sb-logout { display:flex; align-items:center; justify-content:center; gap:8px; width:100%; padding:12px; border-radius:8px; font-size:13.5px; font-weight:600; color:var(--muted); border:1px solid var(--border); background:#fff; transition:all .15s ease; }\n.sb-logout:hover { color:var(--red); border-color:var(--red); background:var(--red-light); }\n\n.main-body { flex:1; background:var(--bg); display:flex; flex-direction:column; overflow:hidden; }\n.topbar { height:60px; background:#fff; border-bottom:1px solid var(--border); display:flex; align-items:center; justify-content:space-between; padding:0 24px; flex-shrink:0; }\n.topbar-left h3 { font-size:16px; font-weight:700; color:var(--text); }\n\n.topbar-date, .date-left, .tbl td.date-cell { \n  direction: ltr !important; \n  text-align: left !important; \n}\n\n.topbar-date { font-size:13px; color:var(--muted); font-weight:600; background:#F1F5F9; padding:6px 14px; border-radius:20px; }\n.content-area { padding:24px; flex:1; overflow-y:auto; }\n.page-pane { display:none; }\n.page-pane.active { display:block; }\n\n.card { background:#fff; border:1px solid var(--border); border-radius:var(--r-lg); padding:24px; box-shadow:var(--s1); margin-bottom:20px; }\n.card-header { margin-bottom:16px; }\n.card-header h4 { font-size:16px; font-weight:700; color:var(--text); }\n.card-header p { font-size:13px; color:var(--muted); margin-top:2px; }\n\n.g4 { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:20px; }\n.g21 { display:grid; grid-template-columns:2fr 1fr; gap:20px; }\n.g-devoirs { display:grid; grid-template-columns:360px 1fr; gap:20px; align-items:start; }\n.kpi { padding:18px; border-radius:var(--r-lg); background:#fff; border:1px solid var(--border); display:flex; align-items:center; gap:14px; box-shadow:var(--s1); transition:all .2s ease; }\n.kpi:hover { box-shadow:var(--s2); transform:translateY(-1px); }\n.kpi-icon { width:46px; height:46px; border-radius:12px; display:flex; align-items:center; justify-content:center; font-size:20px; flex-shrink:0; }\n.kpi-icon.blue  { background:var(--blue-light); color:var(--blue-dark); }\n.kpi-icon.orange { background:var(--orange-light); color:var(--orange-dark); }\n.kpi-icon.amber { background:var(--amber-light); color:var(--amber); }\n.kpi-icon.red   { background:var(--red-light); color:var(--red); }\n.kpi-val { font-family:'Plus Jakarta Sans',sans-serif; font-size:24px; font-weight:800; color:var(--text); }\n.kpi-label { font-size:12px; color:var(--muted); font-weight:500; margin-top:2px; }\n\n.tbl { width:100%; border-collapse:collapse; table-layout:fixed; }\n.tbl td, .tbl th { overflow:hidden; }\n.tbl th { font-size:11px; font-weight:700; text-transform:uppercase; color:var(--muted); padding:12px 14px; text-align:left; background:#F8FAFC; border-bottom:1px solid var(--border); letter-spacing:.05em; white-space:nowrap; }\n.tbl td { padding:14px; border-bottom:1px solid var(--border); font-size:13.5px; color:var(--text-2); word-break:break-word; }\n.tbl tr:last-child td { border-bottom:none; }\n.tbl tbody tr:hover td { background:#F8FAFC; }\n\/* Scroll indicator on tbl-wrap *\/\n.tbl-wrap { position:relative; }\n.tbl-wrap::after {\n  content:'';\n  position:absolute;\n  top:0; right:0; bottom:0;\n  width:28px;\n  background:linear-gradient(to right, transparent, rgba(255,255,255,0.9));\n  pointer-events:none;\n  opacity:0;\n  transition:opacity .2s;\n  border-radius:0 8px 8px 0;\n}\n.tbl-wrap.can-scroll::after { opacity:1; }\n\n.chip { display:inline-flex; align-items:center; font-size:11.5px; font-weight:600; padding:4px 10px; border-radius:20px; white-space:nowrap; }\n.chip-blue  { background:var(--blue-light);   color:var(--blue-dark); }\n.chip-orange { background:var(--orange-light);  color:var(--orange-dark); }\n.chip-red   { background:var(--red-light);    color:var(--red); }\n.chip-amber { background:var(--amber-light);  color:var(--amber); }\n\n.form-group { margin-bottom:18px; }\n.form-label { display:block; font-size:13px; font-weight:600; color:var(--text-2); margin-bottom:8px; }\n\n.form-input, .form-select, .form-textarea { \n  width:100%; \n  border:1.5px solid var(--border); \n  border-radius:12px; \n  padding:12px 16px; \n  font-size:14.5px; \n  color:var(--text); \n  background:#fff; \n  transition:all .2s; \n}\n.form-input, .form-select {\n  height:48px;\n}\n.form-input:focus, .form-select:focus, .form-textarea:focus { \n  outline:none; \n  border-color:var(--blue); \n  box-shadow:0 0 0 4px rgba(59,130,246,.08); \n}\n.form-textarea { min-height:110px; resize:vertical; line-height:1.5; }\n.form-row { display:grid; grid-template-columns:1fr 1fr; gap:16px; }\n\n.form-stack-full { display:block; }\n.form-stack-full .form-group { margin-bottom:18px; width:100%; }\n\n\/* Recherche rapide d'\u00e9l\u00e8ve (formulaire de saisie d'absence) *\/\n.eleve-search-wrap { position:relative; }\n.eleve-search-wrap .form-input { padding-left:42px; }\n.eleve-search-icon { position:absolute; left:14px; top:50%; transform:translateY(-50%); font-size:15px; color:var(--muted); pointer-events:none; }\n.eleve-search-results {\n  position:absolute; left:0; right:0; top:calc(100% + 6px); z-index:30;\n  background:#fff; border:1.5px solid var(--border); border-radius:12px;\n  box-shadow:var(--s2); max-height:260px; overflow-y:auto; display:none;\n}\n.eleve-search-results.open { display:block; }\n.eleve-search-item { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px 14px; cursor:pointer; font-size:14px; }\n.eleve-search-item:hover, .eleve-search-item.active { background:var(--blue-light); }\n.eleve-search-item .esi-name { font-weight:600; color:var(--text); }\n.eleve-search-item .esi-classe { font-size:12px; color:var(--muted); background:var(--bg); border:1px solid var(--border); border-radius:999px; padding:2px 8px; white-space:nowrap; }\n.eleve-search-empty { padding:12px 14px; font-size:13px; color:var(--muted); text-align:center; }\n.eleve-search-hint { font-size:12px; color:var(--muted); margin-top:6px; display:block; }\n\n\/* Toggle \"saisie multiple\" + checklist d'\u00e9l\u00e8ves (formulaire d'absence) *\/\n.abs-mode-toggle-group { margin-bottom:14px; }\n.abs-mode-toggle { display:flex; align-items:center; gap:9px; cursor:pointer; font-size:13.5px; font-weight:600; color:var(--text-2); user-select:none; }\n.abs-mode-toggle input[type=\"checkbox\"] { width:17px; height:17px; cursor:pointer; accent-color: var(--blue, #2563eb); }\n.abs-multi-actions { display:flex; align-items:center; gap:8px; margin-bottom:8px; flex-wrap:wrap; }\n.abs-multi-action-btn { font-size:12px; font-weight:600; padding:5px 10px; border-radius:8px; border:1.5px solid var(--border); background:#fff; color:var(--text-2); cursor:pointer; }\n.abs-multi-action-btn:hover { background:var(--bg); }\n.abs-multi-count { font-size:12px; color:var(--muted); margin-left:auto; }\n.abs-multi-list { border:1.5px solid var(--border); border-radius:12px; max-height:280px; overflow-y:auto; background:#fff; }\n.abs-multi-item { display:flex; align-items:center; gap:10px; padding:9px 14px; border-bottom:1px solid var(--border); cursor:pointer; font-size:14px; }\n.abs-multi-item:last-child { border-bottom:none; }\n.abs-multi-item:hover { background:var(--bg); }\n.abs-multi-item input[type=\"checkbox\"] { width:16px; height:16px; cursor:pointer; accent-color: var(--blue, #2563eb); flex-shrink:0; }\n.abs-multi-item span { color:var(--text); }\n.abs-multi-item.checked { background:var(--blue-light); }\n\n.btn { display:inline-flex; align-items:center; justify-content:center; gap:8px; border:none; border-radius:12px; padding:12px 24px; font-size:14.5px; font-weight:600; transition:all .15s ease; cursor:pointer; height:48px; }\n.btn-blue { background:linear-gradient(135deg,var(--blue),var(--blue-dark)); color:#fff; box-shadow:0 2px 6px rgba(59,130,246,0.15); }\n.btn-blue:hover { transform:translateY(-1px); background:linear-gradient(135deg, #2563EB, #1E40AF); box-shadow:var(--s-blue); }\n.btn-purple { background:linear-gradient(135deg,var(--purple),#5B21B6); color:#fff; }\n.btn-purple:hover { transform:translateY(-1px); background:linear-gradient(135deg, #7C3AED, #4C1D95); }\n.btn-outline { background:#fff; border:1.5px solid var(--border); color:var(--muted); }\n.btn-outline:hover { border-color:var(--blue); color:var(--blue-dark); background:var(--blue-light); }\n.btn-sm { padding:8px 16px; font-size:13px; border-radius:8px; height:36px; }\n.btn-lg { padding:14px 28px; font-size:15.5px; font-weight:700; border-radius:12px; height:52px; }\n\n.ann-card { border-radius:var(--r); border:1px solid var(--border); padding:16px; background:#fff; margin-bottom:12px; border-left:4px solid var(--blue); }\n.ann-card.urgent { border-left-color:var(--red); }\n\n.modal-ov { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(15,23,42,0.2); backdrop-filter:blur(4px); z-index:9999; display:none; align-items:center; justify-content:center; }\n.modal-box { background:#fff; border-radius:16px; padding:26px; width:100%; max-width:500px; box-shadow:var(--s3); border:1px solid var(--border); }\n.modal-header { display:flex; align-items:flex-start; justify-content:space-between; margin-bottom:20px; }\n.modal-header h3 { font-size:17px; font-weight:800; }\n.modal-header p { font-size:13px; color:var(--muted); margin-top:3px; }\n.modal-close { width:28px; height:28px; border-radius:50%; background:var(--bg); border:none; display:flex; align-items:center; justify-content:center; color:var(--muted); font-size:16px; transition:all .15s; }\n.modal-close:hover { background:var(--red-light); color:var(--red); }\n\n.page-title { display:flex; align-items:center; justify-content:space-between; margin-bottom:20px; }\n.page-title h4 { font-size:18px; font-weight:800; color:var(--text); }\n\n\/* \u2500\u2500 Toast notifications \u2500\u2500 *\/\n.ouzoud-toast-wrap { position:fixed; bottom:28px; right:28px; z-index:99999; display:flex; flex-direction:column; gap:10px; pointer-events:none; }\n.ouzoud-toast { display:flex; align-items:center; gap:10px; padding:13px 18px; border-radius:12px; font-size:13.5px; font-weight:600; color:#fff; box-shadow:0 8px 24px rgba(15,23,42,0.15); opacity:0; transform:translateY(12px); transition:all .25s cubic-bezier(0.4,0,0.2,1); pointer-events:auto; max-width:320px; }\n.ouzoud-toast.show { opacity:1; transform:translateY(0); }\n.ouzoud-toast.success { background:linear-gradient(135deg,#10B981,#059669); }\n.ouzoud-toast.error   { background:linear-gradient(135deg,var(--red),var(--red-dark)); }\n.ouzoud-toast.info    { background:linear-gradient(135deg,var(--blue),var(--blue-dark)); }\n.ouzoud-toast-icon    { font-size:16px; flex-shrink:0; }\n\n\/* \u2500\u2500 Workspace universel (Parent & Admin) : layout topbar \u2500\u2500 *\/\n.main-workspace { display:none; min-height:82vh; flex-direction:column; background:var(--bg); }\n.main-workspace.active { display:flex; }\n\n.main-topbar {\n  background:#fff;\n  border-bottom:1px solid var(--border);\n  padding:0 28px;\n  display:flex;\n  align-items:center;\n  justify-content:space-between;\n  flex-shrink:0;\n  flex-wrap:wrap;\n  gap:0;\n}\n.main-topbar-left { display:flex; align-items:center; gap:0; }\n.main-topbar-brand {\n  display:flex; align-items:center; gap:12px;\n  padding:14px 20px 14px 0;\n  border-right:1px solid var(--border);\n  margin-right:8px;\n}\n.main-topbar-brand .m-brand-name {\n  font-family:'Plus Jakarta Sans',sans-serif;\n  font-size:15px; font-weight:800; letter-spacing:-.3px;\n}\n.main-topbar-brand .m-brand-name.parent-brand { color:var(--blue-dark); }\n.main-topbar-brand .m-brand-name.admin-brand  { color:var(--purple); }\n.main-topbar-brand .m-brand-icon {\n  width:34px; height:34px; border-radius:10px;\n  display:flex; align-items:center; justify-content:center; font-size:16px; flex-shrink:0;\n}\n.main-topbar-brand .m-brand-icon.parent-icon { background:var(--blue-light); }\n.main-topbar-brand .m-brand-icon.admin-icon  { background:var(--purple-light); }\n.main-topbar-brand .main-topbar-user-name { padding-left:12px; border-left:1px solid var(--border); }\n\n\/* Navigation horizontale *\/\n.main-topbar-nav { display:flex; align-items:center; gap:2px; height:100%; position:relative; }\n\/* [FIX#16 v4.6] : indicateur de fade quand la nav devient scrollable horizontalement\n   (tablette\/t\u00e9l\u00e9phone) \u2014 m\u00eame principe que .tbl-wrap, activ\u00e9 via JS (classe .can-scroll). *\/\n.main-topbar-nav::after {\n  content:'';\n  position:absolute;\n  top:0; right:0; bottom:0;\n  width:22px;\n  background:linear-gradient(to right, transparent, rgba(255,255,255,0.95));\n  pointer-events:none;\n  opacity:0;\n  transition:opacity .2s;\n}\n.main-topbar-nav.can-scroll::after { opacity:1; }\n.main-nav-btn {\n  display:flex; align-items:center; gap:7px;\n  padding:10px 14px; border-radius:8px;\n  font-size:13.5px; font-weight:500;\n  color:var(--text-2); border:none; background:none;\n  transition:all .15s ease; white-space:nowrap;\n  height:44px;\n}\n.main-nav-btn:hover { background:#F1F5F9; color:var(--text); }\n.main-nav-btn.active { background:var(--blue-light); color:var(--blue-dark); font-weight:600; }\n.main-nav-btn.admin-nav.active { background:var(--purple-light); color:var(--purple); }\n.main-nav-btn svg { flex-shrink:0; }\n\n.main-topbar-right { display:flex; align-items:center; gap:12px; padding:10px 0; }\n.main-topbar-user { display:flex; align-items:center; gap:8px; }\n.main-topbar-user-name { font-size:15px; font-weight:700; color:var(--text); }\n.main-topbar-date { font-size:13px; color:var(--muted); font-weight:600; background:#F1F5F9; padding:6px 14px; border-radius:20px; }\n.main-logout-btn {\n  display:flex; align-items:center; gap:7px;\n  padding:9px 16px; border-radius:8px;\n  font-size:13px; font-weight:600;\n  color:var(--muted); border:1px solid var(--border); background:#fff;\n  transition:all .15s ease;\n}\n.main-logout-btn:hover { color:var(--red); border-color:var(--red); background:var(--red-light); }\n.main-logout-btn svg { flex-shrink:0; }\n\n.main-content-area { padding:24px; flex:1; overflow-y:auto; }\n\n@media(max-width:860px){\n  \/* [FIX#16 v4.6] : sur tablette\/t\u00e9l\u00e9phone, la marque + la navigation horizontale\n     se partageaient l'espace avec les infos utilisateur, ce qui rendait les onglets\n     trop \u00e9troits pour \u00eatre cliqu\u00e9s confortablement au doigt. On empile d\u00e9sormais\n     la barre sur 2 lignes pleine largeur : (marque + nav), puis (utilisateur + d\u00e9connexion). *\/\n  .main-topbar { padding:0; flex-wrap:wrap; gap:0; }\n  .main-topbar-left { flex-direction:column; align-items:flex-start; width:100%; gap:0; }\n  .main-topbar-brand { width:100%; border-right:none; padding:10px 16px; margin-right:0; box-sizing:border-box; }\n  .main-topbar-nav { width:100%; gap:2px; padding:0 10px 8px; overflow-x:auto; -webkit-overflow-scrolling:touch; }\n  .main-nav-btn { padding:8px 10px; font-size:12.5px; }\n  .main-topbar-right { width:100%; justify-content:space-between; padding:8px 16px; border-top:1px solid var(--border); box-sizing:border-box; }\n  .main-topbar-date { display:none; }\n  .main-topbar-user-name { display:inline-block; max-width:130px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; vertical-align:middle; }\n}\n.teacher-workspace { display:none; min-height:82vh; flex-direction:column; background:var(--bg); }\n.teacher-workspace.active { display:flex; }\n.teacher-topbar {\n  background:#fff;\n  border-bottom:1px solid var(--border);\n  padding:0 28px;\n  min-height:64px;\n  display:flex;\n  align-items:center;\n  justify-content:space-between;\n  flex-shrink:0;\n}\n.teacher-topbar-brand { display:flex; align-items:center; gap:14px; }\n.teacher-topbar-brand .t-brand-name {\n  font-family:'Plus Jakarta Sans',sans-serif;\n  font-size:17px;\n  font-weight:800;\n  color:#059669;\n  letter-spacing:-.3px;\n}\n.teacher-topbar-brand .t-teacher-name { font-size:15px; font-weight:700; color:var(--text); line-height:1.2; }\n.teacher-topbar-brand .t-teacher-name:not(:empty) { padding-left:12px; border-left:1px solid var(--border); }\n.teacher-topbar-brand .t-teacher-name-row { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-top:1px; }\n.teacher-topbar-brand .t-teacher-classes { font-size:12px; color:var(--muted); font-weight:500; display:flex; flex-wrap:wrap; gap:4px; align-items:center; }\n.teacher-topbar-brand .t-teacher-classes .t-class-chip { background:#D1FAE5; color:#065F46; border-radius:999px; padding:1px 8px; font-size:11.5px; font-weight:600; white-space:nowrap; }\n.teacher-topbar-right { display:flex; align-items:center; gap:14px; }\n.teacher-topbar-date { font-size:13px; color:var(--muted); font-weight:600; background:#F1F5F9; padding:6px 14px; border-radius:20px; }\n.teacher-logout-btn {\n  display:flex; align-items:center; gap:8px;\n  padding:10px 18px; border-radius:8px;\n  font-size:13.5px; font-weight:600;\n  color:var(--muted);\n  border:1px solid var(--border);\n  background:#fff;\n  cursor:pointer;\n  transition:all .15s ease;\n}\n.teacher-logout-btn:hover { color:var(--red); border-color:var(--red); background:var(--red-light); }\n.teacher-content {\n  padding:24px;\n  flex:1;\n  overflow-y:auto;\n  display:grid;\n  grid-template-columns:360px 1fr;\n  gap:20px;\n  align-items:start;\n}\n@media(max-width:860px){\n  .teacher-content { grid-template-columns:1fr; }\n  .teacher-topbar { padding:0 16px; min-height:56px; }\n}\n\n\/* \u2500\u2500 Tablette (\u2264 860px) \u2500\u2500 *\/\n@media(max-width:860px){\n  .login-page{flex-direction:column;}\n  \/* [FIX#16 v4.6] : flex:0 0 560px (valeur desktop) devenait une hauteur fixe\n     de 560px une fois .login-page pass\u00e9 en colonne ci-dessus \u2014 ce qui cr\u00e9ait un\n     grand vide avant\/apr\u00e8s le formulaire selon la hauteur de l'\u00e9cran. *\/\n  .login-left{max-width:100%;padding:36px 24px;flex:1 1 auto;}\n  .login-right{display:none;}\n  .app-workspace{flex-direction:column;}\n  .sidebar{width:100%;border-right:none;border-bottom:1px solid var(--border);}\n  .g4{grid-template-columns:1fr 1fr;}\n  .g21{grid-template-columns:1fr;}\n  .g-devoirs{grid-template-columns:1fr;}\n  .page-title{flex-direction:column;align-items:flex-end;gap:12px;}\n  .page-title > div:first-child{align-self:flex-start;}\n  .page-title > div:last-child{display:flex;flex-direction:column;align-items:flex-end;gap:8px;width:auto;}\n  .page-title .btn{white-space:nowrap;}\n  .ouzoud-toast-wrap { bottom:16px; right:16px; left:16px; }\n  .ouzoud-toast { max-width:100%; }\n  \/* scroll horizontal avec indicateur de fade *\/\n  .tbl-wrap { overflow-x:auto; -webkit-overflow-scrolling:touch; border-radius:8px; }\n  .tbl { min-width:560px; }\n  .tbl th, .tbl td { padding:10px 12px; font-size:12.5px; }\n  [id$=\"-pagination\"] { flex-wrap:wrap !important; }\n  [id$=\"-pagination-btns\"] { flex-wrap:wrap !important; }\n}\n\n\/* \u2500\u2500 Mobile (\u2264 600px) : transformation en cartes \u2500\u2500 *\/\n@media(max-width:600px){\n  \/* [FIX#17 v4.6] : la r\u00e8gle ciblait \".content-area\", une classe absente du HTML\n     (le vrai conteneur est \".main-content-area\") \u2014 le padding r\u00e9duit ne s'appliquait\n     donc jamais sur t\u00e9l\u00e9phone, et s'ajoutait aux 24px de \".card\" pour resserrer\n     inutilement la largeur des cartes. On corrige le s\u00e9lecteur ET on resserre\n     aussi le padding interne des cartes pour gagner de la largeur utile. *\/\n  .main-content-area { padding:14px; }\n  .teacher-content { padding:14px; }\n  .card { padding:16px; }\n  .g4 { grid-template-columns:1fr; }\n\n  \/* Chaque ligne devient une carte empil\u00e9e *\/\n  .tbl-wrap { overflow-x:visible; border-radius:0; }\n  .tbl-wrap::after { display:none; }\n  .tbl,\n  .tbl tbody,\n  .tbl tr,\n  .tbl td { display:block; width:100%; box-sizing:border-box; }\n\n  \/* Masquer le thead \u2014 les labels viennent via data-label en CSS *\/\n  .tbl thead { display:none; }\n\n  \/* Carte par ligne *\/\n  .tbl tbody tr {\n    background:#fff;\n    border:1px solid var(--border);\n    border-radius:12px;\n    margin-bottom:10px;\n    padding:4px 0;\n    box-shadow:var(--s1);\n    overflow:hidden;\n  }\n  .tbl tbody tr:last-child { margin-bottom:0; }\n  .tbl tbody tr:hover td { background:transparent; }\n\n  \/* Cellule = ligne label \/ valeur *\/\n  .tbl td {\n    display:flex;\n    align-items:flex-start;\n    gap:8px;\n    padding:9px 14px;\n    border-bottom:1px solid var(--border);\n    font-size:13px;\n    line-height:1.4;\n  }\n  .tbl td:last-child { border-bottom:none; }\n\n  \/* Label affich\u00e9 avant la valeur via data-label *\/\n  .tbl td::before {\n    content: attr(data-label);\n    flex-shrink:0;\n    width:110px;\n    font-size:10.5px;\n    font-weight:700;\n    text-transform:uppercase;\n    letter-spacing:.04em;\n    color:var(--muted);\n    padding-top:1px;\n  }\n\n  \/* Colonne actions : pas de label, centrer les boutons *\/\n  .tbl td[data-label=\"Actions\"],\n  .tbl td[data-label=\"Action\"] {\n    justify-content:flex-end;\n    flex-wrap:wrap;\n    gap:6px;\n  }\n  .tbl td[data-label=\"Actions\"]::before,\n  .tbl td[data-label=\"Action\"]::before { display:none; }\n\n  \/* Cellule vide \u2192 masquer *\/\n  .tbl td:empty { display:none; }\n\n  \/* \u00c9tats vides (colspan) : reset mode carte \u2192 ligne centr\u00e9e pleine largeur *\/\n  .tbl td[colspan] {\n    display:block !important;\n    width:100% !important;\n    text-align:center;\n    padding:20px 14px;\n    color:var(--muted);\n    font-style:italic;\n  }\n  .tbl td[colspan]::before { display:none; }\n\n  \/* Badge statut : pleine largeur dans la carte *\/\n  .tbl td .badge { display:inline-flex; }\n\n  \/* Pagination plus compacte *\/\n  [id$=\"-pagination\"] { flex-direction:column; align-items:flex-start !important; gap:8px !important; }\n  [id$=\"-pagination-btns\"] { flex-wrap:wrap !important; }\n\n  \/* Page title boutons en colonne *\/\n  .page-title > div:last-child { flex-direction:column; align-items:stretch !important; width:100% !important; }\n  .page-title > div:last-child .btn { width:100%; justify-content:center; }\n\n  \/* Modal plein \u00e9cran *\/\n  .modal-ov { align-items:flex-end; }\n  .modal-box { border-radius:16px 16px 0 0; max-height:90vh; overflow-y:auto; max-width:100% !important; }\n\n  \/* [FIX#16 v4.6] : sur un t\u00e9l\u00e9phone \u00e9troit (\u2264375px), le texte des onglets de\n     connexion (\"Administration\", \"Enseignants\"...) pouvait d\u00e9passer visuellement\n     de son bouton. On resserre la taille et on ajoute une troncature de s\u00e9curit\u00e9. *\/\n  .lf-tabs { gap:3px; }\n  .lf-tab { padding:9px 4px; font-size:10.8px; overflow:hidden; text-overflow:ellipsis; }\n  .lf-tab svg { width:13px; height:13px; }\n\n  \/* [FIX#16 v4.6] : le bandeau de l'espace enseignant n'avait pas de retour \u00e0 la\n     ligne \u2014 sur un petit t\u00e9l\u00e9phone, la date et le bouton de d\u00e9connexion pouvaient\n     chevaucher le nom de la marque. On autorise le retour \u00e0 la ligne et on masque\n     la date (d\u00e9j\u00e0 masqu\u00e9e sur les autres bandeaux \u00e0 cette largeur). *\/\n  .teacher-topbar { flex-wrap:wrap; height:auto; padding:10px 16px; gap:8px; row-gap:8px; }\n  .teacher-topbar-date { display:none; }\n  .teacher-topbar-right { width:100%; justify-content:flex-end; }\n  .main-logout-btn, .teacher-logout-btn { padding:8px 12px; font-size:12.5px; }\n\n  \/* [FIX#18 v4.6] : sur t\u00e9l\u00e9phone, le formulaire \"Publier un nouveau Devoir\"\n     est repositionn\u00e9 apr\u00e8s \"Historique du Cahier de Textes\" (on consulte\n     d'abord, on publie ensuite). Les deux cartes gardent la m\u00eame largeur\n     (100%, comme avant), seul l'ordre d'affichage change. *\/\n  .teacher-content > .card { width:100%; margin:0 auto; }\n  .teacher-content > .card:first-child { order:2; }\n  .teacher-content > .card:last-child { order:1; }\n}\n<\/style>\n<div class=\"ouzoud-scope\">\n\n  <div class=\"modal-ov\" id=\"m-csv-import\">\n    <div class=\"modal-box\" style=\"max-width:560px;\">\n      <div class=\"modal-header\">\n        <div>\n          <h3>Importer des Parents via CSV<\/h3>\n          <p>Ajout ou mise \u00e0 jour en masse depuis un fichier CSV<\/p>\n        <\/div>\n        <button class=\"modal-close\" onclick=\"closeOuzoudModal('m-csv-import')\">\u00d7<\/button>\n      <\/div>\n      <div style=\"background:var(--blue-light);border:1px solid rgba(59,130,246,0.2);border-radius:10px;padding:14px 16px;margin-bottom:18px;\">\n        <p style=\"font-size:13px;font-weight:600;color:var(--blue-dark);margin-bottom:6px;\">\ud83d\udccb Format du fichier CSV requis<\/p>\n        <p style=\"font-size:12.5px;color:var(--text-2);line-height:1.7;\">\n          Colonnes : <code style=\"background:#fff;padding:1px 5px;border-radius:4px;font-size:11.5px;\">nom_complet<\/code>, <code style=\"background:#fff;padding:1px 5px;border-radius:4px;font-size:11.5px;\">code_parent<\/code> <span style=\"font-size:11px;color:var(--muted);\">(optionnel)<\/span>, <code style=\"background:#fff;padding:1px 5px;border-radius:4px;font-size:11.5px;\">email<\/code>, <code style=\"background:#fff;padding:1px 5px;border-radius:4px;font-size:11.5px;\">enfants<\/code><br>\n          S\u00e9parateur de colonnes : <b>virgule (,) ou point-virgule (;)<\/b><br>\n          Enfants (m\u00eame cellule) : <b>Prenom Nom (CP-1)|Prenom2 Nom2 (CE2-3)<\/b><br>\n          Si <code style=\"background:#fff;padding:1px 5px;border-radius:4px;font-size:11.5px;\">code_parent<\/code> est omis, il est <b>g\u00e9n\u00e9r\u00e9 automatiquement<\/b> (OUZ-AAAA-XXXX).<br>\n          Si un code_parent existe d\u00e9j\u00e0, la fiche sera <b>mise \u00e0 jour<\/b> automatiquement.\n        <\/p>\n      <\/div>\n      <div style=\"margin-bottom:16px;\">\n        <button class=\"btn btn-outline btn-sm\" onclick=\"downloadCsvTemplate()\" style=\"width:100%;justify-content:center;\">\n          \u2b07\ufe0f T\u00e9l\u00e9charger le fichier mod\u00e8le CSV\n        <\/button>\n      <\/div>\n      <div class=\"form-group\">\n        <label class=\"form-label\">S\u00e9lectionner le fichier CSV *<\/label>\n        <input class=\"form-input\" id=\"csv-file-input\" type=\"file\" accept=\".csv\" style=\"padding:10px;\">\n      <\/div>\n      <div id=\"csv-import-result\" style=\"display:none;margin-bottom:14px;\"><\/div>\n      <div style=\"display:flex;gap:12px;margin-top:20px;\">\n        <button class=\"btn btn-outline\" style=\"flex:1;\" onclick=\"closeOuzoudModal('m-csv-import')\">Annuler<\/button>\n        <button class=\"btn btn-blue\" style=\"flex:1;\" id=\"btn-launch-import\" onclick=\"launchCsvImport()\">\ud83d\ude80 Lancer l'importation<\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div class=\"modal-ov\" id=\"m-parent-form\">\n    <div class=\"modal-box\">\n      <div class=\"modal-header\">\n        <div>\n          <h3 id=\"mp-title\">Ajouter un Parent<\/h3>\n          <p>Gestion de la fiche d'acc\u00e8s parent<\/p>\n        <\/div>\n        <button class=\"modal-close\" onclick=\"closeOuzoudModal('m-parent-form')\">\u00d7<\/button>\n      <\/div>\n      <input type=\"hidden\" id=\"p-id\" value=\"0\">\n      <input type=\"hidden\" id=\"p-code-parent\" value=\"\">\n      <div class=\"form-group\"><label class=\"form-label\">Nom Complet *<\/label><input class=\"form-input\" id=\"p-nom\" type=\"text\" placeholder=\"Mme\/M. ...\"><\/div>\n      <div class=\"form-group\"><label class=\"form-label\">Email<\/label><input class=\"form-input\" id=\"p-email\" type=\"email\" placeholder=\"parent@email.ma\"><\/div>\n      <div class=\"form-group\">\n        <label class=\"form-label\">Enfants rattach\u00e9s * <span style=\"font-weight:400;color:var(--muted);\">(Format: Nom (Classe), s\u00e9par\u00e9s par virgule)<\/span><\/label>\n        <input class=\"form-input\" id=\"p-enfants\" type=\"text\" placeholder=\"Yassine Benali (CE2-1), Nour Benali (PS-1), Sami Benali (6AP-1)\">\n      <\/div>\n      <div style=\"display:flex;gap:12px;margin-top:20px;\">\n        <button class=\"btn btn-outline\" style=\"flex:1;\" onclick=\"closeOuzoudModal('m-parent-form')\">Annuler<\/button>\n        <button class=\"btn btn-blue\" style=\"flex:1;\" onclick=\"submitParentForm()\">Enregistrer<\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <!-- \u2550\u2550 Modal : Fiche Enseignant \u2550\u2550 -->\n  <div class=\"modal-ov\" id=\"m-ens-form\">\n    <div class=\"modal-box\" style=\"max-width:560px;\">\n      <div class=\"modal-header\">\n        <div>\n          <h3 id=\"me-title\">Ajouter un Enseignant<\/h3>\n          <p>Gestion de la fiche enseignant<\/p>\n        <\/div>\n        <button class=\"modal-close\" onclick=\"closeOuzoudModal('m-ens-form')\">\u00d7<\/button>\n      <\/div>\n      <input type=\"hidden\" id=\"e-id\" value=\"0\">\n      <div class=\"form-group\"><label class=\"form-label\">Nom Complet *<\/label><input class=\"form-input\" id=\"e-nom\" type=\"text\" placeholder=\"Ex : Mme Fatima Alaoui\"><\/div>\n      <div class=\"form-group\">\n        <label class=\"form-label\">Mati\u00e8re(s) * <span style=\"font-weight:400;color:var(--muted);\">(s\u00e9par\u00e9es par virgule)<\/span><\/label>\n        <input class=\"form-input\" id=\"e-matieres\" type=\"text\" placeholder=\"Ex : Arabe, Fran\u00e7ais, Maths\">\n      <\/div>\n      <div class=\"form-group\">\n        <label class=\"form-label\">Classe(s) assign\u00e9e(s) *<\/label>\n        <div id=\"e-classes-checkboxes\" style=\"display:flex;flex-wrap:wrap;gap:6px 12px;margin-top:6px;max-height:180px;overflow-y:auto;padding:8px;background:#F8FAFC;border:1px solid #E2E8F0;border-radius:8px;\"><\/div>\n        <small style=\"color:var(--muted);font-size:12px;margin-top:4px;display:block;\">Cochez une ou plusieurs classes<\/small>\n      <\/div>\n      <div style=\"display:flex;gap:12px;margin-top:20px;\">\n        <button class=\"btn btn-outline\" style=\"flex:1;\" onclick=\"closeOuzoudModal('m-ens-form')\">Annuler<\/button>\n        <button class=\"btn btn-lg\" style=\"flex:1;background:linear-gradient(135deg,var(--purple),#5B21B6);color:#fff;\" id=\"btn-save-ens\" onclick=\"submitEnsForm()\">Enregistrer<\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <!-- \u2550\u2550 Modal : Import CSV Enseignants \u2550\u2550 -->\n  <div class=\"modal-ov\" id=\"m-ens-csv-import\">\n    <div class=\"modal-box\" style=\"max-width:560px;\">\n      <div class=\"modal-header\">\n        <div>\n          <h3>Importer des Enseignants via CSV<\/h3>\n          <p>Ajout ou mise \u00e0 jour en masse depuis un fichier CSV<\/p>\n        <\/div>\n        <button class=\"modal-close\" onclick=\"closeOuzoudModal('m-ens-csv-import')\">\u00d7<\/button>\n      <\/div>\n      <div style=\"background:#EDE9FE;border:1px solid rgba(124,58,237,0.2);border-radius:10px;padding:14px 16px;margin-bottom:18px;\">\n        <p style=\"font-size:13px;font-weight:600;color:var(--purple);margin-bottom:6px;\">\ud83d\udccb Format du fichier CSV requis<\/p>\n        <p style=\"font-size:12.5px;color:var(--text-2);line-height:1.7;\">\n          Colonnes : <code style=\"background:#fff;padding:1px 5px;border-radius:4px;font-size:11.5px;\">nom_complet<\/code>, <code style=\"background:#fff;padding:1px 5px;border-radius:4px;font-size:11.5px;\">code_enseignant<\/code> <span style=\"font-size:11px;color:var(--muted);\">(optionnel)<\/span>, <code style=\"background:#fff;padding:1px 5px;border-radius:4px;font-size:11.5px;\">matieres<\/code>, <code style=\"background:#fff;padding:1px 5px;border-radius:4px;font-size:11.5px;\">classes<\/code><br>\n          S\u00e9parateur de colonnes : <b>virgule (,) ou point-virgule (;)<\/b><br>\n          Classes (m\u00eame cellule) : <b>CP-1|CE2-3|CM1-2<\/b><br>\n          Si <code style=\"background:#fff;padding:1px 5px;border-radius:4px;font-size:11.5px;\">code_enseignant<\/code> est omis, il est <b>g\u00e9n\u00e9r\u00e9 automatiquement<\/b> (ENS-AAAA-XXXX).<br>\n          Si un code existe d\u00e9j\u00e0, la fiche sera <b>mise \u00e0 jour<\/b> automatiquement.\n        <\/p>\n      <\/div>\n      <div style=\"margin-bottom:16px;\">\n        <button class=\"btn btn-outline btn-sm\" onclick=\"downloadEnsCsvTemplate()\" style=\"width:100%;justify-content:center;\">\n          \u2b07\ufe0f T\u00e9l\u00e9charger le fichier mod\u00e8le CSV\n        <\/button>\n      <\/div>\n      <div class=\"form-group\">\n        <label class=\"form-label\">S\u00e9lectionner le fichier CSV *<\/label>\n        <input class=\"form-input\" id=\"ens-csv-file-input\" type=\"file\" accept=\".csv\" style=\"padding:10px;\">\n      <\/div>\n      <div id=\"ens-csv-import-result\" style=\"display:none;margin-bottom:14px;\"><\/div>\n      <div style=\"display:flex;gap:12px;margin-top:20px;\">\n        <button class=\"btn btn-outline\" style=\"flex:1;\" onclick=\"closeOuzoudModal('m-ens-csv-import')\">Annuler<\/button>\n        <button class=\"btn btn-lg\" style=\"flex:1;background:linear-gradient(135deg,var(--purple),#5B21B6);color:#fff;\" id=\"btn-launch-ens-import\" onclick=\"launchEnsCsvImport()\">\ud83d\ude80 Lancer l'importation<\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div class=\"login-page\" id=\"ouzoud-login\">\n    <div class=\"login-left\">\n      <h1>Espace num\u00e9rique<\/h1>\n      <p>S\u00e9lectionnez votre profil pour acc\u00e9der \u00e0 l'espace scolaire de suivi et de communication.<\/p>\n      <div class=\"lf-tabs\">\n        <button class=\"lf-tab active\" id=\"btn-tab-p\" onclick=\"toggleLoginTab('parent')\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"vertical-align:-3px;margin-right:6px;\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"\/><circle cx=\"9\" cy=\"7\" r=\"4\"\/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"\/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"\/><\/svg>Espace Parents<\/button>\n        <button class=\"lf-tab teacher-tab-btn\" id=\"btn-tab-t\" onclick=\"toggleLoginTab('teacher')\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"vertical-align:-3px;margin-right:6px;\"><path d=\"M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z\"\/><path d=\"M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z\"\/><\/svg>Enseignants<\/button>\n        <button class=\"lf-tab admin-tab-btn\" id=\"btn-tab-a\" onclick=\"toggleLoginTab('admin')\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"vertical-align:-3px;margin-right:6px;\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"\/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"\/><\/svg>Administration<\/button>\n      <\/div>\n      \n      <div id=\"panel-parent\">\n        <div class=\"lf-group\">\n          <label class=\"lf-label\">Code d'acc\u00e8s parent<\/label>\n          <div class=\"lf-input-wrap\">\n            <input class=\"lf-input\" type=\"password\" id=\"parent-cin-field\" placeholder=\"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\" autocomplete=\"new-password\" onblur=\"validateParentCodeFormat(this);\" oninput=\"this.style.borderColor='';\">\n            <button type=\"button\" class=\"lf-eye\" onclick=\"toggleFieldVisibility('parent-cin-field', this)\" tabindex=\"-1\" aria-label=\"Afficher \/ masquer le code\">\n              <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"\/><circle cx=\"12\" cy=\"12\" r=\"3\"\/><\/svg>\n            <\/button>\n          <\/div>\n        <\/div>\n        <button class=\"lf-btn\" onclick=\"executeLogin('parent')\">Acc\u00e9der \u00e0 l'espace parent \u2192<\/button>\n      <\/div>\n      \n      <div id=\"panel-admin\" style=\"display:none;\">\n        <div class=\"lf-group\">\n          <label class=\"lf-label\">Cl\u00e9 d'administration<\/label>\n          <div class=\"lf-input-wrap\">\n            <input class=\"lf-input\" type=\"password\" id=\"admin-password-field\" placeholder=\"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\" autocomplete=\"new-password\">\n            <button type=\"button\" class=\"lf-eye\" onclick=\"toggleFieldVisibility('admin-password-field', this)\" tabindex=\"-1\" aria-label=\"Afficher \/ masquer le mot de passe\">\n              <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"\/><circle cx=\"12\" cy=\"12\" r=\"3\"\/><\/svg>\n            <\/button>\n          <\/div>\n        <\/div>\n        <button class=\"lf-btn admin\" onclick=\"executeLogin('admin')\">Connexion Administrative \u2192<\/button>\n      <\/div>\n\n      <div id=\"panel-teacher\" style=\"display:none;\">\n        <div class=\"lf-group\">\n          <label class=\"lf-label\">Code enseignant<\/label>\n          <div class=\"lf-input-wrap\">\n            <input class=\"lf-input\" type=\"password\" id=\"teacher-code-field\" placeholder=\"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\"\n                   autocomplete=\"new-password\"\n                   onkeydown=\"if(event.key==='Enter')executeLogin('teacher')\">\n            <button type=\"button\" class=\"lf-eye\" onclick=\"toggleFieldVisibility('teacher-code-field', this)\" tabindex=\"-1\" aria-label=\"Afficher \/ masquer le code enseignant\">\n              <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"\/><circle cx=\"12\" cy=\"12\" r=\"3\"\/><\/svg>\n            <\/button>\n          <\/div>\n        <\/div>\n        <button class=\"lf-btn\" style=\"background:linear-gradient(135deg,#10B981,#059669);box-shadow:0 4px 14px rgba(16,185,129,.25);\" onclick=\"executeLogin('teacher')\">Acc\u00e9der \u00e0 l'espace enseignant \u2192<\/button>\n      <\/div>\n    <\/div>\n    \n    <div class=\"login-right\">\n      <div class=\"login-right-inner\">\n        <h2>Suivi scolaire en temps r\u00e9el<\/h2>\n        <p style=\"color:var(--muted);font-size:14.5px;line-height:1.6;\">Les informations de nos enfants accessibles en un clic, de n'importe o\u00f9.<\/p>\n        \n        <div class=\"lr-feat\">\n          <div class=\"lr-feat-icon\">\ud83d\udcda<\/div>\n          <div><h4>Absences d\u00e9taill\u00e9es<\/h4><p>Suivi par demi-journ\u00e9e avec justification<\/p><\/div>\n        <\/div>\n        <div class=\"lr-feat\">\n          <div class=\"lr-feat-icon\">\ud83d\udcda<\/div>\n          <div><h4>Cahier de textes<\/h4><p>Devoirs et le\u00e7ons planifi\u00e9s par classe<\/p><\/div>\n        <\/div>\n        <div class=\"lr-feat\">\n          <div class=\"lr-feat-icon\">\ud83d\udce2<\/div>\n          <div><h4>Espace Annonces<\/h4><p>Toutes les communications officielles de la direction<\/p><\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <!-- \u2550\u2550 Workspace Parent \u2550\u2550 -->\n  <div class=\"main-workspace\" id=\"ouzoud-parent-app\">\n    <div class=\"main-topbar\">\n      <div class=\"main-topbar-left\">\n        <div class=\"main-topbar-brand\">\n          <div class=\"m-brand-icon parent-icon\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"var(--blue-dark)\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"\/><circle cx=\"9\" cy=\"7\" r=\"4\"\/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"\/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"\/><\/svg>\n          <\/div>\n          <div class=\"m-brand-name parent-brand\">ESPACE PARENT<\/div>\n          <span class=\"main-topbar-user-name\" id=\"p-user-name\">...<\/span>\n        <\/div>\n        <nav class=\"main-topbar-nav\">\n          <button class=\"main-nav-btn active\" id=\"p-nav-dashboard\" onclick=\"switchMainPane('p-dashboard','parent',this)\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"\/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"\/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"\/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"\/><\/svg>\n            Synth\u00e8se\n          <\/button>\n          <button class=\"main-nav-btn\" id=\"p-nav-absences\" onclick=\"switchMainPane('p-absences','parent',this)\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"\/><polyline points=\"14 2 14 8 20 8\"\/><line x1=\"9\" y1=\"15\" x2=\"15\" y2=\"15\"\/><\/svg>\n            Absences\n          <\/button>\n          <button class=\"main-nav-btn\" id=\"p-nav-devoirs\" onclick=\"switchMainPane('p-devoirs','parent',this)\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z\"\/><path d=\"M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z\"\/><\/svg>\n            Cahier de Devoirs\n          <\/button>\n          <button class=\"main-nav-btn\" id=\"p-nav-annonces\" onclick=\"switchMainPane('p-annonces','parent',this)\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 17H2a3 3 0 0 0 3-3V9a7 7 0 0 1 14 0v5a3 3 0 0 0 3 3zm-8.27 4a2 2 0 0 1-3.46 0\"\/><\/svg>\n            Annonces\n          <\/button>\n        <\/nav>\n      <\/div>\n      <div class=\"main-topbar-right\">\n        <div class=\"main-topbar-date\" id=\"p-date-bar\">...<\/div>\n        <button class=\"main-logout-btn\" onclick=\"triggerLogout()\">\n          <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"\/><polyline points=\"16 17 21 12 16 7\"\/><line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"\/><\/svg>\n          D\u00e9connexion\n        <\/button>\n      <\/div>\n    <\/div>\n    <div class=\"main-content-area\">\n      <div class=\"page-pane active\" id=\"p-dashboard\">\n        <div class=\"g4\">\n          <div class=\"kpi\"><div class=\"kpi-icon blue\">\ud83c\udf93<\/div><div><div class=\"kpi-val\" id=\"dash-kids-count\">0<\/div><div class=\"kpi-label\">\u00c9l\u00e8ves \u00e0 charge<\/div><\/div><\/div>\n          <div class=\"kpi\"><div class=\"kpi-icon red\">\u26a0\ufe0f<\/div><div><div class=\"kpi-val\" id=\"dash-abs-count\">0<\/div><div class=\"kpi-label\">Absences<\/div><\/div><\/div>\n          <div class=\"kpi\"><div class=\"kpi-icon orange\">\ud83d\udcda<\/div><div><div class=\"kpi-val\" id=\"dash-devoirs-count\">0<\/div><div class=\"kpi-label\">Devoirs actifs<\/div><\/div><\/div>\n          <div class=\"kpi\"><div class=\"kpi-icon blue\">\ud83d\udce2<\/div><div><div class=\"kpi-val\" id=\"dash-annonces-count\">0<\/div><div class=\"kpi-label\">Annonces \u00e9cole<\/div><\/div><\/div>\n        <\/div>\n        <div class=\"g21\">\n          <div class=\"card\">\n            <div class=\"card-header\"><h4>Derni\u00e8res Annonces<\/h4><p>Communications de la direction<\/p><\/div>\n            <div id=\"p-dash-ann-list\"><\/div>\n          <\/div>\n          <div class=\"card\">\n            <div class=\"card-header\"><h4>Vos Enfants<\/h4><p>Classes rattach\u00e9es<\/p><\/div>\n            <div class=\"tbl-wrap\"><table class=\"tbl\"><thead><tr><th>Pr\u00e9nom &amp; Nom<\/th><th>Classe<\/th><\/tr><\/thead><tbody id=\"p-dash-kids-table\"><\/tbody><\/table><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"page-pane\" id=\"p-absences\">\n        <div class=\"card\">\n          <div class=\"card-header\"><h4>Historique des Absences<\/h4><p>Suivi par ligne pour une visibilit\u00e9 optimale<\/p><\/div>\n          <div id=\"p-absences-pagination\" style=\"display:none;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:nowrap;gap:8px;overflow-x:auto;\">\n            <div id=\"p-absences-pagination-info\" style=\"font-size:13px;color:var(--muted);\"><\/div>\n            <div id=\"p-absences-pagination-btns\" style=\"display:flex;gap:6px;flex-wrap:nowrap;\"><\/div>\n          <\/div>\n          <div class=\"tbl-wrap\"><table class=\"tbl\"><thead><tr><th>\u00c9l\u00e8ve<\/th><th>Classe<\/th><th>Date d'absence<\/th><th>P\u00e9riode<\/th><th>Motif explicatif<\/th><th>Statut<\/th><\/tr><\/thead><tbody id=\"p-table-absences\"><\/tbody><\/table><\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"page-pane\" id=\"p-devoirs\">\n        <div class=\"card\">\n          <div class=\"card-header\"><h4>Cahier de Devoirs<\/h4><p>Liste chronologique des travaux planifi\u00e9s par ligne<\/p><\/div>\n          <div id=\"p-devoirs-pagination\" style=\"display:none;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:nowrap;gap:8px;overflow-x:auto;\">\n            <div id=\"p-devoirs-pagination-info\" style=\"font-size:13px;color:var(--muted);\"><\/div>\n            <div id=\"p-devoirs-pagination-btns\" style=\"display:flex;gap:6px;flex-wrap:nowrap;\"><\/div>\n          <\/div>\n          <div class=\"tbl-wrap\"><table class=\"tbl\">\n            <thead><tr>\n              <th style=\"width:15%;\">Mati\u00e8re<\/th>\n              <th style=\"width:20%;\">Travail demand\u00e9<\/th>\n              <th style=\"width:40%;\">Consignes &amp; D\u00e9tails<\/th>\n              <th style=\"width:10%;\">Document<\/th>\n              <th style=\"width:15%;\">Date de remise<\/th>\n            <\/tr><\/thead>\n            <tbody id=\"p-list-devoirs-table\"><\/tbody>\n          <\/table><\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"page-pane\" id=\"p-annonces\">\n        <div class=\"card\">\n          <div class=\"card-header\"><h4>Flux d'Annonces<\/h4><p>Historique complet des publications<\/p><\/div>\n          <div id=\"p-annonces-pagination\" style=\"display:none;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:nowrap;gap:8px;overflow-x:auto;\">\n            <div id=\"p-annonces-pagination-info\" style=\"font-size:13px;color:var(--muted);\"><\/div>\n            <div id=\"p-annonces-pagination-btns\" style=\"display:flex;gap:6px;flex-wrap:nowrap;\"><\/div>\n          <\/div>\n          <div id=\"p-list-annonces\"><\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n  <!-- \u2550\u2550 \/Workspace Parent \u2550\u2550 -->\n\n  <!-- \u2550\u2550 Workspace Admin \u2550\u2550 -->\n  <div class=\"main-workspace\" id=\"ouzoud-admin-app\">\n    <div class=\"main-topbar\">\n      <div class=\"main-topbar-left\">\n        <div class=\"main-topbar-brand\">\n          <div class=\"m-brand-icon admin-icon\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"var(--purple)\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"\/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"\/><\/svg>\n          <\/div>\n          <div class=\"m-brand-name admin-brand\">ADMINISTRATION<\/div>\n          <span class=\"main-topbar-user-name\" id=\"a-user-name\">Administration<\/span>\n        <\/div>\n        <nav class=\"main-topbar-nav\">\n          <button class=\"main-nav-btn admin-nav active\" id=\"a-nav-parents\" onclick=\"switchMainPane('a-parents','admin',this)\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"\/><circle cx=\"9\" cy=\"7\" r=\"4\"\/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"\/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"\/><\/svg>\n            Parents\n          <\/button>\n          <button class=\"main-nav-btn admin-nav\" id=\"a-nav-enseignants\" onclick=\"switchMainPane('a-enseignants','admin',this)\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z\"\/><path d=\"M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z\"\/><\/svg>\n            Enseignants\n          <\/button>\n          <button class=\"main-nav-btn admin-nav\" id=\"a-nav-absences\" onclick=\"switchMainPane('a-absences','admin',this)\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"\/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"\/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"\/><\/svg>\n            Absences\n          <\/button>\n          <button class=\"main-nav-btn admin-nav\" id=\"a-nav-devoirs\" onclick=\"switchMainPane('a-devoirs','admin',this)\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 19.5A2.5 2.5 0 0 1 6.5 17H20\"\/><path d=\"M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z\"\/><\/svg>\n            Devoirs\n          <\/button>\n          <button class=\"main-nav-btn admin-nav\" id=\"a-nav-annonces\" onclick=\"switchMainPane('a-annonces','admin',this)\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 17H2a3 3 0 0 0 3-3V9a7 7 0 0 1 14 0v5a3 3 0 0 0 3 3zm-8.27 4a2 2 0 0 1-3.46 0\"\/><\/svg>\n            Annonces\n          <\/button>\n        <\/nav>\n      <\/div>\n      <div class=\"main-topbar-right\">\n        <div class=\"main-topbar-date\" id=\"a-date-bar\">...<\/div>\n        <button class=\"main-logout-btn\" onclick=\"triggerLogout()\">\n          <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"\/><polyline points=\"16 17 21 12 16 7\"\/><line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"\/><\/svg>\n          D\u00e9connexion\n        <\/button>\n      <\/div>\n    <\/div>\n    <div class=\"main-content-area\">\n      <div class=\"page-pane active\" id=\"a-parents\">\n        <div class=\"page-title\">\n          <div><h4>Base de Donn\u00e9es Parents<\/h4><\/div>\n          <div style=\"display:flex;gap:10px;\">\n            <button class=\"btn btn-outline btn-lg\" onclick=\"exportParentsCsv()\" style=\"color:var(--blue-dark);border-color:var(--blue);\">\u2b07\ufe0f T\u00e9l\u00e9charger CSV<\/button>\n            <button class=\"btn btn-outline btn-lg\" onclick=\"openCsvImportModal()\" style=\"color:var(--blue-dark);border-color:var(--blue);\">\ud83d\udce5 Importer CSV<\/button>\n            <button class=\"btn btn-blue btn-lg\" onclick=\"openParentModal(0)\">+ Ajouter un Parent<\/button>\n          <\/div>\n        <\/div>\n        <div class=\"card\">\n          <div style=\"margin-bottom:16px;\">\n            <div style=\"position:relative;max-width:420px;\">\n              <span style=\"position:absolute;left:14px;top:50%;transform:translateY(-50%);font-size:15px;color:var(--muted);pointer-events:none;\">\ud83d\udd0d<\/span>\n              <input class=\"form-input\" id=\"parents-search-input\" type=\"text\" placeholder=\"Rechercher par nom, code parent ou email...\" oninput=\"filterParentsTable(this.value)\" style=\"padding-left:42px;height:44px;font-size:14px;\">\n            <\/div>\n          <\/div>\n          <div id=\"parents-pagination\" style=\"display:none;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:nowrap;gap:8px;overflow-x:auto;\">\n            <div id=\"parents-pagination-info\" style=\"font-size:13px;color:var(--muted);\"><\/div>\n            <div id=\"parents-pagination-btns\" style=\"display:flex;gap:6px;flex-wrap:nowrap;\"><\/div>\n          <\/div>\n          <div class=\"tbl-wrap\"><table class=\"tbl\"><colgroup><col style=\"width:18%\"><col style=\"width:18%\"><col style=\"width:22%\"><col style=\"width:20%\"><col style=\"width:22%\"><\/colgroup><thead><tr><th>Parent<\/th><th>Code Parent<\/th><th>Email<\/th><th>Enfants Rattach\u00e9s<\/th><th>Actions<\/th><\/tr><\/thead><tbody id=\"a-table-parents\"><\/tbody><\/table><\/div>\n          <div id=\"parents-search-empty\" style=\"display:none;text-align:center;padding:28px;color:var(--muted);font-size:14px;\">Aucun parent ne correspond \u00e0 la recherche.<\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"page-pane\" id=\"a-absences\">\n        <div class=\"g-devoirs\">\n          <!-- Colonne gauche : formulaire -->\n          <div class=\"card\">\n            <div class=\"card-header\"><h4>D\u00e9clarer une Absence<\/h4><\/div>\n            <div class=\"form-stack-full\">\n              <div class=\"form-group\">\n                <label class=\"form-label\">Recherche rapide (\u00e9l\u00e8ve ou classe)<\/label>\n                <div class=\"eleve-search-wrap\">\n                  <span class=\"eleve-search-icon\">\ud83d\udd0e<\/span>\n                  <input class=\"form-input\" id=\"abs-eleve-search\" type=\"text\" placeholder=\"Tapez un nom d'\u00e9l\u00e8ve ou une classe (ex: CM2)\u2026\" autocomplete=\"off\"\n                         oninput=\"filterAbsenceEleveSearch()\" onfocus=\"filterAbsenceEleveSearch()\" onkeydown=\"absEleveSearchKeydown(event)\">\n                  <div class=\"eleve-search-results\" id=\"abs-eleve-search-results\"><\/div>\n                <\/div>\n              <\/div>\n              <div class=\"form-group\">\n                <label class=\"form-label\">Niveau *<\/label>\n                <select class=\"form-select\" id=\"abs-niveau\" onchange=\"updateAbsenceClasseDropdown()\">\n                  <option value=\"\" disabled selected>-- Choisir le niveau --<\/option>\n                  <option value=\"PS\">PS<\/option><option value=\"MS\">MS<\/option><option value=\"GS\">GS<\/option>\n                  <option value=\"CP\">CP<\/option><option value=\"CE1\">CE1<\/option><option value=\"CE2\">CE2<\/option>\n                  <option value=\"CM1\">CM1<\/option><option value=\"CM2\">CM2<\/option><option value=\"6AP\">6AP<\/option>\n                <\/select>\n              <\/div>\n              <div class=\"form-group\">\n                <label class=\"form-label\">Classe *<\/label>\n                <select class=\"form-select\" id=\"abs-classe\" onchange=\"updateAbsencesStudentsDropdown()\" disabled>\n                  <option value=\"\" disabled selected>S\u00e9lectionnez d'abord un niveau<\/option>\n                <\/select>\n              <\/div>\n\n              <div class=\"form-group abs-mode-toggle-group\">\n                <label class=\"abs-mode-toggle\">\n                  <input type=\"checkbox\" id=\"abs-multi-toggle\" onchange=\"toggleAbsenceMultiMode()\">\n                  <span>Saisir plusieurs \u00e9l\u00e8ves de cette classe en une fois<\/span>\n                <\/label>\n              <\/div>\n\n              <div class=\"form-group\" id=\"abs-nom-single-wrap\">\n                <label class=\"form-label\">Nom de l'\u00c9l\u00e8ve *<\/label>\n                <select class=\"form-select\" id=\"abs-nom\" disabled>\n                  <option value=\"\" disabled selected>S\u00e9lectionnez d'abord une classe<\/option>\n                <\/select>\n              <\/div>\n\n              <div class=\"form-group\" id=\"abs-nom-multi-wrap\" style=\"display:none;\">\n                <label class=\"form-label\">\u00c9l\u00e8ves concern\u00e9s *<\/label>\n                <div class=\"abs-multi-actions\">\n                  <button type=\"button\" class=\"abs-multi-action-btn\" onclick=\"toggleAllAbsenceCheckboxes(true)\">Tout cocher<\/button>\n                  <button type=\"button\" class=\"abs-multi-action-btn\" onclick=\"toggleAllAbsenceCheckboxes(false)\">Tout d\u00e9cocher<\/button>\n                  <span id=\"abs-multi-count\" class=\"abs-multi-count\">0 s\u00e9lectionn\u00e9(s)<\/span>\n                <\/div>\n                <div class=\"abs-multi-list\" id=\"abs-multi-list\">\n                  <div class=\"eleve-search-empty\">S\u00e9lectionnez d'abord une classe<\/div>\n                <\/div>\n              <\/div>\n\n              <div class=\"form-group\">\n                <label class=\"form-label\">Date<\/label>\n                <input class=\"form-input date-left\" id=\"abs-date\" type=\"date\">\n              <\/div>\n              <div class=\"form-group\">\n                <label class=\"form-label\">P\u00e9riode<\/label>\n                <select class=\"form-select\" id=\"abs-periode\">\n                  <option value=\"matin\">Matin<\/option>\n                  <option value=\"apr\u00e8s-midi\">Apr\u00e8s-midi<\/option>\n                  <option value=\"journ\u00e9e\">Journ\u00e9e compl\u00e8te<\/option>\n                <\/select>\n              <\/div>\n              <div class=\"form-group\">\n                <label class=\"form-label\">Statut<\/label>\n                <select class=\"form-select\" id=\"abs-statut\">\n                  <option value=\"unjustified\">Non justifi\u00e9e<\/option>\n                  <option value=\"justified\">Justifi\u00e9e<\/option>\n                <\/select>\n              <\/div>\n              <div class=\"form-group\">\n                <label class=\"form-label\">Motif \/ Note interne<\/label>\n                <input class=\"form-input\" id=\"abs-motif\" type=\"text\" placeholder=\"Ex: Certificat m\u00e9dical d\u00e9pos\u00e9\">\n              <\/div>\n            <\/div>\n            <button class=\"btn btn-blue\" style=\"width:100%;margin-top:6px;\" id=\"abs-submit-btn\" onclick=\"publishAbsence()\">Enregistrer l'absence<\/button>\n          <\/div>\n\n          <!-- Colonne droite : historique -->\n          <div class=\"card\">\n            <div class=\"card-header\"><h4>Registre des Absences<\/h4><p>Historique des absences enregistr\u00e9es<\/p><\/div>\n            <div id=\"absences-pagination\" style=\"display:none;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:nowrap;gap:8px;overflow-x:auto;\">\n              <div id=\"absences-pagination-info\" style=\"font-size:13px;color:var(--muted);\"><\/div>\n              <div id=\"absences-pagination-btns\" style=\"display:flex;gap:6px;flex-wrap:nowrap;\"><\/div>\n            <\/div>\n            <div class=\"tbl-wrap\"><table class=\"tbl\"><colgroup><col style=\"width:18%\"><col style=\"width:16%\"><col style=\"width:20%\"><col style=\"width:16%\"><col style=\"width:16%\"><col style=\"width:14%\"><\/colgroup><thead><tr><th>\u00c9l\u00e8ve<\/th><th>Classe<\/th><th>Date<\/th><th>P\u00e9riode<\/th><th>Statut<\/th><th>Action<\/th><\/tr><\/thead><tbody id=\"a-table-absences\"><\/tbody><\/table><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"page-pane\" id=\"a-devoirs\">\n        <div class=\"g-devoirs\">\n          <!-- Colonne gauche : formulaire -->\n          <div class=\"card\">\n            <div class=\"card-header\"><h4>Publier un nouveau Devoir<\/h4><\/div>\n            <div class=\"form-stack-full\">\n              <div class=\"form-group\">\n                <label class=\"form-label\">Recherche rapide d'une classe<\/label>\n                <div class=\"eleve-search-wrap\">\n                  <span class=\"eleve-search-icon\">\ud83d\udd0e<\/span>\n                  <input class=\"form-input\" id=\"a-dev-classe-search\" type=\"text\" placeholder=\"Tapez le nom de la classe\u2026\" autocomplete=\"off\"\n                         oninput=\"filterClasseSearch('a-dev')\" onfocus=\"filterClasseSearch('a-dev')\" onkeydown=\"classeSearchKeydown(event,'a-dev')\">\n                  <div class=\"eleve-search-results\" id=\"a-dev-classe-search-results\"><\/div>\n                <\/div>\n              <\/div>\n              <div class=\"form-group\">\n                <label class=\"form-label\">Niveau *<\/label>\n                <select class=\"form-select\" id=\"a-dev-niveau\" onchange=\"updateADevoirClasseDropdown()\">\n                  <option value=\"\" disabled selected>-- Choisir le niveau --<\/option>\n                  <option value=\"PS\">PS<\/option><option value=\"MS\">MS<\/option><option value=\"GS\">GS<\/option>\n                  <option value=\"CP\">CP<\/option><option value=\"CE1\">CE1<\/option><option value=\"CE2\">CE2<\/option>\n                  <option value=\"CM1\">CM1<\/option><option value=\"CM2\">CM2<\/option><option value=\"6AP\">6AP<\/option>\n                <\/select>\n              <\/div>\n              <div class=\"form-group\">\n                <label class=\"form-label\">Classe *<\/label>\n                <select class=\"form-select\" id=\"a-dev-classe\" disabled>\n                  <option value=\"\" disabled selected>S\u00e9lectionnez d'abord un niveau<\/option>\n                <\/select>\n              <\/div>\n              <div class=\"form-group\">\n                <label class=\"form-label\">Mati\u00e8re *<\/label>\n                <select class=\"form-select\" id=\"a-dev-matiere\">\n                  <option value=\"\" disabled selected>-- Choisir la mati\u00e8re --<\/option>\n                <\/select>\n              <\/div>\n            <\/div>\n            <div class=\"form-group\" id=\"a-dev-autre-wrap\" style=\"display:none;\">\n              <label class=\"form-label\">Pr\u00e9cisez la mati\u00e8re *<\/label>\n              <input class=\"form-input\" id=\"a-dev-matiere-autre\" type=\"text\" placeholder=\"Ex: Sciences, Anglais\u2026\">\n            <\/div>\n            <div class=\"form-group\">\n              <label class=\"form-label\" style=\"display:flex;align-items:center;gap:8px;cursor:pointer;\">\n                <input type=\"checkbox\" id=\"a-dev-all-classes\" style=\"width:16px;height:16px;accent-color:var(--blue);flex-shrink:0;\" onchange=\"toggleADevAllClasses()\">\n                <span>Envoyer \u00e0 toutes les classes de ce niveau<\/span>\n              <\/label>\n            <\/div>\n            <div class=\"form-group\">\n              <label class=\"form-label\">Titre du devoir *<\/label>\n              <input class=\"form-input\" id=\"a-dev-titre\" type=\"text\" value=\"Devoir maison\" placeholder=\"Ex: Lire la le\u00e7on G12\">\n            <\/div>\n            <div class=\"form-group\">\n              <label class=\"form-label\">Fichier joint <span style=\"font-weight:400;color:var(--muted);\">(Image, TXT ou PDF - Max 2 Mo)<\/span><\/label>\n              <input class=\"form-input\" id=\"a-dev-fichier\" type=\"file\" accept=\".jpg,.jpeg,.png,.txt,.pdf\" style=\"padding:10px;\">\n            <\/div>\n            <div class=\"form-group\">\n              <label class=\"form-label\">Description \/ Consignes<\/label>\n              <textarea class=\"form-textarea\" id=\"a-dev-desc\" placeholder=\"D\u00e9tails du travail \u00e0 faire...\"><\/textarea>\n            <\/div>\n            <div class=\"form-group\">\n              <label class=\"form-label\">Date de remise requise<\/label>\n              <input class=\"form-input date-left\" id=\"a-dev-date\" type=\"date\">\n            <\/div>\n            <button class=\"btn btn-blue\" style=\"width:100%;margin-top:6px;background:linear-gradient(135deg,#10B981,#059669);\" onclick=\"publishADevoir()\">\ud83d\udce4 Diffuser le devoir<\/button>\n          <\/div>\n\n          <!-- Colonne droite : historique -->\n          <div class=\"card\">\n            <div class=\"card-header\"><h4>Historique du Cahier de Textes<\/h4><p>Devoirs publi\u00e9s par l'administration et les enseignants<\/p><\/div>\n            <div id=\"devoirs-pagination\" style=\"display:none;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:nowrap;gap:8px;overflow-x:auto;\">\n              <div id=\"devoirs-pagination-info\" style=\"font-size:13px;color:var(--muted);\"><\/div>\n              <div id=\"devoirs-pagination-btns\" style=\"display:flex;gap:6px;flex-wrap:nowrap;\"><\/div>\n            <\/div>\n            <div class=\"tbl-wrap\">\n              <table class=\"tbl\">\n                <colgroup><col style=\"width:8%\"><col style=\"width:14%\"><col style=\"width:23%\"><col style=\"width:20%\"><col style=\"width:14%\"><col style=\"width:13%\"><col style=\"width:8%\"><\/colgroup>\n                <thead><tr><th>Classe<\/th><th>Mati\u00e8re<\/th><th>Titre<\/th><th>Publi\u00e9 par<\/th><th>Date Butoir<\/th><th>Document<\/th><th>Action<\/th><\/tr><\/thead>\n                <tbody id=\"a-table-devoirs\"><\/tbody>\n              <\/table>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"page-pane\" id=\"a-annonces\">\n        <div class=\"g-devoirs\">\n          <!-- Colonne gauche : formulaire -->\n          <div class=\"card\">\n            <div class=\"card-header\"><h4>R\u00e9diger un communiqu\u00e9<\/h4><\/div>\n            <div class=\"form-group\"><label class=\"form-label\">Titre de l'annonce *<\/label><input class=\"form-input\" id=\"ann-titre\" type=\"text\" placeholder=\"Objet officiel du message...\"><\/div>\n            <div class=\"form-group\"><label class=\"form-label\">Niveau d'urgence<\/label><select class=\"form-select\" id=\"ann-priorite\"><option value=\"normale\">Normale (Bleu)<\/option><option value=\"haute\">Haute \/ Urgente (Rouge)<\/option><\/select><\/div>\n            <div class=\"form-group\">\n              <label class=\"form-label\">Fichier joint <span style=\"font-weight:400;color:var(--muted);\">(Image, TXT ou PDF - Max 2 Mo)<\/span><\/label>\n              <input class=\"form-input\" id=\"ann-fichier\" type=\"file\" accept=\".jpg,.jpeg,.png,.txt,.pdf\" style=\"padding:10px;\">\n            <\/div>\n            <div class=\"form-group\"><label class=\"form-label\">Corps du message<\/label><textarea class=\"form-textarea\" id=\"ann-contenu\" style=\"min-height:120px;\" placeholder=\"\u00c9crivez le contenu ici...\"><\/textarea><\/div>\n            <button class=\"btn btn-blue\" style=\"width:100%;margin-top:6px;\" onclick=\"publishAnnonce()\">Lancer la publication<\/button>\n          <\/div>\n\n          <!-- Colonne droite : historique -->\n          <div class=\"card\">\n            <div class=\"card-header\"><h4>Registre des Annonces Diffus\u00e9es<\/h4><p>Annonces publi\u00e9es par l'administration<\/p><\/div>\n            <div id=\"annonces-pagination\" style=\"display:none;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:nowrap;gap:8px;overflow-x:auto;\">\n              <div id=\"annonces-pagination-info\" style=\"font-size:13px;color:var(--muted);\"><\/div>\n              <div id=\"annonces-pagination-btns\" style=\"display:flex;gap:6px;flex-wrap:nowrap;\"><\/div>\n            <\/div>\n            <div class=\"tbl-wrap\"><table class=\"tbl\"><colgroup><col style=\"width:65%\"><col style=\"width:20%\"><col style=\"width:15%\"><\/colgroup><thead><tr><th>Titre<\/th><th>Priorit\u00e9<\/th><th>Action<\/th><\/tr><\/thead><tbody id=\"a-table-annonces\"><\/tbody><\/table><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <!-- \u2500\u2500 Onglet Enseignants \u2500\u2500 -->\n      <div class=\"page-pane\" id=\"a-enseignants\">\n        <div class=\"page-title\">\n          <div><h4>Corps Enseignant<\/h4><\/div>\n          <div style=\"display:flex;gap:10px;\">\n            <button class=\"btn btn-outline btn-lg\" onclick=\"exportEnseignantsCsv()\" style=\"color:var(--purple);border-color:var(--purple);\">\u2b07\ufe0f T\u00e9l\u00e9charger CSV<\/button>\n            <button class=\"btn btn-outline btn-lg\" onclick=\"openEnsImportModal()\" style=\"color:var(--purple);border-color:var(--purple);\">\ud83d\udce5 Importer CSV<\/button>\n            <button class=\"btn btn-lg\" style=\"background:linear-gradient(135deg,var(--purple),#5B21B6);color:#fff;box-shadow:0 4px 14px rgba(124,58,237,.25);\" onclick=\"openEnsModal(0)\">+ Ajouter un Enseignant<\/button>\n          <\/div>\n        <\/div>\n        <div class=\"card\">\n          <div style=\"margin-bottom:16px;\">\n            <div style=\"position:relative;max-width:420px;\">\n              <span style=\"position:absolute;left:14px;top:50%;transform:translateY(-50%);font-size:15px;color:var(--muted);pointer-events:none;\">\ud83d\udd0d<\/span>\n              <input class=\"form-input\" id=\"ens-search-input\" type=\"text\" placeholder=\"Rechercher par nom, code, mati\u00e8re ou classe...\" oninput=\"filterEnseignantsTable(this.value)\" style=\"padding-left:42px;height:44px;font-size:14px;\">\n            <\/div>\n          <\/div>\n          <div id=\"ens-pagination\" style=\"display:none;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:nowrap;gap:8px;overflow-x:auto;\">\n            <div id=\"ens-pagination-info\" style=\"font-size:13px;color:var(--muted);\"><\/div>\n            <div id=\"ens-pagination-btns\" style=\"display:flex;gap:6px;flex-wrap:nowrap;\"><\/div>\n          <\/div>\n          <div class=\"tbl-wrap\">\n            <table class=\"tbl\">\n              <colgroup><col style=\"width:16%\"><col style=\"width:14%\"><col style=\"width:25%\"><col style=\"width:27%\"><col style=\"width:18%\"><\/colgroup>\n              <thead><tr><th>Enseignant<\/th><th>Code<\/th><th>Mati\u00e8re(s)<\/th><th>Classe(s) assign\u00e9e(s)<\/th><th>Actions<\/th><\/tr><\/thead>\n              <tbody id=\"a-table-enseignants\"><\/tbody>\n            <\/table>\n          <\/div>\n          <div id=\"ens-search-empty\" style=\"display:none;text-align:center;padding:28px;color:var(--muted);font-size:14px;\">Aucun enseignant ne correspond \u00e0 la recherche.<\/div>\n        <\/div>\n      <\/div>\n\n    <\/div>\n  <\/div>\n  <!-- \u2550\u2550 \/Workspace Admin \u2550\u2550 -->\n  <!-- \u2550\u2550 Workspace d\u00e9di\u00e9 Enseignant \u2550\u2550 -->\n  <div class=\"teacher-workspace\" id=\"ouzoud-teacher-app\">\n    <div class=\"teacher-topbar\">\n      <div class=\"teacher-topbar-brand\">\n        <div style=\"width:36px;height:36px;border-radius:10px;background:#10B981;border:1px solid #E5E7EB;display:flex;align-items:center;justify-content:center;\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z\"\/><path d=\"M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z\"\/><\/svg><\/div>\n        <div>\n          <div class=\"t-teacher-name-row\">\n            <div class=\"t-brand-name\">ESPACE ENSEIGNANT<\/div>\n            <div class=\"t-teacher-name\" id=\"teacher-name-bar\"><\/div>\n            <div class=\"t-teacher-classes\" id=\"teacher-classes-bar\"><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n      <div class=\"teacher-topbar-right\">\n        <div class=\"teacher-topbar-date\" id=\"teacher-date-bar\">...<\/div>\n        <button class=\"teacher-logout-btn\" onclick=\"triggerLogout()\">\n          <span>\u21a9<\/span> D\u00e9connexion\n        <\/button>\n      <\/div>\n    <\/div>\n\n    <div class=\"teacher-content\">\n      <!-- Colonne gauche : formulaire -->\n      <div class=\"card\">\n        <div class=\"card-header\"><h4>Publier un nouveau Devoir<\/h4><\/div>\n        <div class=\"form-stack-full\">\n          <div class=\"form-group\">\n            <label class=\"form-label\">Recherche rapide d'une classe<\/label>\n            <div class=\"eleve-search-wrap\">\n              <span class=\"eleve-search-icon\">\ud83d\udd0e<\/span>\n              <input class=\"form-input\" id=\"t-dev-classe-search\" type=\"text\" placeholder=\"Tapez le nom de la classe\u2026\" autocomplete=\"off\"\n                     oninput=\"filterClasseSearch('t-dev')\" onfocus=\"filterClasseSearch('t-dev')\" onkeydown=\"classeSearchKeydown(event,'t-dev')\">\n              <div class=\"eleve-search-results\" id=\"t-dev-classe-search-results\"><\/div>\n            <\/div>\n          <\/div>\n          <div class=\"form-group\">\n            <label class=\"form-label\">Niveau *<\/label>\n            <select class=\"form-select\" id=\"t-dev-niveau\" onchange=\"updateTDevoirClasseDropdown()\">\n              <option value=\"\" disabled selected>-- Choisir le niveau --<\/option>\n            <\/select>\n          <\/div>\n          <div class=\"form-group\">\n            <label class=\"form-label\">Classe *<\/label>\n            <select class=\"form-select\" id=\"t-dev-classe\" disabled>\n              <option value=\"\" disabled selected>S\u00e9lectionnez d'abord un niveau<\/option>\n            <\/select>\n          <\/div>\n          <div class=\"form-group\">\n            <label class=\"form-label\">Mati\u00e8re *<\/label>\n            <select class=\"form-select\" id=\"t-dev-matiere\">\n              <option value=\"\" disabled selected>-- Choisir la mati\u00e8re --<\/option>\n            <\/select>\n          <\/div>\n        <\/div>\n        <div class=\"form-group\" id=\"t-dev-autre-wrap\" style=\"display:none;\">\n          <label class=\"form-label\">Pr\u00e9cisez la mati\u00e8re *<\/label>\n          <input class=\"form-input\" id=\"t-dev-matiere-autre\" type=\"text\" placeholder=\"Ex: Sciences, Anglais\u2026\">\n        <\/div>\n        <div class=\"form-group\">\n          <label class=\"form-label\" style=\"display:flex;align-items:center;gap:8px;cursor:pointer;\">\n            <input type=\"checkbox\" id=\"t-dev-all-classes\" style=\"width:16px;height:16px;accent-color:var(--blue);flex-shrink:0;\" onchange=\"toggleTDevAllClasses()\">\n            <span>Envoyer \u00e0 toutes les classes de ce niveau<\/span>\n          <\/label>\n        <\/div>\n        <div class=\"form-group\">\n          <label class=\"form-label\">Titre du devoir *<\/label>\n          <input class=\"form-input\" id=\"t-dev-titre\" type=\"text\" value=\"Devoir maison\" placeholder=\"Ex: Lire la le\u00e7on G12\">\n        <\/div>\n        <div class=\"form-group\">\n          <label class=\"form-label\">Fichier joint <span style=\"font-weight:400;color:var(--muted);\">(Image, TXT ou PDF - Max 2 Mo)<\/span><\/label>\n          <input class=\"form-input\" id=\"t-dev-fichier\" type=\"file\" accept=\".jpg,.jpeg,.png,.txt,.pdf\" style=\"padding:10px;\">\n        <\/div>\n        <div class=\"form-group\">\n          <label class=\"form-label\">Description \/ Consignes<\/label>\n          <textarea class=\"form-textarea\" id=\"t-dev-desc\" placeholder=\"D\u00e9tails du travail \u00e0 faire...\"><\/textarea>\n        <\/div>\n        <div class=\"form-group\">\n          <label class=\"form-label\">Date de remise requise<\/label>\n          <input class=\"form-input date-left\" id=\"t-dev-date\" type=\"date\">\n        <\/div>\n        <button class=\"btn btn-blue\" style=\"width:100%;margin-top:6px;background:linear-gradient(135deg,#10B981,#059669);\" onclick=\"publishTDevoir()\">\ud83d\udce4 Diffuser le devoir<\/button>\n      <\/div>\n\n      <!-- Colonne droite : historique -->\n      <div class=\"card\">\n        <div class=\"card-header\"><h4>Historique du Cahier de Textes<\/h4><p>Devoirs publi\u00e9s par l'administration et les enseignants<\/p><\/div>\n        <div id=\"t-devoirs-pagination\" style=\"display:none;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:nowrap;gap:8px;overflow-x:auto;\">\n          <div id=\"t-devoirs-pagination-info\" style=\"font-size:13px;color:var(--muted);\"><\/div>\n          <div id=\"t-devoirs-pagination-btns\" style=\"display:flex;gap:6px;flex-wrap:nowrap;\"><\/div>\n        <\/div>\n        <div class=\"tbl-wrap\">\n          <table class=\"tbl\">\n            <colgroup><col style=\"width:8%\"><col style=\"width:14%\"><col style=\"width:23%\"><col style=\"width:20%\"><col style=\"width:14%\"><col style=\"width:13%\"><col style=\"width:8%\"><\/colgroup>\n            <thead><tr><th>Classe<\/th><th>Mati\u00e8re<\/th><th>Titre<\/th><th>Publi\u00e9 par<\/th><th>Date Butoir<\/th><th>Document<\/th><th>Action<\/th><\/tr><\/thead>\n            <tbody id=\"t-table-devoirs\"><\/tbody>\n          <\/table>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n  <!-- \u2550\u2550 \/Workspace Enseignant \u2550\u2550 -->\n\n  <div class=\"ouzoud-toast-wrap\" id=\"ouzoud-toast-wrap\"><\/div>\n\n<\/div>\n\n<script>\n\/\/ \u2500\u2500 \u00c9chappement HTML \u2014 protection XSS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction escHtml(str) {\n    if (str === null || str === undefined) return '';\n    return String(str)\n        .replace(\/&\/g, '&amp;')\n        .replace(\/<\/g, '&lt;')\n        .replace(\/>\/g, '&gt;')\n        .replace(\/\"\/g, '&quot;')\n        .replace(\/'\/g, '&#39;');\n}\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\/\/ [FIX#4 v4.3] : le nonce ne doit plus appara\u00eetre dans les URLs de fichiers (logs serveur,\n\/\/ Referer, historique navigateur). On obtient d'abord un token \u00e9ph\u00e9m\u00e8re (valable 60s) via POST,\n\/\/ puis on charge le fichier via GET avec ce token \u2014 le nonce reste dans le body POST uniquement.\n\/\/ Toutes les fonctions qui affichent des liens vers des fichiers sont d\u00e9sormais asynchrones.\nasync function buildSecureFileUrl(rawUrl) {\n    if (!rawUrl) return '';\n    let p = new URLSearchParams();\n    p.append('action', 'ouzoud_ajax_get_file_token');\n    p.append('nonce',  OuzoudAjax.nonce);\n    p.append('file',   rawUrl);\n    try {\n        const res = await fetch(OuzoudAjax.url, { method: 'POST', body: p });\n        const json = await res.json();\n        if (json.success && json.data && json.data.token) {\n            return OuzoudAjax.url\n                + '?action=ouzoud_serve_file'\n                + '&token=' + encodeURIComponent(json.data.token);\n        }\n    } catch(e) { \/* silencieux \u2014 le lien sera vide *\/ }\n    return '';\n}\n\nlet ouzoudSessionData = null;\nlet currentProfileType = 'parent';\n\n\/\/ [FIX#8 v4.2] : renouveler le nonce toutes les 10h. En cas d'\u00e9chec,\n\/\/ on notifie l'utilisateur plut\u00f4t que d'avaler l'erreur silencieusement \u2014\n\/\/ sans nonce valide, toutes les actions AJAX \u00e9choueraient sans explication.\n(function ouzoudScheduleNonceRenewal() {\n    setInterval(function() {\n        let p = new URLSearchParams();\n        p.append('action', 'ouzoud_ajax_refresh_nonce');\n        fetch(OuzoudAjax.url, { method: 'POST', body: p })\n            .then(r => r.json())\n            .then(res => {\n                if (res.success && res.data && res.data.nonce) {\n                    OuzoudAjax.nonce = res.data.nonce;\n                } else {\n                    \/\/ Nonce non renouvel\u00e9 (session expir\u00e9e c\u00f4t\u00e9 serveur) \u2192 d\u00e9connexion propre\n                    showToast('Votre session a expir\u00e9. Veuillez vous reconnecter.', 'info', 6000);\n                    setTimeout(() => triggerLogout(), 2000);\n                }\n            })\n            .catch(() => {\n                showToast('Connexion interrompue. V\u00e9rifiez votre r\u00e9seau \u2014 la page n\u00e9cessite un rechargement.', 'error', 8000);\n            });\n    }, 10 * 60 * 60 * 1000); \/\/ 10 heures\n})();\n\n\/\/ \u2500\u2500 Toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction showToast(message, type = 'success', duration = 3200) {\n    const icons = { success: '\u2705', error: '\u274c', info: '\u2139\ufe0f' };\n    const wrap = document.getElementById('ouzoud-toast-wrap');\n    const toast = document.createElement('div');\n    toast.className = `ouzoud-toast ${type}`;\n    toast.innerHTML = `<span class=\"ouzoud-toast-icon\">${icons[type] || '\ud83d\udcac'}<\/span><span>${escHtml(message)}<\/span>`;\n    wrap.appendChild(toast);\n    requestAnimationFrame(() => { requestAnimationFrame(() => { toast.classList.add('show'); }); });\n    setTimeout(() => {\n        toast.classList.remove('show');\n        setTimeout(() => toast.remove(), 280);\n    }, duration);\n}\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction formatToFrenchDate(inputDate) {\n    if (!inputDate) return '-';\n    let parts = inputDate.split('-');\n    if (parts.length === 3) {\n        return `${parts[2]}\/${parts[1]}\/${parts[0]}`;\n    }\n    return inputDate;\n}\n\nfunction getTomorrowSQLDate() {\n    let tomorrow = new Date();\n    tomorrow.setDate(tomorrow.getDate() + 1);\n    let tomDay = String(tomorrow.getDate()).padStart(2, '0');\n    let tomMonth = String(tomorrow.getMonth() + 1).padStart(2, '0');\n    let tomYear = tomorrow.getFullYear();\n    return `${tomYear}-${tomMonth}-${tomDay}`;\n}\n\n\/\/ [FIX#1] : date d'aujourd'hui en format SQL pour r\u00e9initialisation du formulaire d'absence\nfunction getTodaySQLDate() {\n    let today = new Date();\n    return `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}-${String(today.getDate()).padStart(2,'0')}`;\n}\n\n\/\/ [FIX#9] : validation du format OUZ-YYYY-XXXX \u2014 d\u00e9clench\u00e9e \u00e0 la perte de focus\nfunction validateParentCodeFormat(input) {\n    const val = input.value.trim().toUpperCase();\n    const valid = \/^OUZ-\\d{4}-\\d{4}$\/.test(val);\n    if (val.length > 0 && !valid) {\n        input.style.borderColor = 'var(--amber)';\n    } else {\n        input.style.borderColor = valid ? 'var(--blue)' : '';\n    }\n}\n\/\/ R\u00e9initialise un champ password et remet l'\u0153il en mode masqu\u00e9\nfunction clearPasswordField(fieldId) {\n    const f = document.getElementById(fieldId);\n    if (!f) return;\n    f.value = '';\n    f.style.borderColor = '';\n    if (f.type === 'text') {\n        f.type = 'password';\n        const btn = f.parentElement && f.parentElement.querySelector('.lf-eye');\n        if (btn) btn.innerHTML = eyeOpenSVG();\n    }\n}\nfunction clearParentField() { clearPasswordField('parent-cin-field'); }\nfunction clearAdminField()  { clearPasswordField('admin-password-field'); }\n\/\/ SVG \u0153il ouvert (mot de passe masqu\u00e9) et \u0153il barr\u00e9 (mot de passe visible)\nfunction eyeOpenSVG() {\n    return '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"\/><circle cx=\"12\" cy=\"12\" r=\"3\"\/><\/svg>';\n}\nfunction eyeClosedSVG() {\n    return '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94\"\/><path d=\"M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19\"\/><line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"\/><\/svg>';\n}\nfunction _buildMatiereOptions(selectEl) {\n    const matieres = (ouzoudSessionData && Array.isArray(ouzoudSessionData.matieres_config) && ouzoudSessionData.matieres_config.length > 0)\n        ? ouzoudSessionData.matieres_config\n        : ['Arabe', 'Fran\u00e7ais', 'Maths'];\n    selectEl.innerHTML = '<option value=\"\" disabled selected>-- Choisir la mati\u00e8re --<\/option>';\n    matieres.forEach(m => {\n        const opt = document.createElement('option');\n        opt.value = m; opt.textContent = m;\n        selectEl.appendChild(opt);\n    });\n}\nfunction populateAllMatiereSelects() {\n    const admin = document.getElementById('a-dev-matiere');\n    if (admin) _buildMatiereOptions(admin);\n    const teacher = document.getElementById('t-dev-matiere');\n    if (teacher) _buildMatiereOptions(teacher);\n}\n\nfunction toggleFieldVisibility(fieldId, btn) {\n    const f = document.getElementById(fieldId);\n    if (f.type === 'password') {\n        f.type = 'text';\n        btn.innerHTML = eyeClosedSVG();\n    } else {\n        f.type = 'password';\n        btn.innerHTML = eyeOpenSVG();\n    }\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", function() {\n    let d = new Date();\n    let day = String(d.getDate()).padStart(2, '0');\n    let month = String(d.getMonth() + 1).padStart(2, '0');\n    let year = d.getFullYear();\n\n    \/\/ Indicateur de scroll horizontal sur les tbl-wrap\n    function updateScrollIndicators() {\n      document.querySelectorAll('.tbl-wrap').forEach(w => {\n        w.classList.toggle('can-scroll', w.scrollWidth > w.clientWidth + 2);\n      });\n      \/\/ [FIX#16 v4.6] : m\u00eame indicateur pour la navigation horizontale du bandeau\n      \/\/ (utile sur tablette\/t\u00e9l\u00e9phone quand les onglets d\u00e9passent la largeur visible).\n      document.querySelectorAll('.main-topbar-nav').forEach(w => {\n        w.classList.toggle('can-scroll', w.scrollWidth > w.clientWidth + 2);\n      });\n    }\n    updateScrollIndicators();\n    window.addEventListener('resize', updateScrollIndicators);\n    document.addEventListener('click', updateScrollIndicators);\n\n    let todaySQL = `${year}-${month}-${day}`;\n    if(document.getElementById('abs-date')) document.getElementById('abs-date').value = todaySQL;\n    if(document.getElementById('abs-classe')) document.getElementById('abs-classe').disabled = true;\n\n    setTimeout(function() {\n        clearParentField();\n        clearAdminField();\n    }, 50);\n});\n\nfunction toggleLoginTab(type) {\n    currentProfileType = type;\n    document.getElementById('btn-tab-p').classList.toggle('active', type === 'parent');\n    document.getElementById('btn-tab-a').classList.toggle('active', type === 'admin');\n    document.getElementById('btn-tab-t').classList.toggle('active', type === 'teacher');\n    document.getElementById('panel-parent').style.display  = type === 'parent'  ? 'block' : 'none';\n    document.getElementById('panel-admin').style.display   = type === 'admin'   ? 'block' : 'none';\n    document.getElementById('panel-teacher').style.display = type === 'teacher' ? 'block' : 'none';\n    clearParentField();\n    clearAdminField();\n    clearTeacherField();\n}\n\nfunction clearTeacherField() {\n    const f = document.getElementById('teacher-code-field');\n    if (f) f.value = '';\n}\n\nfunction executeLogin(type) {\n    let p = new URLSearchParams();\n    p.append('action', 'ouzoud_ajax_login');\n    p.append('nonce', OuzoudAjax.nonce);\n    p.append('login_type', type);\n\n    if(type === 'admin') {\n        p.append('password', document.getElementById('admin-password-field').value);\n    } else if(type === 'teacher') {\n        const codeT = document.getElementById('teacher-code-field').value.trim().toUpperCase();\n        if (!\/^ENS-\\d{4}-\\d{4}$\/.test(codeT)) {\n            showToast('Cl\u00e9 enseignant invalide.', 'error');\n            return;\n        }\n        p.append('code_enseignant', codeT);\n    } else {\n        const codeVal = document.getElementById('parent-cin-field').value.trim().toUpperCase();\n        \/\/ [FIX#9] : valider le format avant envoi \u2014 \u00e9vite un aller-retour inutile\n        if (!\/^OUZ-\\d{4}-\\d{4}$\/.test(codeVal)) {\n            showToast('Cl\u00e9 parent invalide.', 'error');\n            return;\n        }\n        p.append('code_parent', codeVal);\n    }\n\n    fetch(OuzoudAjax.url, { method: 'POST', body: p })\n    .then(r => r.json())\n    .then(res => {\n        if(res.success) {\n            ouzoudSessionData = res.data;\n            ouzoudSessionData._loginType = type;\n            \/\/ Effacer imm\u00e9diatement les champs du formulaire apr\u00e8s connexion\n            clearAdminField();\n            clearParentField();\n            clearTeacherField();\n            setupApplicationWorkspace();\n        } else {\n            showToast(res.data || \"Identifiant ou cl\u00e9 incorrecte.\", 'error');\n            if(type === 'admin') clearAdminField();\n            else if(type === 'teacher') clearTeacherField();\n            else clearParentField();\n        }\n    })\n    \/\/ [FIX#11] : gestion des erreurs r\u00e9seau sur la connexion\n    .catch(() => {\n        showToast(\"Erreur r\u00e9seau. V\u00e9rifiez votre connexion et r\u00e9essayez.\", 'error');\n    });\n}\n\nfunction setupApplicationWorkspace() {\n    document.getElementById('ouzoud-login').style.display = 'none';\n\n    let role = ouzoudSessionData.role;\n    let isA = (role === 'admin');\n    let isT = (role === 'teacher');\n    const now = new Date();\n    const dateStr = now.toLocaleDateString('fr-FR', {weekday:'long', day:'numeric', month:'long', year:'numeric'});\n\n    if (isT) {\n        document.getElementById('ouzoud-teacher-app').classList.add('active');\n        document.getElementById('teacher-date-bar').innerText = dateStr;\n        const nameBar = document.getElementById('teacher-name-bar');\n        if (nameBar) {\n            const nom = ouzoudSessionData.name || '';\n            nameBar.innerText = nom;\n        }\n        const classesBar = document.getElementById('teacher-classes-bar');\n        if (classesBar) {\n            const classes = (ouzoudSessionData && Array.isArray(ouzoudSessionData.classes) && ouzoudSessionData.classes.length > 0)\n                ? ouzoudSessionData.classes\n                : [];\n            if (classes.length > 0) {\n                classesBar.innerHTML = classes.map(c => `<span class=\"t-class-chip\">${escHtml(c.trim())}<\/span>`).join('');\n            } else {\n                classesBar.innerHTML = '';\n            }\n        }\n        updateTDevoirMatiereDropdown();\n        updateTDevoirClasseDropdown();\n        document.getElementById('t-dev-date').value = getTomorrowSQLDate();\n        loadApplicationWorkspace();\n    } else if (isA) {\n        document.getElementById('ouzoud-admin-app').classList.add('active');\n        document.getElementById('a-date-bar').innerText = dateStr;\n        document.getElementById('a-user-name').innerText = ouzoudSessionData.name || 'Administration';\n        updateADevoirClasseDropdown();\n        document.getElementById('a-dev-date').value = getTomorrowSQLDate();\n        loadApplicationWorkspace();\n    } else {\n        document.getElementById('ouzoud-parent-app').classList.add('active');\n        document.getElementById('p-date-bar').innerText = dateStr;\n        document.getElementById('p-user-name').innerText = ouzoudSessionData.name || '';\n        loadApplicationWorkspace();\n    }\n}\n\nfunction switchMainPane(targetId, role, btn) {\n    \/\/ D\u00e9sactiver tous les panes dans le bon workspace\n    const workspace = role === 'admin' ? document.getElementById('ouzoud-admin-app') : document.getElementById('ouzoud-parent-app');\n    workspace.querySelectorAll('.page-pane').forEach(p => p.classList.remove('active'));\n    workspace.querySelectorAll('.main-nav-btn').forEach(b => b.classList.remove('active'));\n    document.getElementById(targetId).classList.add('active');\n    btn.classList.add('active');\n}\n\nfunction navigateOuzoud(targetId, btn) {\n    \/\/ Conserv\u00e9 pour compatibilit\u00e9 avec d'\u00e9ventuels appels r\u00e9siduels\n    document.querySelectorAll('.page-pane').forEach(p => p.classList.remove('active'));\n    document.querySelectorAll('.main-nav-btn').forEach(l => l.classList.remove('active'));\n    const el = document.getElementById(targetId);\n    if (el) el.classList.add('active');\n    if (btn) btn.classList.add('active');\n}\n\nfunction loadApplicationWorkspace() {\n    let p = new URLSearchParams();\n    p.append('action', 'ouzoud_ajax_refresh_data');\n    p.append('nonce', OuzoudAjax.nonce);\n\n    fetch(OuzoudAjax.url, { method: 'POST', body: p })\n    .then(r => r.json())\n    .then(res => {\n        if(res.success) {\n            \/\/ [FIX#14 v4.5] : isoler les erreurs d'affichage des erreurs r\u00e9seau.\n            \/\/ Avant ce correctif, une exception JS lev\u00e9e pendant le rendu (ex: \u00e9l\u00e9ment\n            \/\/ DOM manquant) \u00e9tait captur\u00e9e par le .catch() r\u00e9seau ci-dessous et affichait\n            \/\/ \u00e0 tort \"Erreur r\u00e9seau\" alors que les donn\u00e9es avaient bien \u00e9t\u00e9 re\u00e7ues.\n            try {\n                let savedMeta = {\n                    _loginType: ouzoudSessionData ? ouzoudSessionData._loginType : null\n                };\n                ouzoudSessionData = res.data;\n                Object.assign(ouzoudSessionData, savedMeta);\n                let r = ouzoudSessionData.role;\n                if(r === 'admin') renderAdminPanels();\n                else if(r === 'teacher') renderTeacherPanels();\n                else renderParentPanels();\n            } catch (renderErr) {\n                console.error('[Ouzoud] Erreur lors de l\\'affichage des donn\u00e9es re\u00e7ues :', renderErr);\n                showToast(\"Les donn\u00e9es ont \u00e9t\u00e9 re\u00e7ues mais n'ont pas pu \u00eatre affich\u00e9es. Veuillez recharger la page.\", 'error');\n            }\n        } else {\n            \/\/ Session expir\u00e9e \u2014 retour \u00e0 l'\u00e9cran de connexion\n            triggerLogout();\n            showToast('Session expir\u00e9e. Veuillez vous reconnecter.', 'info');\n        }\n    })\n    \/\/ [FIX#11] : gestion des erreurs r\u00e9seau \u2014 \u00e9vite un \u00e9cran blanc silencieux\n    .catch((err) => {\n        console.error('[Ouzoud] Erreur r\u00e9seau lors du rafra\u00eechissement des donn\u00e9es :', err);\n        showToast(\"Erreur r\u00e9seau. Les donn\u00e9es n'ont pas pu \u00eatre charg\u00e9es.\", 'error');\n    });\n}\n\nfunction renderParentPanels() {\n    document.getElementById('dash-kids-count').innerText = ouzoudSessionData.enfants.length;\n    document.getElementById('dash-abs-count').innerText = ouzoudSessionData.absences.length;\n    document.getElementById('dash-devoirs-count').innerText = ouzoudSessionData.devoirs.length;\n    document.getElementById('dash-annonces-count').innerText = ouzoudSessionData.annonces.length;\n\n    let kHtml = '';\n    ouzoudSessionData.enfants.forEach(k => { kHtml += `<tr><td data-label=\"Pr\u00e9nom &amp; Nom\"><b>${escHtml(k.nom_enfant)}<\/b><\/td><td data-label=\"Classe\"><span class=\"chip chip-blue\">${escHtml(k.classe)}<\/span><\/td><\/tr>`; });\n    document.getElementById('p-dash-kids-table').innerHTML = kHtml;\n\n    let dAnnHtml = '';\n    ouzoudSessionData.annonces.slice(0, 2).forEach(a => {\n        let fileButton = '';\n        if (a.fichier_joint) {\n            \/\/ [FIX#4 v4.3] : le token est r\u00e9solu au clic (async) \u2014 pas dans l'URL statique\n            fileButton = `<div style=\"margin-top:8px;\"><button class=\"btn btn-outline btn-sm ouzoud-file-btn\" data-fileurl=\"${escHtml(a.fichier_joint)}\" style=\"height:26px; padding:2px 8px; font-size:11px;\">\ud83d\udcc4 Ouvrir le document<\/button><\/div>`;\n        }\n        dAnnHtml += `<div class=\"ann-card ${escHtml(a.priorite==='haute'?'urgent':'')}\"><h5>${escHtml(a.titre)}<\/h5><p style=\"font-size:12.5px;color:var(--text-2);margin-top:4px;\">${escHtml(a.contenu)}<\/p>${fileButton}<\/div>`;\n    });\n    document.getElementById('p-dash-ann-list').innerHTML = dAnnHtml || '<p style=\"font-size:13px;color:var(--muted);\">Aucune annonce r\u00e9cente.<\/p>';\n\n    \/\/ R\u00e9initialiser les pages et d\u00e9clencher le rendu pagin\u00e9\n    _pAbsCurrentPage = 1;\n    _pDevCurrentPage = 1;\n    _pAnnCurrentPage = 1;\n    _pAbsRenderPage(1);\n    _pDevRenderPage(1);\n    _pAnnRenderPage(1);\n}\n\n\/\/ \u2500\u2500 Pagination absences (espace parent) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet _pAbsCurrentPage = 1;\nfunction _pAbsRenderPage(page) {\n    const list = ouzoudSessionData ? (ouzoudSessionData.absences || []) : [];\n    const total = list.length;\n    const totalPages = Math.max(1, Math.ceil(total \/ PARENTS_PER_PAGE));\n    _pAbsCurrentPage = Math.min(Math.max(1, page), totalPages);\n    const slice = list.slice((_pAbsCurrentPage-1)*PARENTS_PER_PAGE, _pAbsCurrentPage*PARENTS_PER_PAGE);\n    let html = slice.map(a => {\n        let chip = a.statut==='justified' ? '<span class=\"chip chip-orange\">Justifi\u00e9e<\/span>' : '<span class=\"chip chip-red\">Non justifi\u00e9e<\/span>';\n        return `<tr><td data-label=\"\u00c9l\u00e8ve\"><b>${escHtml(a.nom_eleve)}<\/b><\/td><td data-label=\"Classe\"><span class=\"chip chip-blue\">${escHtml(a.classe)}<\/span><\/td><td data-label=\"Date d'absence\" class=\"date-cell\">${escHtml(formatToFrenchDate(a.date_absence))}<\/td><td data-label=\"P\u00e9riode\">${escHtml(a.demi_journee)}<\/td><td data-label=\"Motif\"><i>${escHtml(a.motif||'-')}<\/i><\/td><td data-label=\"Statut\">${chip}<\/td><\/tr>`;\n    }).join('');\n    document.getElementById('p-table-absences').innerHTML = html || '<tr><td colspan=\"6\" style=\"text-align:center;color:var(--muted);\">Aucune absence r\u00e9pertori\u00e9e.<\/td><\/tr>';\n    _renderPaginationBar('p-absences', total, totalPages, _pAbsCurrentPage, '_pAbsRenderPage');\n    if (typeof updateScrollIndicators === 'function') updateScrollIndicators();\n}\n\n\/\/ \u2500\u2500 Pagination devoirs (espace parent) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet _pDevCurrentPage = 1;\nfunction _pDevRenderPage(page) {\n    const list = ouzoudSessionData ? (ouzoudSessionData.devoirs || []) : [];\n    const total = list.length;\n    const totalPages = Math.max(1, Math.ceil(total \/ PARENTS_PER_PAGE));\n    _pDevCurrentPage = Math.min(Math.max(1, page), totalPages);\n    const slice = list.slice((_pDevCurrentPage-1)*PARENTS_PER_PAGE, _pDevCurrentPage*PARENTS_PER_PAGE);\n    let html = slice.map(d => {\n        let fileLink = '-';\n        if (d.fichier_joint) {\n            \/\/ [FIX#4 v4.3] : token r\u00e9solu au clic\n            fileLink = `<button class=\"btn btn-outline btn-sm ouzoud-file-btn\" data-fileurl=\"${escHtml(d.fichier_joint)}\" style=\"height:28px; padding:4px 8px; font-size:11px;\">\ud83d\udcc4 Ouvrir<\/button>`;\n        }\n        return `<tr>\n            <td data-label=\"Mati\u00e8re\"><span class=\"chip chip-blue\" style=\"font-weight:700;\">${escHtml(d.matiere)}<\/span><\/td>\n            <td data-label=\"Travail demand\u00e9\"><b>${escHtml(d.titre)}<\/b> <span class=\"chip chip-orange\" style=\"font-size:10px;padding:2px 6px;\">${escHtml(d.classe)}<\/span><\/td>\n            <td data-label=\"Consignes\" style=\"color:var(--text-2); max-width:400px; word-wrap:break-word;\">${d.description ? escHtml(d.description) : '<span style=\"color:var(--muted);\">Aucune consigne sp\u00e9cifique.<\/span>'}<\/td>\n            <td data-label=\"Document\">${fileLink}<\/td>\n            <td data-label=\"Date de remise\" class=\"date-cell\"><b>Pour le ${escHtml(formatToFrenchDate(d.date_butoir))}<\/b><\/td>\n        <\/tr>`;\n    }).join('');\n    document.getElementById('p-list-devoirs-table').innerHTML = html || '<tr><td colspan=\"5\" style=\"text-align:center;color:var(--muted);padding:20px;\">Aucun devoir planifi\u00e9 pour le moment.<\/td><\/tr>';\n    _renderPaginationBar('p-devoirs', total, totalPages, _pDevCurrentPage, '_pDevRenderPage');\n    if (typeof updateScrollIndicators === 'function') updateScrollIndicators();\n}\n\n\/\/ \u2500\u2500 Pagination annonces (espace parent) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet _pAnnCurrentPage = 1;\nfunction _pAnnRenderPage(page) {\n    const list = ouzoudSessionData ? (ouzoudSessionData.annonces || []) : [];\n    const total = list.length;\n    const totalPages = Math.max(1, Math.ceil(total \/ PARENTS_PER_PAGE));\n    _pAnnCurrentPage = Math.min(Math.max(1, page), totalPages);\n    const slice = list.slice((_pAnnCurrentPage-1)*PARENTS_PER_PAGE, _pAnnCurrentPage*PARENTS_PER_PAGE);\n    let html = slice.map(a => {\n        let prioriteChip = a.priorite === 'haute' ? 'chip-red' : 'chip-blue';\n        let prioriteLabel = a.priorite === 'haute' ? 'Urgente' : 'Normale';\n        let fileLinkBlock = '';\n        if (a.fichier_joint) {\n            \/\/ [FIX#4 v4.3] : token r\u00e9solu au clic\n            fileLinkBlock = `\n            <div style=\"margin-top:12px; padding-top:10px; border-top:1px dashed var(--border);\">\n                <button class=\"btn btn-outline btn-sm ouzoud-file-btn\" data-fileurl=\"${escHtml(a.fichier_joint)}\" style=\"height:30px; font-size:12px;\">\ud83d\udcc4 Ouvrir le document joint<\/button>\n            <\/div>`;\n        }\n        return `\n        <div class=\"ann-card ${escHtml(a.priorite === 'haute' ? 'urgent' : '')}\">\n            <div style=\"display:flex; justify-content:space-between; margin-bottom:8px; align-items:center;\">\n                <b style=\"font-size:15px; color:var(--text);\">${escHtml(a.titre)}<\/b>\n                <span class=\"chip ${prioriteChip}\">${escHtml(prioriteLabel)}<\/span>\n            <\/div>\n            <p style=\"font-size:13.5px; color:var(--text-2); line-height:1.5; white-space:pre-line;\">${escHtml(a.contenu)}<\/p>\n            ${fileLinkBlock}\n        <\/div>`;\n    }).join('');\n    document.getElementById('p-list-annonces').innerHTML = html || '<p style=\"color:var(--muted); text-align:center; padding:20px;\">Aucune annonce disponible pour le moment.<\/p>';\n    _renderPaginationBar('p-annonces', total, totalPages, _pAnnCurrentPage, '_pAnnRenderPage');\n    if (typeof updateScrollIndicators === 'function') updateScrollIndicators();\n}\n\n\/\/ \u2500\u2500 Constante et helper partag\u00e9s pour toutes les paginations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst PARENTS_PER_PAGE = 10;\n\nfunction _renderPaginationBar(id, total, totalPages, currentPage, callbackName) {\n    const wrap = document.getElementById(id+'-pagination');\n    const info = document.getElementById(id+'-pagination-info');\n    const btns = document.getElementById(id+'-pagination-btns');\n    if (total > PARENTS_PER_PAGE) {\n        wrap.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:nowrap;gap:8px;overflow-x:auto;';\n        btns.style.cssText = 'display:flex;gap:6px;flex-wrap:nowrap;flex-shrink:0;';\n        const from = total === 0 ? 0 : (currentPage-1)*PARENTS_PER_PAGE + 1;\n        const to   = Math.min(currentPage*PARENTS_PER_PAGE, total);\n        info.textContent = `${from}\u2013${to} sur ${total}`;\n        let b = `<button class=\"btn btn-outline btn-sm\" onclick=\"${callbackName}(${currentPage-1})\" ${currentPage===1?'disabled':''} style=\"padding:6px 10px;flex-shrink:0;\">\u2039<\/button>`;\n        for (let i = 1; i <= totalPages; i++) {\n            b += `<button class=\"btn btn-sm\" onclick=\"${callbackName}(${i})\" style=\"padding:6px 12px;flex-shrink:0;${i===currentPage?'background:var(--blue);color:#fff;':'background:var(--bg);border:1px solid var(--border);color:var(--text);'}\">${i}<\/button>`;\n        }\n        b += `<button class=\"btn btn-outline btn-sm\" onclick=\"${callbackName}(${currentPage+1})\" ${currentPage===totalPages?'disabled':''} style=\"padding:6px 10px;flex-shrink:0;\">\u203a<\/button>`;\n        btns.innerHTML = b;\n    } else {\n        wrap.style.display = 'none';\n    }\n}\n\n\/\/ \u2500\u2500 Pagination absences \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet _absCurrentPage = 1;\nfunction _absRenderPage(page) {\n    const list = ouzoudSessionData ? ouzoudSessionData.absences : [];\n    const total = list ? list.length : 0;\n    const totalPages = Math.max(1, Math.ceil(total \/ PARENTS_PER_PAGE));\n    _absCurrentPage = Math.min(Math.max(1, page), totalPages);\n    const slice = list ? list.slice((_absCurrentPage-1)*PARENTS_PER_PAGE, _absCurrentPage*PARENTS_PER_PAGE) : [];\n\n    let html = slice.map(a => `<tr><td data-label=\"\u00c9l\u00e8ve\">${escHtml(a.nom_eleve)}<\/td><td data-label=\"Classe\">${escHtml(a.classe)}<\/td><td data-label=\"Date\" class=\"date-cell\">${escHtml(formatToFrenchDate(a.date_absence))}<\/td><td data-label=\"P\u00e9riode\">${escHtml(a.demi_journee)}<\/td><td data-label=\"Statut\">${a.statut==='justified'?'Justifi\u00e9e':'Non juste.'}<\/td><td data-label=\"Action\"><button class=\"btn btn-outline btn-sm\" style=\"color:var(--red);\" onclick=\"deleteItemRecord('absence', ${parseInt(a.id)})\">\ud83d\uddd1<\/button><\/td><\/tr>`).join('');\n    document.getElementById('a-table-absences').innerHTML = html || '<tr><td colspan=\"6\">Historique vide.<\/td><\/tr>';\n    _renderPaginationBar('absences', total, totalPages, _absCurrentPage, '_absRenderPage');\n    if (typeof updateScrollIndicators === 'function') updateScrollIndicators();\n}\n\/\/ \u2500\u2500 Pagination annonces \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet _annCurrentPage = 1;\nfunction _annRenderPage(page) {\n    const list = ouzoudSessionData ? ouzoudSessionData.annonces : [];\n    const total = list ? list.length : 0;\n    const totalPages = Math.max(1, Math.ceil(total \/ PARENTS_PER_PAGE));\n    _annCurrentPage = Math.min(Math.max(1, page), totalPages);\n    const slice = list ? list.slice((_annCurrentPage-1)*PARENTS_PER_PAGE, _annCurrentPage*PARENTS_PER_PAGE) : [];\n\n    let html = slice.map(a => `<tr><td data-label=\"Titre\"><b>${escHtml(a.titre)}<\/b><\/td><td data-label=\"Priorit\u00e9\">${escHtml(a.priorite)}<\/td><td data-label=\"Action\"><button class=\"btn btn-outline btn-sm\" style=\"color:var(--red);\" onclick=\"deleteItemRecord('annonce', ${parseInt(a.id)})\">\ud83d\uddd1<\/button><\/td><\/tr>`).join('');\n    document.getElementById('a-table-annonces').innerHTML = html || '<tr><td colspan=\"3\">Aucune annonce.<\/td><\/tr>';\n    _renderPaginationBar('annonces', total, totalPages, _annCurrentPage, '_annRenderPage');\n    if (typeof updateScrollIndicators === 'function') updateScrollIndicators();\n}\n\n\/\/ \u2500\u2500 Pagination devoirs (admin) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet _devCurrentPage = 1;\nfunction _devRenderPage(page) {\n    const list = ouzoudSessionData ? (ouzoudSessionData.devoirs || []) : [];\n    const total = list ? list.length : 0;\n    const totalPages = Math.max(1, Math.ceil(total \/ PARENTS_PER_PAGE));\n    _devCurrentPage = Math.min(Math.max(1, page), totalPages);\n    const slice = list ? list.slice((_devCurrentPage-1)*PARENTS_PER_PAGE, _devCurrentPage*PARENTS_PER_PAGE) : [];\n\n    let html = slice.map(d => {\n        let fileBtn = d.fichier_joint\n            ? `<button class=\"btn btn-outline btn-sm ouzoud-file-btn\" data-fileurl=\"${escHtml(d.fichier_joint)}\" style=\"height:26px;padding:2px 8px;font-size:11px;\">\ud83d\udcc4 Ouvrir<\/button>`\n            : '<span style=\"color:var(--muted);\">\u2014<\/span>';\n        const publiePar = d.publie_par && d.publie_par !== '' ? d.publie_par : 'Administration';\n        const isAdmin = publiePar === 'Administration';\n        \/\/ On n'affiche que le nom de l'enseignant : le code (ex. \"(ENS-2026-2177)\") reste\n        \/\/ en base pour le filtrage\/la suppression mais n'est jamais montr\u00e9 \u00e0 l'\u00e9cran.\n        const publieParAffiche = publiePar.replace(\/\\s*\\(ENS-\\d{4}-\\d{4}\\)\\s*$\/i, '').trim() || publiePar;\n        const badgeStyle = isAdmin\n            ? 'background:#FEF3C7;color:#92400E;'\n            : 'background:#D1FAE5;color:#065F46;';\n        return `<tr>\n            <td data-label=\"Classe\"><span class=\"chip chip-blue\">${escHtml(d.classe)}<\/span><\/td>\n            <td data-label=\"Mati\u00e8re\">${escHtml(d.matiere)}<\/td>\n            <td data-label=\"Titre\"><b>${escHtml(d.titre)}<\/b><\/td>\n            <td data-label=\"Publi\u00e9 par\"><span style=\"font-size:11.5px;padding:2px 8px;border-radius:20px;font-weight:600;${badgeStyle}\">${escHtml(publieParAffiche)}<\/span><\/td>\n            <td data-label=\"Date Butoir\" class=\"date-cell\">${escHtml(formatToFrenchDate(d.date_butoir))}<\/td>\n            <td data-label=\"Document\">${fileBtn}<\/td>\n            <td data-label=\"Action\" style=\"white-space:nowrap;\">\n                <button class=\"btn btn-outline btn-sm\" style=\"color:var(--red);\" onclick=\"deleteItemRecord('devoir', ${parseInt(d.id)})\">\ud83d\uddd1<\/button>\n            <\/td>\n        <\/tr>`;\n    }).join('');\n    document.getElementById('a-table-devoirs').innerHTML = html || '<tr><td colspan=\"7\" style=\"text-align:center;color:var(--muted);padding:20px;\">Aucun devoir publi\u00e9 pour le moment.<\/td><\/tr>';\n    _renderPaginationBar('devoirs', total, totalPages, _devCurrentPage, '_devRenderPage');\n    if (typeof updateScrollIndicators === 'function') updateScrollIndicators();\n}\n\nlet _parentCurrentPage = 1;\nlet _parentFilteredList = null; \/\/ null = pas de filtre actif\n\nfunction _parentsBuildRows(list) {\n    if (!list || list.length === 0) return '';\n    return list.map(p => `<tr><td data-label=\"Parent\"><b>${escHtml(p.nom_complet)}<\/b><\/td><td data-label=\"Code Parent\"><span style=\"font-family:monospace;font-size:12px;font-weight:600;color:var(--blue-dark);background:var(--blue-light);padding:3px 8px;border-radius:6px;\">${escHtml(p.code_parent)}<\/span><\/td><td data-label=\"Email\">${escHtml(p.email||'-')}<\/td><td data-label=\"Enfants Rattach\u00e9s\">${p.enfants ? escHtml(p.enfants.replace(\/\\|\/g, ', ')) : '<i style=\"color:var(--muted)\">Aucun<\/i>'}<\/td><td data-label=\"Actions\"><button class=\"btn btn-outline btn-sm\" onclick=\"openParentModal(${parseInt(p.id)})\">\u00c9diter<\/button> <button class=\"btn btn-outline btn-sm\" style=\"color:var(--red);\" onclick=\"deleteItemRecord('parent', ${parseInt(p.id)})\">Suppr.<\/button><\/td><\/tr>`).join('');\n}\n\nfunction _parentsRenderPage(page) {\n    const list = _parentFilteredList !== null ? _parentFilteredList : (ouzoudSessionData ? ouzoudSessionData.parents : []);\n    const total = list ? list.length : 0;\n    const totalPages = Math.max(1, Math.ceil(total \/ PARENTS_PER_PAGE));\n    _parentCurrentPage = Math.min(Math.max(1, page), totalPages);\n\n    const start = (_parentCurrentPage - 1) * PARENTS_PER_PAGE;\n    const slice = list ? list.slice(start, start + PARENTS_PER_PAGE) : [];\n\n    const tbody = document.getElementById('a-table-parents');\n    const emptyMsg = document.getElementById('parents-search-empty');\n\n    tbody.innerHTML = _parentsBuildRows(slice) || '<tr><td colspan=\"5\">Aucun parent enregistr\u00e9.<\/td><\/tr>';\n    emptyMsg.style.display = (_parentFilteredList !== null && total === 0) ? 'block' : 'none';\n    _renderPaginationBar('parents', total, totalPages, _parentCurrentPage, '_parentsRenderPage');\n    if (typeof updateScrollIndicators === 'function') updateScrollIndicators();\n}\n\nfunction renderAdminPanels() {\n    populateAllMatiereSelects();\n    \/\/ R\u00e9initialiser la recherche \u00e0 chaque rechargement\n    const searchInput = document.getElementById('parents-search-input');\n    if (searchInput) { searchInput.value = ''; }\n    const emptyMsg = document.getElementById('parents-search-empty');\n    if (emptyMsg) { emptyMsg.style.display = 'none'; }\n\n    _parentFilteredList = null;\n    _parentCurrentPage = 1;\n    _parentsRenderPage(1);\n\n    _absCurrentPage = 1;\n    _annCurrentPage = 1;\n    _devCurrentPage = 1;\n    _absRenderPage(1);\n    _annRenderPage(1);\n    _devRenderPage(1);\n\n    _ensFilteredList = null;\n    _ensCurrentPage  = 1;\n    _ensRenderPage(1);\n\n    const ensSearch = document.getElementById('ens-search-input');\n    if (ensSearch) ensSearch.value = '';\n    const ensEmpty = document.getElementById('ens-search-empty');\n    if (ensEmpty) ensEmpty.style.display = 'none';\n\n    updateAbsenceClasseDropdown();\n    updateAbsencesStudentsDropdown();\n    restoreLastAbsenceClass();\n    updateADevoirClasseDropdown();\n\n    \/\/ Date par d\u00e9faut = demain\n    const aDate = document.getElementById('a-dev-date');\n    if (aDate && !aDate.value) aDate.value = getTomorrowSQLDate();\n\n    if (typeof updateScrollIndicators === 'function') updateScrollIndicators();\n}\n\n\/\/ \u2500\u2500 Enseignants : pagination, rendu, modal, CSV \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet _ensFilteredList = null;\nlet _ensCurrentPage  = 1;\nconst ENS_PER_PAGE   = 20;\n\nfunction _ensBuildRows(list) {\n    return list.map(e => `<tr>\n        <td data-label=\"Enseignant\"><b>${escHtml(e.nom_complet)}<\/b><\/td>\n        <td data-label=\"Code\"><span style=\"font-family:monospace;font-size:12px;font-weight:600;color:var(--purple);background:#EDE9FE;padding:3px 8px;border-radius:6px;\">${escHtml(e.code_enseignant)}<\/span><\/td>\n        <td data-label=\"Mati\u00e8re(s)\">${escHtml(e.matieres||'\u2014')}<\/td>\n        <td data-label=\"Classe(s)\" style=\"font-size:12px;color:var(--muted);\">${escHtml(e.classes||'\u2014')}<\/td>\n        <td data-label=\"Actions\"><button class=\"btn btn-outline btn-sm\" onclick=\"openEnsModal(${parseInt(e.id)})\">\u00c9diter<\/button> <button class=\"btn btn-outline btn-sm\" style=\"color:var(--red);\" onclick=\"deleteItemRecord('enseignant',${parseInt(e.id)})\">Suppr.<\/button><\/td>\n    <\/tr>`).join('');\n}\n\nfunction _ensRenderPage(page) {\n    const all   = _ensFilteredList !== null ? _ensFilteredList : (ouzoudSessionData ? (ouzoudSessionData.enseignants||[]) : []);\n    const total = all.length;\n    const totalPages = Math.max(1, Math.ceil(total \/ ENS_PER_PAGE));\n    _ensCurrentPage  = Math.min(Math.max(1, page), totalPages);\n    const start = (_ensCurrentPage - 1) * ENS_PER_PAGE;\n    const slice = all.slice(start, start + ENS_PER_PAGE);\n    const tbody = document.getElementById('a-table-enseignants');\n    if (tbody) tbody.innerHTML = slice.length ? _ensBuildRows(slice) : '<tr><td colspan=\"5\" style=\"text-align:center;color:var(--muted);padding:24px;\">Aucun enseignant enregistr\u00e9.<\/td><\/tr>';\n    _renderPaginationBar('ens', total, totalPages, _ensCurrentPage, '_ensRenderPage');\n}\n\nfunction filterEnseignantsTable(query) {\n    function norm(s) { return (s||'').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,'').toLowerCase(); }\n    query = norm(query.trim());\n    const emptyMsg = document.getElementById('ens-search-empty');\n    if (!query) {\n        _ensFilteredList = null;\n        if (emptyMsg) emptyMsg.style.display = 'none';\n    } else {\n        const all = ouzoudSessionData ? (ouzoudSessionData.enseignants||[]) : [];\n        _ensFilteredList = all.filter(e => norm([e.nom_complet,e.code_enseignant,e.matieres,e.classes].filter(Boolean).join(' ')).includes(query));\n        if (emptyMsg) emptyMsg.style.display = (_ensFilteredList.length === 0) ? 'block' : 'none';\n    }\n    _ensRenderPage(1);\n}\n\n\/\/ \u2500\u2500 Modal enseignant \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction _buildEnsClassesCheckboxes(selectedClasses) {\n    const wrap = document.getElementById('e-classes-checkboxes');\n    if (!wrap) return;\n    const config = (ouzoudSessionData && ouzoudSessionData.classes_config) ? ouzoudSessionData.classes_config : {};\n    const sel = (selectedClasses || []).map(c => c.trim());\n    wrap.innerHTML = '';\n    for (const niveau in config) {\n        (config[niveau] || []).forEach(classe => {\n            const checked = sel.includes(classe) ? ' checked' : '';\n            wrap.innerHTML += `<label style=\"display:flex;align-items:center;gap:5px;font-size:13px;cursor:pointer;white-space:nowrap;min-width:80px;\">\n                <input type=\"checkbox\" value=\"${escHtml(classe)}\" style=\"accent-color:var(--purple);\"${checked}> ${escHtml(classe)}\n            <\/label>`;\n        });\n    }\n}\n\nfunction openEnsModal(id) {\n    if (id === 0) {\n        document.getElementById('e-id').value = 0;\n        document.getElementById('e-nom').value = '';\n        document.getElementById('e-matieres').value = '';\n        document.getElementById('me-title').innerText = 'Ajouter un Enseignant';\n        _buildEnsClassesCheckboxes([]);\n    } else {\n        const e = (ouzoudSessionData && ouzoudSessionData.enseignants||[]).find(x => x.id == id);\n        if (!e) return;\n        document.getElementById('e-id').value = e.id;\n        document.getElementById('e-nom').value = e.nom_complet;\n        document.getElementById('e-matieres').value = e.matieres || '';\n        document.getElementById('me-title').innerText = 'Modifier : ' + e.nom_complet;\n        const classes = (e.classes||'').split(',').map(c => c.trim());\n        _buildEnsClassesCheckboxes(classes);\n    }\n    const btn = document.getElementById('btn-save-ens');\n    if (btn) { btn.disabled = false; btn.textContent = 'Enregistrer'; }\n    document.getElementById('m-ens-form').style.display = 'flex';\n}\n\nfunction submitEnsForm() {\n    const id       = parseInt(document.getElementById('e-id').value) || 0;\n    const nom      = document.getElementById('e-nom').value.trim();\n    const matieres = document.getElementById('e-matieres').value.trim();\n    const wrap     = document.getElementById('e-classes-checkboxes');\n    const classes  = wrap ? Array.from(wrap.querySelectorAll('input[type=checkbox]:checked')).map(cb => cb.value) : [];\n\n    if (!nom)           { showToast('Le nom complet est obligatoire.', 'error'); return; }\n    if (!matieres)      { showToast('Au moins une mati\u00e8re est requise.', 'error'); return; }\n    if (!classes.length){ showToast('Cochez au moins une classe.', 'error'); return; }\n\n    const btn = document.getElementById('btn-save-ens');\n    if (btn) { btn.disabled = true; btn.textContent = 'Enregistrement\u2026'; }\n\n    let p = new URLSearchParams();\n    p.append('action', 'ouzoud_ajax_save_data');\n    p.append('action_type', 'save_enseignant');\n    p.append('nonce', OuzoudAjax.nonce);\n    p.append('id', id);\n    p.append('nom', nom);\n    p.append('matieres', matieres);\n    p.append('classes', classes.join(', '));\n\n    fetch(OuzoudAjax.url, { method: 'POST', body: p }).then(r => r.text()).then(text => {\n        let res; try { res = JSON.parse(text); } catch(ex) { res = { success: false, data: 'Erreur. Vous devez \u00eatre administrateur.' }; }\n        if (res.success) {\n            const code = res.data.code_enseignant;\n            if (res.data.is_new) {\n                showToast('Enseignant cr\u00e9\u00e9. Code : ' + code, 'success', 6000);\n                setTimeout(() => showToast('Code : ' + code + ' \u2014 \u00c0 communiquer \u00e0 l\\'enseignant.', 'success', 10000), 200);\n            } else {\n                showToast('Fiche enseignant mise \u00e0 jour.', 'success');\n            }\n            closeOuzoudModal('m-ens-form');\n            loadApplicationWorkspace();\n        } else {\n            showToast(res.data || '\u00c9chec de l\\'enregistrement.', 'error');\n            if (btn) { btn.disabled = false; btn.textContent = 'Enregistrer'; }\n        }\n    }).catch(() => {\n        showToast('Erreur r\u00e9seau.', 'error');\n        if (btn) { btn.disabled = false; btn.textContent = 'Enregistrer'; }\n    });\n}\n\n\/\/ \u2500\u2500 Suppression via deleteItemRecord (message de confirmation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\/\/ Ajout du cas 'enseignant' dans deleteItemRecord \u2014 inject\u00e9 dynamiquement\n(function patchDeleteItemRecord() {\n    const orig = window.deleteItemRecord;\n    window.deleteItemRecord = function(target, id) {\n        if (target === 'enseignant' && ouzoudSessionData && ouzoudSessionData.enseignants) {\n            const e = ouzoudSessionData.enseignants.find(x => x.id == id);\n            const msg = e\n                ? `Supprimer d\u00e9finitivement l'enseignant \"${_safeConfirmStr(e.nom_complet)}\" (${_safeConfirmStr(e.code_enseignant)}) ?\\n\\nCette action est irr\u00e9versible.`\n                : 'Confirmer la suppression d\u00e9finitive ?';\n            if (!confirm(msg)) return;\n            let p = new URLSearchParams();\n            p.append('action','ouzoud_ajax_delete_data'); p.append('nonce',OuzoudAjax.nonce);\n            p.append('target','enseignant'); p.append('id',id);\n            fetch(OuzoudAjax.url,{method:'POST',body:p}).then(r=>r.text()).then(text=>{\n                let res; try{res=JSON.parse(text);}catch(ex){res={success:false,data:'Erreur r\u00e9seau.'};}\n                if(res.success){showToast('Enseignant supprim\u00e9.','info');loadApplicationWorkspace();}\n                else{showToast(res.data||'\u00c9chec de la suppression.','error');}\n            }).catch(()=>showToast('Erreur r\u00e9seau.','error'));\n        } else {\n            orig(target, id);\n        }\n    };\n})();\n\n\/\/ \u2500\u2500 Export CSV \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction exportEnseignantsCsv() {\n    let p = new URLSearchParams();\n    p.append('action','ouzoud_ajax_refresh_data'); p.append('nonce',OuzoudAjax.nonce);\n    fetch(OuzoudAjax.url,{method:'POST',body:p}).then(r=>r.json()).then(res=>{\n        if (res.success) {\n            const saved = { _loginType: ouzoudSessionData ? ouzoudSessionData._loginType : null };\n            ouzoudSessionData = res.data;\n            Object.assign(ouzoudSessionData, saved);\n        }\n        _doExportEnseignantsCsv();\n    }).catch(()=>showToast('Erreur r\u00e9seau. Export annul\u00e9.','error'));\n}\n\nfunction _doExportEnseignantsCsv() {\n    const list = ouzoudSessionData && ouzoudSessionData.enseignants;\n    if (!list || list.length === 0) { showToast('Aucun enseignant \u00e0 exporter.','info'); return; }\n    const sep = ';';\n    const bom = '\\uFEFF';\n    let csv = bom + ['nom_complet','code_enseignant','matieres','classes'].join(sep) + '\\n';\n    list.forEach(e => {\n        csv += `\"${csvSafeField(e.nom_complet)}\"${sep}\"${csvSafeField(e.code_enseignant)}\"${sep}\"${csvSafeField(e.matieres)}\"${sep}\"${csvSafeField(e.classes)}\"\\n`;\n    });\n    const blob = new Blob([csv],{type:'text\/csv;charset=utf-8;'});\n    const url  = URL.createObjectURL(blob);\n    const a    = document.createElement('a');\n    const today = new Date();\n    const ds = `${today.getFullYear()}${String(today.getMonth()+1).padStart(2,'0')}${String(today.getDate()).padStart(2,'0')}`;\n    a.href = url; a.download = `enseignants_ecole_ouzoud_${ds}.csv`;\n    document.body.appendChild(a); a.click(); document.body.removeChild(a);\n    URL.revokeObjectURL(url);\n    showToast(`${list.length} enseignant(s) export\u00e9(s) en CSV.`, 'success');\n}\n\n\/\/ \u2500\u2500 Import CSV enseignants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction openEnsImportModal() {\n    document.getElementById('ens-csv-file-input').value = '';\n    const rd = document.getElementById('ens-csv-import-result');\n    rd.style.display = 'none'; rd.innerHTML = '';\n    const btn = document.getElementById('btn-launch-ens-import');\n    btn.disabled = false; btn.textContent = '\ud83d\ude80 Lancer l\\'importation';\n    btn.onclick = launchEnsCsvImport;\n    document.getElementById('m-ens-csv-import').style.display = 'flex';\n}\n\nfunction downloadEnsCsvTemplate() {\n    const year = new Date().getFullYear();\n    let csv = 'nom_complet,code_enseignant,matieres,classes\\n';\n    csv += `Mme F. Alaoui,ENS-${year}-1234,\"Maths, Fran\u00e7ais\",\"CP-1|CP-2|CE1-1\"\\n`;\n    csv += `M. K. Benali,,\"Arabe\",\"CM1-1|CM1-2\"\\n`;\n    csv += `Mme N. Tazi,,Fran\u00e7ais,\"CE2-1\"\\n`;\n    const blob = new Blob([csv],{type:'text\/csv;charset=utf-8;'});\n    const url  = URL.createObjectURL(blob);\n    const a    = document.createElement('a');\n    a.href = url; a.download = 'modele_import_enseignants.csv';\n    document.body.appendChild(a); a.click(); document.body.removeChild(a);\n    URL.revokeObjectURL(url);\n}\n\nfunction launchEnsCsvImport() {\n    const fi = document.getElementById('ens-csv-file-input');\n    if (!fi.files || fi.files.length === 0) { showToast('Veuillez s\u00e9lectionner un fichier CSV.','error'); return; }\n    const btn = document.getElementById('btn-launch-ens-import');\n    btn.disabled = true; btn.textContent = '\u23f3 Importation en cours...';\n    const fd = new FormData();\n    fd.append('action','ouzoud_ajax_import_ens_csv');\n    fd.append('nonce',OuzoudAjax.nonce);\n    fd.append('csv_file',fi.files[0]);\n    fetch(OuzoudAjax.url,{method:'POST',body:fd})\n    .then(r=>r.text()).then(text=>{\n        let res; try{res=JSON.parse(text);}catch(e){res={success:false,data:'\u00c9chec de l\\'importation. Vous devez \u00eatre administrateur.'};}\n        const rd = document.getElementById('ens-csv-import-result');\n        rd.style.display = 'block';\n        if (res.success) {\n            const d = res.data;\n            let errorsHtml = '';\n            if (d.errors && d.errors.length > 0) {\n                errorsHtml = `<div style=\"margin-top:10px;padding:10px 12px;background:#FEF3C7;border:1px solid #FCD34D;border-radius:8px;\">\n                    <p style=\"font-weight:700;color:#92400E;margin-bottom:6px;\">\u26a0\ufe0f ${d.errors.length} ligne(s) avec erreur(s) :<\/p>\n                    <ul style=\"font-size:12px;color:#78350F;padding-left:16px;line-height:1.7;\">${d.errors.map(e=>`<li>${escHtml(e)}<\/li>`).join('')}<\/ul>\n                <\/div>`;\n            }\n            rd.innerHTML = `<div style=\"padding:14px;background:#F5F3FF;border:1px solid #C4B5FD;border-radius:10px;\">\n                <p style=\"font-weight:700;color:#4C1D95;margin-bottom:8px;\">\u2705 Importation r\u00e9ussie !<\/p>\n                <div style=\"display:flex;gap:16px;flex-wrap:wrap;\">\n                    <span style=\"font-size:13px;color:#6D28D9;\"><b>${d.inserted}<\/b> enseignant(s) ajout\u00e9(s)<\/span>\n                    <span style=\"font-size:13px;color:#0369A1;\"><b>${d.updated}<\/b> fiche(s) mise(s) \u00e0 jour<\/span>\n                    ${d.skipped>0?`<span style=\"font-size:13px;color:var(--muted);\"><b>${d.skipped}<\/b> ligne(s) ignor\u00e9e(s)<\/span>`:''}\n                <\/div>\n            <\/div>${errorsHtml}`;\n            btn.disabled = false; btn.textContent = '\u2705 Termin\u00e9';\n            btn.onclick = function(){ closeOuzoudModal('m-ens-csv-import'); };\n            loadApplicationWorkspace();\n        } else {\n            rd.innerHTML = `<div style=\"padding:14px;background:var(--red-light);border:1px solid #FCA5A5;border-radius:10px;\"><p style=\"font-weight:700;color:var(--red-dark);\">\u274c Erreur : ${escHtml(res.data||'\u00c9chec.')}<\/p><\/div>`;\n            btn.disabled = false; btn.textContent = '\ud83d\ude80 Lancer l\\'importation';\n        }\n    }).catch(()=>{\n        btn.disabled=false; btn.textContent='\ud83d\ude80 Lancer l\\'importation';\n        showToast('Erreur r\u00e9seau lors de l\\'importation.','error');\n    });\n}\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\n\/\/ \u2500\u2500 Workspace Admin : cascade niveau \u2192 classe (devoirs) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\/\/ \u2500\u2500 Recherche rapide de classe (formulaires \"Publier un devoir\" admin & enseignant) \u2500\u2500\nlet _classeSearchActiveIdx = {};\nlet _classeSearchMatches = {};\n\nfunction getAllClassesList() {\n    const config = (ouzoudSessionData && ouzoudSessionData.classes_config) ? ouzoudSessionData.classes_config : {};\n    const list = [];\n    for (const niveau in config) {\n        (config[niveau] || []).forEach(classe => list.push({ niveau: niveau, classe: classe }));\n    }\n    return list;\n}\n\nfunction filterClasseSearch(prefix) {\n    const input = document.getElementById(prefix + '-classe-search');\n    const box = document.getElementById(prefix + '-classe-search-results');\n    if (!input || !box) return;\n    const q = input.value.trim().toLowerCase();\n\n    \/\/ [SEC] : dans l'espace enseignant (prefix 't-dev'), limiter la recherche\n    \/\/ aux classes assign\u00e9es \u00e0 cet enseignant uniquement.\n    let all = getAllClassesList();\n    if (prefix === 't-dev' && ouzoudSessionData && Array.isArray(ouzoudSessionData.classes) && ouzoudSessionData.classes.length > 0) {\n        const teacherClasses = ouzoudSessionData.classes.map(c => c.trim());\n        all = all.filter(c => teacherClasses.includes(c.classe));\n    }\n\n    if (!q) { box.classList.remove('open'); box.innerHTML = ''; _classeSearchMatches[prefix] = []; _classeSearchActiveIdx[prefix] = -1; return; }\n\n    _classeSearchMatches[prefix] = all.filter(c => (c.classe || '').toLowerCase().includes(q)).slice(0, 30);\n    _classeSearchActiveIdx[prefix] = -1;\n    const matches = _classeSearchMatches[prefix];\n\n    if (matches.length === 0) {\n        box.innerHTML = '<div class=\"eleve-search-empty\">Aucune classe trouv\u00e9e.<\/div>';\n    } else {\n        box.innerHTML = matches.map((c, i) =>\n            `<div class=\"eleve-search-item\" data-idx=\"${i}\" onclick=\"selectClasseFromSearch('${prefix}', ${i})\">\n                <span class=\"esi-name\">${escHtml(c.classe)}<\/span>\n                <span class=\"esi-classe\">${escHtml(c.niveau)}<\/span>\n            <\/div>`\n        ).join('');\n    }\n    box.classList.add('open');\n}\n\nfunction classeSearchKeydown(evt, prefix) {\n    const box = document.getElementById(prefix + '-classe-search-results');\n    const matches = _classeSearchMatches[prefix] || [];\n    if (!box || !box.classList.contains('open') || matches.length === 0) return;\n    const items = box.querySelectorAll('.eleve-search-item');\n    let idx = _classeSearchActiveIdx[prefix] ?? -1;\n    if (evt.key === 'ArrowDown') {\n        evt.preventDefault();\n        idx = Math.min(idx + 1, matches.length - 1);\n        _classeSearchActiveIdx[prefix] = idx;\n        items.forEach((it, i) => it.classList.toggle('active', i === idx));\n        if (items[idx]) items[idx].scrollIntoView({ block: 'nearest' });\n    } else if (evt.key === 'ArrowUp') {\n        evt.preventDefault();\n        idx = Math.max(idx - 1, 0);\n        _classeSearchActiveIdx[prefix] = idx;\n        items.forEach((it, i) => it.classList.toggle('active', i === idx));\n        if (items[idx]) items[idx].scrollIntoView({ block: 'nearest' });\n    } else if (evt.key === 'Enter') {\n        evt.preventDefault();\n        if (idx >= 0) selectClasseFromSearch(prefix, idx);\n        else if (matches.length === 1) selectClasseFromSearch(prefix, 0);\n    } else if (evt.key === 'Escape') {\n        box.classList.remove('open');\n    }\n}\n\nfunction selectClasseFromSearch(prefix, idx) {\n    const match = (_classeSearchMatches[prefix] || [])[idx];\n    if (!match) return;\n\n    const niveauSel = document.getElementById(prefix + '-niveau');\n    const classeSel = document.getElementById(prefix + '-classe');\n\n    niveauSel.value = match.niveau;\n    if (prefix === 'a-dev') updateADevoirClasseDropdown();\n    else if (prefix === 't-dev') updateTDevoirClasseDropdown();\n    classeSel.value = match.classe;\n\n    document.getElementById(prefix + '-classe-search').value = match.classe;\n    document.getElementById(prefix + '-classe-search-results').classList.remove('open');\n    showToast(`Classe ${match.classe} s\u00e9lectionn\u00e9e \u2014 ${match.niveau}.`, 'success');\n}\n\n\/\/ Ferme les listes de suggestions de classe au clic en dehors du champ\ndocument.addEventListener('click', function(evt) {\n    ['a-dev', 't-dev'].forEach(prefix => {\n        const wrap = document.getElementById(prefix + '-classe-search');\n        const box = document.getElementById(prefix + '-classe-search-results');\n        if (!wrap || !box) return;\n        if (evt.target !== wrap && !box.contains(evt.target)) box.classList.remove('open');\n    });\n});\n\nfunction updateADevoirClasseDropdown() {\n    const niveauSel = document.getElementById('a-dev-niveau');\n    const classeSel = document.getElementById('a-dev-classe');\n    if (!niveauSel || !classeSel) return;\n    const selectedNiveau = niveauSel.value;\n    classeSel.innerHTML = '';\n    if (!selectedNiveau) {\n        classeSel.innerHTML = '<option value=\"\" disabled selected>S\u00e9lectionnez d\\'abord un niveau<\/option>';\n        classeSel.disabled = true;\n        return;\n    }\n    const config = (ouzoudSessionData && ouzoudSessionData.classes_config) ? ouzoudSessionData.classes_config : {};\n    const classes = config[selectedNiveau] || [];\n    if (classes.length === 0) {\n        classeSel.innerHTML = '<option value=\"\" disabled selected>Aucune classe configur\u00e9e<\/option>';\n        classeSel.disabled = true;\n    } else {\n        classeSel.innerHTML = '<option value=\"\" disabled selected>-- Choisir la classe --<\/option>';\n        classes.forEach(c => {\n            let opt = document.createElement('option');\n            opt.value = c; opt.textContent = c;\n            classeSel.appendChild(opt);\n        });\n        classeSel.disabled = false;\n    }\n}\n\n\/\/ \u2500\u2500 Devoir : mati\u00e8re \"Autre\" (admin & enseignant) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.addEventListener('change', function(e) {\n    if (e.target.id === 'a-dev-matiere') {\n        const wrap = document.getElementById('a-dev-autre-wrap');\n        if (wrap) wrap.style.display = (e.target.value === '__autre__') ? '' : 'none';\n    }\n    if (e.target.id === 't-dev-matiere') {\n        const wrap = document.getElementById('t-dev-autre-wrap');\n        if (wrap) wrap.style.display = (e.target.value === '__autre__') ? '' : 'none';\n    }\n});\n\n\/\/ \u2500\u2500 Devoir : mode lot (toutes les classes du niveau) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction toggleADevAllClasses() {\n    const cb = document.getElementById('a-dev-all-classes');\n    const classeSelect = document.getElementById('a-dev-classe');\n    if (!classeSelect) return;\n    classeSelect.disabled = !!(cb && cb.checked);\n    if (cb && cb.checked) {\n        classeSelect.dataset.prevValue = classeSelect.value;\n    } else {\n        if (classeSelect.dataset.prevValue) classeSelect.value = classeSelect.dataset.prevValue;\n    }\n}\n\nfunction toggleTDevAllClasses() {\n    const cb = document.getElementById('t-dev-all-classes');\n    const classeSelect = document.getElementById('t-dev-classe');\n    if (!classeSelect) return;\n    classeSelect.disabled = !!(cb && cb.checked);\n    if (cb && cb.checked) {\n        classeSelect.dataset.prevValue = classeSelect.value;\n    } else {\n        if (classeSelect.dataset.prevValue) classeSelect.value = classeSelect.dataset.prevValue;\n    }\n}\n\nfunction _getDevMatiere(prefix) {\n    const sel = document.getElementById(prefix+'-matiere');\n    if (!sel) return '';\n    if (sel.value === '__autre__') {\n        return (document.getElementById(prefix+'-matiere-autre') || {}).value || '';\n    }\n    return sel.value;\n}\n\nfunction _getDevClasses(prefix) {\n    const nivEl = document.getElementById(prefix+'-niveau');\n    const cbAll = document.getElementById(prefix+'-all-classes');\n    if (cbAll && cbAll.checked && nivEl && nivEl.value) {\n        const config = (ouzoudSessionData && ouzoudSessionData.classes_config) ? ouzoudSessionData.classes_config : {};\n        return config[nivEl.value] || [];\n    }\n    const classeEl = document.getElementById(prefix+'-classe');\n    return classeEl && classeEl.value ? [classeEl.value] : [];\n}\n\n\/\/ \u2500\u2500 Workspace Admin : publier un devoir \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction publishADevoir() {\n    const btnSubmit = document.querySelector('[onclick=\"publishADevoir()\"]');\n    \/\/ [FIX#19] : garde anti-double-soumission \u2014 coh\u00e9rent avec save_parent \/ save_absence\n    if (btnSubmit && btnSubmit.disabled) return;\n\n    const classes  = _getDevClasses('a-dev');\n    let matiere = _getDevMatiere('a-dev');\n    let titre   = document.getElementById('a-dev-titre').value.trim();\n    let devDate = document.getElementById('a-dev-date').value;\n    let fileInput = document.getElementById('a-dev-fichier');\n\n    const cbAll = document.getElementById('a-dev-all-classes');\n    const allClasses = cbAll && cbAll.checked;\n\n    if (!allClasses && classes.length === 0) { showToast(\"Le niveau et la classe sont obligatoires.\", 'error'); return; }\n    if (allClasses && classes.length === 0) { showToast(\"Aucune classe trouv\u00e9e pour ce niveau.\", 'error'); return; }\n    if (!matiere) { showToast(\"La mati\u00e8re est obligatoire.\", 'error'); return; }\n    if (!titre) { showToast(\"Le titre est obligatoire.\", 'error'); return; }\n    if (!devDate || !\/^\\d{4}-\\d{2}-\\d{2}$\/.test(devDate)) { showToast(\"Veuillez saisir une date de remise valide.\", 'error'); return; }\n\n    let file = null;\n    if (fileInput.files.length > 0) {\n        file = fileInput.files[0];\n        if (file.size > 2 * 1024 * 1024) { showToast('Le fichier d\u00e9passe la limite de 2 Mo.', 'error'); return; }\n    }\n\n    \/\/ Verrouiller le bouton\n    if (btnSubmit) { btnSubmit.disabled = true; btnSubmit.textContent = '\u23f3 Diffusion en cours\u2026'; }\n\n    const desc = document.getElementById('a-dev-desc').value;\n\n    function sendOne(classe, cb) {\n        let formData = new FormData();\n        formData.append('action', 'ouzoud_ajax_save_data');\n        formData.append('action_type', 'save_devoir');\n        formData.append('nonce', OuzoudAjax.nonce);\n        formData.append('classe', classe);\n        formData.append('matiere', matiere);\n        formData.append('titre', titre);\n        formData.append('desc', desc);\n        formData.append('date', devDate);\n        if (file) formData.append('dev_fichier', file);\n        fetch(OuzoudAjax.url, { method: 'POST', body: formData })\n            .then(r => r.text())\n            .then(text => { let res; try { res = JSON.parse(text); } catch(e) { res = { success: false }; } cb(res.success); })\n            .catch(() => cb(false));\n    }\n\n    let pending = classes.length;\n    let errors = 0;\n    classes.forEach(function(classe) {\n        sendOne(classe, function(ok) {\n            if (!ok) errors++;\n            pending--;\n            if (pending === 0) {\n                \/\/ [FIX#19] : d\u00e9verrouiller le bouton dans tous les cas (succ\u00e8s ET erreur)\n                if (btnSubmit) { btnSubmit.disabled = false; btnSubmit.textContent = '\\u{1F4E4} Diffuser le devoir'; }\n                if (errors === 0) {\n                    showToast(classes.length > 1 ? 'Devoir diffus\u00e9 \u00e0 ' + classes.length + ' classes avec succ\u00e8s.' : 'Devoir publi\u00e9 avec succ\u00e8s.', 'success');\n                    document.getElementById('a-dev-niveau').value = '';\n                    document.getElementById('a-dev-classe').innerHTML = '<option value=\"\" disabled selected>S\u00e9lectionnez d\\'abord un niveau<\/option>';\n                    document.getElementById('a-dev-classe').disabled = true;\n                    document.getElementById('a-dev-matiere').value = '';\n                    const aAutreWrap = document.getElementById('a-dev-autre-wrap');\n                    if (aAutreWrap) { aAutreWrap.style.display = 'none'; document.getElementById('a-dev-matiere-autre').value = ''; }\n                    const cbAllEl = document.getElementById('a-dev-all-classes');\n                    if (cbAllEl) cbAllEl.checked = false;\n                    document.getElementById('a-dev-titre').value = 'Devoir maison';\n                    document.getElementById('a-dev-desc').value = '';\n                    document.getElementById('a-dev-date').value = getTomorrowSQLDate();\n                    fileInput.value = '';\n                    const aDevSearchEl = document.getElementById('a-dev-classe-search');\n                    if(aDevSearchEl) aDevSearchEl.value = '';\n                    const aDevSearchResEl = document.getElementById('a-dev-classe-search-results');\n                    if(aDevSearchResEl) { aDevSearchResEl.classList.remove('open'); aDevSearchResEl.innerHTML = ''; }\n                    loadApplicationWorkspace();\n                } else {\n                    showToast(errors + ' erreur(s) lors de l\\'envoi.', 'error');\n                }\n            }\n        });\n    });\n}\n\nfunction renderTeacherPanels() {\n    \/\/ Mettre \u00e0 jour le nom et les classes dans la topbar (en sync avec les donn\u00e9es fra\u00eeches)\n    const nameBar = document.getElementById('teacher-name-bar');\n    if (nameBar) nameBar.innerText = (ouzoudSessionData && ouzoudSessionData.name) ? ouzoudSessionData.name : '';\n    const classesBar = document.getElementById('teacher-classes-bar');\n    if (classesBar) {\n        const classes = (ouzoudSessionData && Array.isArray(ouzoudSessionData.classes) && ouzoudSessionData.classes.length > 0)\n            ? ouzoudSessionData.classes : [];\n        classesBar.innerHTML = classes.map(c => `<span class=\"t-class-chip\">${escHtml(c.trim())}<\/span>`).join('');\n    }\n\n    \/\/ R\u00e9initialiser et d\u00e9clencher le rendu pagin\u00e9\n    _tDevCurrentPage = 1;\n    _tDevRenderPage(1);\n\n    \/\/ Mettre \u00e0 jour les dropdowns mati\u00e8re et niveau\/classe selon le profil enseignant\n    updateTDevoirMatiereDropdown();\n    updateTDevoirClasseDropdown();\n\n    \/\/ Date par d\u00e9faut = demain\n    const tDate = document.getElementById('t-dev-date');\n    if (tDate && !tDate.value) tDate.value = getTomorrowSQLDate();\n\n    if (typeof updateScrollIndicators === 'function') updateScrollIndicators();\n}\n\n\/\/ \u2500\u2500 Pagination devoirs (espace enseignant) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet _tDevCurrentPage = 1;\nfunction _tDevRenderPage(page) {\n    const list = ouzoudSessionData ? (ouzoudSessionData.devoirs || []) : [];\n    const total = list.length;\n    const totalPages = Math.max(1, Math.ceil(total \/ PARENTS_PER_PAGE));\n    _tDevCurrentPage = Math.min(Math.max(1, page), totalPages);\n    const slice = list.slice((_tDevCurrentPage-1)*PARENTS_PER_PAGE, _tDevCurrentPage*PARENTS_PER_PAGE);\n    const myCode = (ouzoudSessionData && ouzoudSessionData.code_enseignant) ? ouzoudSessionData.code_enseignant : '';\n    let html = slice.map(d => {\n        let fileBtn = d.fichier_joint\n            ? `<button class=\"btn btn-outline btn-sm ouzoud-file-btn\" data-fileurl=\"${escHtml(d.fichier_joint)}\" style=\"height:26px;padding:2px 8px;font-size:11px;\">\ud83d\udcc4 Ouvrir<\/button>`\n            : '<span style=\"color:var(--muted);\">\u2014<\/span>';\n        const publiePar = d.publie_par && d.publie_par !== '' ? d.publie_par : 'Administration';\n        const isAdmin = publiePar === 'Administration';\n        const isMine = myCode && publiePar.includes(myCode);\n        const badgeStyle = isAdmin\n            ? 'background:#FEF3C7;color:#92400E;'\n            : 'background:#D1FAE5;color:#065F46;';\n        \/\/ Un enseignant ne peut supprimer que ses propres devoirs (ceux publi\u00e9s par l'admin sont en lecture seule)\n        const canDelete = isMine;\n        const deleteBtn = canDelete\n            ? `<button class=\"btn btn-outline btn-sm\" style=\"color:var(--red);\" onclick=\"deleteItemRecord('devoir', ${parseInt(d.id)})\">\ud83d\uddd1<\/button>`\n            : `<span style=\"color:var(--muted);font-size:11px;\" title=\"Publi\u00e9 par l'administration\">\u2014<\/span>`;\n        return `<tr>\n                <td data-label=\"Classe\"><span class=\"chip chip-blue\">${escHtml(d.classe)}<\/span><\/td>\n                <td data-label=\"Mati\u00e8re\">${escHtml(d.matiere)}<\/td>\n                <td data-label=\"Titre\"><b>${escHtml(d.titre)}<\/b><\/td>\n                <td data-label=\"Publi\u00e9 par\"><span style=\"font-size:11px;padding:2px 7px;border-radius:20px;font-weight:600;${badgeStyle}\">${escHtml(isAdmin ? 'Administration' : 'Moi')}<\/span><\/td>\n                <td data-label=\"Date Butoir\" class=\"date-cell\">${escHtml(formatToFrenchDate(d.date_butoir))}<\/td>\n                <td data-label=\"Document\">${fileBtn}<\/td>\n                <td data-label=\"Action\" style=\"white-space:nowrap;\">${deleteBtn}<\/td>\n            <\/tr>`;\n    }).join('');\n    const tbody = document.getElementById('t-table-devoirs');\n    if (tbody) tbody.innerHTML = html || '<tr><td colspan=\"7\" style=\"text-align:center;color:var(--muted);padding:20px;\">Aucun devoir publi\u00e9 pour le moment.<\/td><\/tr>';\n    _renderPaginationBar('t-devoirs', total, totalPages, _tDevCurrentPage, '_tDevRenderPage');\n    if (typeof updateScrollIndicators === 'function') updateScrollIndicators();\n}\n\n\/\/ \u2500\u2500 Recherche rapide d'\u00e9l\u00e8ve OU de classe (formulaire \"D\u00e9clarer une absence\") \u2500\u2500\nlet _absEleveSearchActiveIdx = -1;\nlet _absEleveSearchMatches = []; \/\/ \u00e9l\u00e9ments de type { type:'eleve', nom_enfant, classe } ou { type:'classe', classe, niveau, count }\n\nfunction filterAbsenceEleveSearch() {\n    const input = document.getElementById('abs-eleve-search');\n    const box = document.getElementById('abs-eleve-search-results');\n    if (!input || !box) return;\n    const q = input.value.trim().toLowerCase();\n    const allStudents = (ouzoudSessionData && ouzoudSessionData.tous_enfants) ? ouzoudSessionData.tous_enfants : [];\n    const classesConfig = (ouzoudSessionData && ouzoudSessionData.classes_config) ? ouzoudSessionData.classes_config : {};\n\n    if (!q) { box.classList.remove('open'); box.innerHTML = ''; _absEleveSearchMatches = []; _absEleveSearchActiveIdx = -1; return; }\n\n    \/\/ Correspondances par classe (ex: taper \"CM2\" trouve la\/les classe(s) CM2)\n    const classMatches = [];\n    for (const niveau in classesConfig) {\n        (classesConfig[niveau] || []).forEach(classe => {\n            if (classe.toLowerCase().includes(q)) {\n                const count = allStudents.filter(s => s.classe === classe).length;\n                classMatches.push({ type: 'classe', classe, niveau, count });\n            }\n        });\n    }\n\n    \/\/ Correspondances par nom d'\u00e9l\u00e8ve\n    const studentMatches = allStudents\n        .filter(s => (s.nom_enfant || '').toLowerCase().includes(q))\n        .map(s => ({ type: 'eleve', nom_enfant: s.nom_enfant, classe: s.classe }));\n\n    _absEleveSearchMatches = [...classMatches, ...studentMatches].slice(0, 30);\n    _absEleveSearchActiveIdx = -1;\n\n    if (_absEleveSearchMatches.length === 0) {\n        box.innerHTML = '<div class=\"eleve-search-empty\">Aucun \u00e9l\u00e8ve ni classe trouv\u00e9.<\/div>';\n    } else {\n        box.innerHTML = _absEleveSearchMatches.map((m, i) =>\n            m.type === 'classe'\n                ? `<div class=\"eleve-search-item\" data-idx=\"${i}\" onclick=\"selectAbsenceEleveFromSearch(${i})\">\n                        <span class=\"esi-name\">\ud83d\udcda Classe ${escHtml(m.classe)}<\/span>\n                        <span class=\"esi-classe\">${m.count} \u00e9l\u00e8ve(s)<\/span>\n                   <\/div>`\n                : `<div class=\"eleve-search-item\" data-idx=\"${i}\" onclick=\"selectAbsenceEleveFromSearch(${i})\">\n                        <span class=\"esi-name\">${escHtml(m.nom_enfant)}<\/span>\n                        <span class=\"esi-classe\">${escHtml(m.classe)}<\/span>\n                   <\/div>`\n        ).join('');\n    }\n    box.classList.add('open');\n}\n\nfunction absEleveSearchKeydown(evt) {\n    const box = document.getElementById('abs-eleve-search-results');\n    if (!box || !box.classList.contains('open') || _absEleveSearchMatches.length === 0) return;\n    const items = box.querySelectorAll('.eleve-search-item');\n    if (evt.key === 'ArrowDown') {\n        evt.preventDefault();\n        _absEleveSearchActiveIdx = Math.min(_absEleveSearchActiveIdx + 1, _absEleveSearchMatches.length - 1);\n        items.forEach((it, i) => it.classList.toggle('active', i === _absEleveSearchActiveIdx));\n        if (items[_absEleveSearchActiveIdx]) items[_absEleveSearchActiveIdx].scrollIntoView({ block: 'nearest' });\n    } else if (evt.key === 'ArrowUp') {\n        evt.preventDefault();\n        _absEleveSearchActiveIdx = Math.max(_absEleveSearchActiveIdx - 1, 0);\n        items.forEach((it, i) => it.classList.toggle('active', i === _absEleveSearchActiveIdx));\n        if (items[_absEleveSearchActiveIdx]) items[_absEleveSearchActiveIdx].scrollIntoView({ block: 'nearest' });\n    } else if (evt.key === 'Enter') {\n        evt.preventDefault();\n        if (_absEleveSearchActiveIdx >= 0) selectAbsenceEleveFromSearch(_absEleveSearchActiveIdx);\n        else if (_absEleveSearchMatches.length === 1) selectAbsenceEleveFromSearch(0);\n    } else if (evt.key === 'Escape') {\n        box.classList.remove('open');\n    }\n}\n\nfunction selectAbsenceEleveFromSearch(idx) {\n    const match = _absEleveSearchMatches[idx];\n    if (!match) return;\n\n    const niveauSel = document.getElementById('abs-niveau');\n    const classeSel = document.getElementById('abs-classe');\n    const studentSel = document.getElementById('abs-nom');\n\n    if (match.type === 'classe') {\n        \/\/ S\u00e9lection d'une classe enti\u00e8re : pr\u00e9-remplir niveau + classe et basculer en mode multiple\n        niveauSel.value = match.niveau;\n        updateAbsenceClasseDropdown();\n        classeSel.value = match.classe;\n        updateAbsencesStudentsDropdown();\n\n        const multiToggle = document.getElementById('abs-multi-toggle');\n        if (multiToggle && !multiToggle.checked) {\n            multiToggle.checked = true;\n            toggleAbsenceMultiMode();\n        }\n\n        document.getElementById('abs-eleve-search').value = match.classe;\n        document.getElementById('abs-eleve-search-results').classList.remove('open');\n        showToast(`Classe ${match.classe} s\u00e9lectionn\u00e9e \u2014 cochez les \u00e9l\u00e8ves concern\u00e9s ci-dessous.`, 'success');\n        return;\n    }\n\n    \/\/ S\u00e9lection d'un \u00e9l\u00e8ve pr\u00e9cis\n    const config = (ouzoudSessionData && ouzoudSessionData.classes_config) ? ouzoudSessionData.classes_config : {};\n    let niveauTrouve = '';\n    for (const niveau in config) {\n        if ((config[niveau] || []).includes(match.classe)) { niveauTrouve = niveau; break; }\n    }\n\n    if (niveauTrouve) {\n        niveauSel.value = niveauTrouve;\n        updateAbsenceClasseDropdown();\n        classeSel.value = match.classe;\n        updateAbsencesStudentsDropdown();\n        studentSel.value = match.nom_enfant;\n    }\n\n    document.getElementById('abs-eleve-search').value = match.nom_enfant;\n    document.getElementById('abs-eleve-search-results').classList.remove('open');\n    showToast(`${match.nom_enfant} s\u00e9lectionn\u00e9(e) \u2014 ${match.classe}.`, 'success');\n}\n\n\/\/ Ferme la liste de suggestions au clic en dehors du champ\ndocument.addEventListener('click', function(evt) {\n    const wrap = document.getElementById('abs-eleve-search');\n    const box = document.getElementById('abs-eleve-search-results');\n    if (!wrap || !box) return;\n    if (evt.target !== wrap && !box.contains(evt.target)) box.classList.remove('open');\n});\n\n\/\/ \u2500\u2500 M\u00e9morisation de la derni\u00e8re classe utilis\u00e9e (formulaire d'absence) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst ABS_LAST_CLASS_KEY = 'ouzoud_abs_last_niveau_classe';\n\nfunction saveLastAbsenceClass(niveau, classe) {\n    try {\n        \/\/ [FIX#2] : sessionStorage au lieu de localStorage \u2014 les donn\u00e9es ne persistent\n        \/\/ pas au-del\u00e0 de l'onglet\/session, \u00e9vitant toute fuite sur poste partag\u00e9.\n        if (niveau && classe) sessionStorage.setItem(ABS_LAST_CLASS_KEY, JSON.stringify({ niveau, classe }));\n    } catch (e) { \/* sessionStorage indisponible : on ignore silencieusement *\/ }\n}\n\nfunction getLastAbsenceClass() {\n    try {\n        const raw = sessionStorage.getItem(ABS_LAST_CLASS_KEY);\n        return raw ? JSON.parse(raw) : null;\n    } catch (e) { return null; }\n}\n\n\/\/ [FIX] : efface la m\u00e9morisation niveau\/classe \u2014 utilis\u00e9 lors d'une r\u00e9initialisation\n\/\/ compl\u00e8te du formulaire pour qu'un rechargement de page ne r\u00e9applique pas l'ancienne classe.\nfunction clearLastAbsenceClass() {\n    try { sessionStorage.removeItem(ABS_LAST_CLASS_KEY); } catch (e) { \/* sessionStorage indisponible : on ignore *\/ }\n}\n\nfunction restoreLastAbsenceClass() {\n    const last = getLastAbsenceClass();\n    if (!last || !last.niveau || !last.classe) return;\n    const niveauSel = document.getElementById('abs-niveau');\n    const classeSel = document.getElementById('abs-classe');\n    if (!niveauSel || !classeSel) return;\n    \/\/ Ne pas \u00e9craser une s\u00e9lection d\u00e9j\u00e0 en cours\n    if (niveauSel.value) return;\n    niveauSel.value = last.niveau;\n    updateAbsenceClasseDropdown();\n    if ([...classeSel.options].some(o => o.value === last.classe)) {\n        classeSel.value = last.classe;\n        updateAbsencesStudentsDropdown();\n    }\n}\n\n\/\/ \u2500\u2500 Mode \"saisie multiple\" (cocher plusieurs \u00e9l\u00e8ves d'une m\u00eame classe) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction toggleAbsenceMultiMode() {\n    const isMulti = document.getElementById('abs-multi-toggle').checked;\n    document.getElementById('abs-nom-single-wrap').style.display = isMulti ? 'none' : '';\n    document.getElementById('abs-nom-multi-wrap').style.display = isMulti ? '' : 'none';\n    document.getElementById('abs-submit-btn').textContent = isMulti ? 'Enregistrer les absences' : \"Enregistrer l'absence\";\n}\n\nfunction updateAbsenceMultiList(students) {\n    const list = document.getElementById('abs-multi-list');\n    if (!list) return;\n    if (!students || students.length === 0) {\n        list.innerHTML = '<div class=\"eleve-search-empty\">S\u00e9lectionnez d\\'abord une classe<\/div>';\n        updateAbsenceMultiCount();\n        return;\n    }\n    list.innerHTML = students.map((s, i) =>\n        `<label class=\"abs-multi-item\" id=\"abs-multi-item-${i}\">\n            <input type=\"checkbox\" value=\"${escHtml(s.nom_enfant)}\" onchange=\"onAbsenceMultiCheckChange(${i})\">\n            <span>${escHtml(s.nom_enfant)}<\/span>\n        <\/label>`\n    ).join('');\n    updateAbsenceMultiCount();\n}\n\nfunction onAbsenceMultiCheckChange(i) {\n    const item = document.getElementById('abs-multi-item-' + i);\n    if (item) item.classList.toggle('checked', item.querySelector('input').checked);\n    updateAbsenceMultiCount();\n}\n\nfunction toggleAllAbsenceCheckboxes(checked) {\n    const list = document.getElementById('abs-multi-list');\n    if (!list) return;\n    list.querySelectorAll('.abs-multi-item').forEach(item => {\n        item.querySelector('input').checked = checked;\n        item.classList.toggle('checked', checked);\n    });\n    updateAbsenceMultiCount();\n}\n\nfunction updateAbsenceMultiCount() {\n    const countEl = document.getElementById('abs-multi-count');\n    if (!countEl) return;\n    const list = document.getElementById('abs-multi-list');\n    const n = list ? list.querySelectorAll('input[type=\"checkbox\"]:checked').length : 0;\n    countEl.textContent = `${n} s\u00e9lectionn\u00e9(s)`;\n}\n\nfunction updateAbsenceClasseDropdown() {\n    const niveauSel = document.getElementById('abs-niveau');\n    const classeSel = document.getElementById('abs-classe');\n    const studentSel = document.getElementById('abs-nom');\n    const selectedNiveau = niveauSel ? niveauSel.value : '';\n    classeSel.innerHTML = '';\n    studentSel.innerHTML = '<option value=\"\" disabled selected>S\u00e9lectionnez d\\'abord une classe<\/option>';\n    studentSel.disabled = true;\n    if (!selectedNiveau) {\n        classeSel.innerHTML = '<option value=\"\" disabled selected>S\u00e9lectionnez d\\'abord un niveau<\/option>';\n        classeSel.disabled = true;\n        return;\n    }\n    const config = (ouzoudSessionData && ouzoudSessionData.classes_config) ? ouzoudSessionData.classes_config : {};\n    const classes = config[selectedNiveau] || [];\n    if (classes.length === 0) {\n        classeSel.innerHTML = '<option value=\"\" disabled selected>Aucune classe configur\u00e9e<\/option>';\n        classeSel.disabled = true;\n    } else {\n        classeSel.innerHTML = '<option value=\"\" disabled selected>-- Choisir la classe --<\/option>';\n        classes.forEach(c => {\n            let opt = document.createElement('option');\n            opt.value = c;\n            opt.textContent = c;\n            classeSel.appendChild(opt);\n        });\n        classeSel.disabled = false;\n    }\n}\n\nfunction updateAbsencesStudentsDropdown() {\n    let classSelect = document.getElementById('abs-classe');\n    let studentSelect = document.getElementById('abs-nom');\n    let niveauSelect = document.getElementById('abs-niveau');\n    let selectedClass = classSelect ? classSelect.value : '';\n    \n    studentSelect.innerHTML = '';\n    \n    if (!selectedClass) {\n        studentSelect.innerHTML = '<option value=\"\" disabled selected>S\u00e9lectionnez d\\'abord une classe<\/option>';\n        studentSelect.disabled = true;\n        updateAbsenceMultiList([]);\n        return;\n    }\n\n    \/\/ [M\u00e9morisation] sauvegarder le dernier niveau\/classe choisi pour le r\u00e9utiliser au prochain enregistrement\n    saveLastAbsenceClass(niveauSelect ? niveauSelect.value : '', selectedClass);\n    \n    let filteredStudents = [];\n    if (ouzoudSessionData && ouzoudSessionData.tous_enfants) {\n        filteredStudents = ouzoudSessionData.tous_enfants.filter(student => student.classe === selectedClass);\n    }\n    \n    if (filteredStudents.length === 0) {\n        studentSelect.innerHTML = '<option value=\"\" disabled selected>Aucun \u00e9l\u00e8ve trouv\u00e9 dans cette classe<\/option>';\n        studentSelect.disabled = true;\n    } else {\n        studentSelect.innerHTML = '<option value=\"\" disabled selected>-- Choisir l\\'\u00e9l\u00e8ve --<\/option>';\n        filteredStudents.forEach(student => {\n            let opt = document.createElement('option');\n            opt.value = student.nom_enfant;\n            opt.textContent = student.nom_enfant;\n            studentSelect.appendChild(opt);\n        });\n        studentSelect.disabled = false;\n    }\n    updateAbsenceMultiList(filteredStudents);\n}\n\nfunction openParentModal(id) {\n    if(id === 0) {\n        document.getElementById('p-id').value = 0; document.getElementById('p-nom').value = '';\n        document.getElementById('p-code-parent').value = '';\n        document.getElementById('p-email').value = ''; document.getElementById('p-enfants').value = '';\n        document.getElementById('mp-title').innerText = \"Ajouter un Parent\";\n    } else {\n        let item = ouzoudSessionData.parents.find(x => x.id == id);\n        document.getElementById('p-id').value = item.id; document.getElementById('p-nom').value = item.nom_complet;\n        document.getElementById('p-code-parent').value = item.code_parent;\n        document.getElementById('p-email').value = item.email;\n        \/\/ [FIX#6] : convertir le s\u00e9parateur | (serveur) en \", \" (formulaire) pour l'affichage\n        document.getElementById('p-enfants').value = item.enfants ? item.enfants.replace(\/\\|\/g, ', ') : '';\n        \/\/ [FIX#11 v4.3] : innerText (pas innerHTML) \u2014 les caract\u00e8res HTML de nom_complet\n        \/\/ ne sont PAS interpr\u00e9t\u00e9s. Ne jamais remplacer par innerHTML ici : risque XSS.\n        document.getElementById('mp-title').innerText = \"Modifier la fiche de \" + item.nom_complet;\n    }\n    const btnSave = document.querySelector('#m-parent-form .btn-blue');\n    if (btnSave) { btnSave.disabled = false; btnSave.textContent = 'Enregistrer'; }\n    document.getElementById('m-parent-form').style.display = 'flex';\n}\n\nfunction closeOuzoudModal(id) { document.getElementById(id).style.display = 'none'; }\n\n\/\/ Normalise un nom : supprime la civilit\u00e9 et les espaces multiples\nfunction normaliserNom(nom) {\n    return nom.replace(\/^(M\\.?|Mr\\.?|Mme\\.?|Dr\\.?|Prof\\.?)\\s+\/i, '').replace(\/\\s+\/g, ' ').trim().toLowerCase();\n}\n\nfunction submitParentForm() {\n    let nom = document.getElementById('p-nom').value.trim();\n    let enfants = document.getElementById('p-enfants').value.trim();\n    let currentId = parseInt(document.getElementById('p-id').value);\n\n    if (!nom || !enfants) {\n        showToast(\"Le Nom et les Enfants rattach\u00e9s sont obligatoires.\", 'error');\n        return;\n    }\n\n    \/\/ \u2500\u2500 D\u00e9tection de doublon avant envoi (uniquement pour un nouveau parent) \u2500\n    if (currentId === 0 && ouzoudSessionData && ouzoudSessionData.parents) {\n        const nomNorm = normaliserNom(nom);\n        const doublon = ouzoudSessionData.parents.find(p => {\n            const pNorm = normaliserNom(p.nom_complet || '');\n            return pNorm === nomNorm;\n        });\n        if (doublon) {\n            const msg = `\u26a0\ufe0f Un parent avec un nom similaire existe d\u00e9j\u00e0 :\n\n`\n                + `  \u2192 ${doublon.nom_complet}  (${doublon.code_parent})\n\n`\n                + `Voulez-vous mettre \u00e0 jour cette fiche existante\n`\n                + `au lieu d'en cr\u00e9er une nouvelle ?`;\n            if (!confirm(msg)) return; \/\/ l'admin annule\n            \/\/ Basculer en mode mise \u00e0 jour sur la fiche existante\n            document.getElementById('p-id').value = doublon.id;\n        }\n    }\n    \/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n    \/\/ Valider les classes contre la configuration serveur uniquement (ex: CP-1, CE2-3)\n    \/\/ \u2014 coh\u00e9rent avec ouzoud_is_valid_classe() c\u00f4t\u00e9 PHP qui n'accepte pas les niveaux bruts\n    const classesConfig = (ouzoudSessionData && ouzoudSessionData.classes_config) ? ouzoudSessionData.classes_config : {};\n    const classesValides = Object.values(classesConfig).flat();\n    if (classesValides.length === 0) {\n        showToast('Configuration des classes non charg\u00e9e. Rechargez la page et r\u00e9essayez.', 'error');\n        return;\n    }\n    const formatRegex = \/^.+\\(([^)]+)\\)$\/;\n    const partiesEnfants = enfants.split(',').filter(p => p.trim() !== '');\n    \/\/ [FIX#G] : limite 10 enfants c\u00f4t\u00e9 JS, coh\u00e9rente avec la validation serveur\n    if (partiesEnfants.length > 10) {\n        showToast('Nombre d\\'enfants trop \u00e9lev\u00e9. Maximum autoris\u00e9 : 10 par parent.', 'error');\n        return;\n    }\n    for (let partie of partiesEnfants) {\n        partie = partie.trim();\n        if (!partie) continue;\n        const match = partie.match(formatRegex);\n        if (!match) {\n            showToast('Format invalide : \"' + partie + '\". Format attendu : Pr\u00e9nom Nom (Classe), ex: Ali Benali (CP-1)', 'error');\n            return;\n        }\n        const niveau = match[1].trim();\n        if (!classesValides.includes(niveau)) {\n            showToast('Classe invalide : \"' + niveau + '\". Classes accept\u00e9es : ' + classesValides.join(', '), 'error');\n            return;\n        }\n    }\n\n    let p = new URLSearchParams();\n    p.append('action', 'ouzoud_ajax_save_data'); p.append('action_type', 'save_parent');\n    p.append('nonce', OuzoudAjax.nonce);\n    p.append('id', document.getElementById('p-id').value); p.append('nom', nom);\n    \/\/ code_parent g\u00e9n\u00e9r\u00e9 c\u00f4t\u00e9 serveur \u2014 pas besoin de l'envoyer\n    p.append('email', document.getElementById('p-email').value); p.append('enfants', enfants);\n\n    const btnSave = document.querySelector('#m-parent-form .btn-blue');\n    if (btnSave) { btnSave.disabled = true; btnSave.textContent = 'Enregistrement\u2026'; }\n\n    fetch(OuzoudAjax.url, { method: 'POST', body: p }).then(r => r.text()).then(text => {\n        let res; try { res = JSON.parse(text); } catch(e) { res = { success: false, data: 'Echec de l\\'enregistrement. Vous devez \u00eatre administrateur du Site web.' }; }\n        if(res.success) {\n            if(res.data && res.data.code_parent) {\n                if(res.data.is_new) {\n                    showToast('Parent cr\u00e9\u00e9. Code : ' + res.data.code_parent, 'success', 6000);\n                    \/\/ Affichage d\u00e9taill\u00e9 dans une fen\u00eatre modale l\u00e9g\u00e8re\n                    setTimeout(() => showToast('Code parent : ' + res.data.code_parent + ' \u2014 Communiquez-le au parent.', 'success', 10000), 100);\n                } else if(document.getElementById('p-id').value == 0) {\n                    showToast('Fiche mise \u00e0 jour. Code : ' + res.data.code_parent, 'info', 5000);\n                } else {\n                    showToast('Fiche parent mise \u00e0 jour.', 'success');\n                }\n            } else {\n                showToast('Fiche enregistr\u00e9e.', 'success');\n            }\n            closeOuzoudModal('m-parent-form'); loadApplicationWorkspace();\n        }\n        else if(res.data && res.data.message) {\n            showToast('Erreur : ' + res.data.message, 'error');\n            if (btnSave) { btnSave.disabled = false; btnSave.textContent = 'Enregistrer'; }\n        } else {\n            showToast(res.data || 'Echec de l\\'enregistrement. Vous devez \u00eatre administrateur du Site web.', 'error');\n            if (btnSave) { btnSave.disabled = false; btnSave.textContent = 'Enregistrer'; }\n        }\n    }).catch(() => {\n        if (btnSave) { btnSave.disabled = false; btnSave.textContent = 'Enregistrer'; }\n        showToast(\"Erreur r\u00e9seau. La fiche n'a pas pu \u00eatre enregistr\u00e9e.\", 'error');\n    });\n}\n\nfunction publishAbsence() {\n    \/\/ [FIX#7] : guard anti-double-soumission \u2014 coh\u00e9rent avec publishADevoir() et publishAnnonce().\n    \/\/ Sans ce guard, un double-clic rapide pouvait d\u00e9clencher deux lots parall\u00e8les (mode multi)\n    \/\/ ou deux requ\u00eates simultan\u00e9es (mode simple) avant que le bouton ne soit d\u00e9sactiv\u00e9 par\n    \/\/ la fonction appel\u00e9e.\n    const submitBtn = document.getElementById('abs-submit-btn');\n    if (submitBtn && submitBtn.disabled) return;\n\n    const isMulti = document.getElementById('abs-multi-toggle').checked;\n    if (isMulti) {\n        publishAbsencesMulti();\n    } else {\n        publishAbsenceSingle();\n    }\n}\n\nfunction publishAbsenceSingle() {\n    let targetClass = document.getElementById('abs-classe').value;\n    let nomEleve = document.getElementById('abs-nom').value;\n    \/\/ [FIX#2] : valider la date c\u00f4t\u00e9 client avant envoi\n    let absDate = document.getElementById('abs-date').value;\n\n    if (!targetClass || !nomEleve) {\n        showToast(\"Le niveau et le choix de l'\u00e9l\u00e8ve sont obligatoires.\", 'error');\n        return;\n    }\n    \/\/ [FIX#9] : m\u00eame correction que publishAbsencesMulti \u2014 le format valid\u00e9 est AAAA-MM-JJ.\n    if (!absDate || !\/^\\d{4}-\\d{2}-\\d{2}$\/.test(absDate)) {\n        showToast(\"Veuillez saisir une date valide (format AAAA-MM-JJ).\", 'error');\n        return;\n    }\n\n    \/\/ [FIX#3 v4.2] : v\u00e9rifier que l'\u00e9l\u00e8ve s\u00e9lectionn\u00e9 existe encore dans les donn\u00e9es\n    \/\/ courantes (un autre admin peut avoir modifi\u00e9 la liste entre temps)\n    if (ouzoudSessionData && ouzoudSessionData.tous_enfants) {\n        const eleveConnu = ouzoudSessionData.tous_enfants.some(\n            s => s.nom_enfant === nomEleve && s.classe === targetClass\n        );\n        if (!eleveConnu) {\n            showToast(\"Cet \u00e9l\u00e8ve n'est plus dans la liste. Actualisez la page avant de continuer.\", 'error');\n            updateAbsencesStudentsDropdown();\n            return;\n        }\n    }\n\n    let p = new URLSearchParams();\n    p.append('action', 'ouzoud_ajax_save_data'); p.append('action_type', 'save_absence');\n    p.append('nonce', OuzoudAjax.nonce);\n    p.append('nom_eleve', nomEleve); p.append('classe', targetClass);\n    p.append('date', document.getElementById('abs-date').value); p.append('periode', document.getElementById('abs-periode').value);\n    p.append('statut', document.getElementById('abs-statut').value); p.append('motif', document.getElementById('abs-motif').value);\n\n    const submitBtn = document.getElementById('abs-submit-btn');\n    if (submitBtn) submitBtn.disabled = true;\n\n    fetch(OuzoudAjax.url, { method: 'POST', body: p }).then(r => r.text()).then(text => {\n        let res; try { res = JSON.parse(text); } catch(e) { res = { success: false, data: null }; }\n        if(res.success) { \n            showToast('Absence enregistr\u00e9e.', 'success');\n            resetAbsenceFormAfterSave();\n            loadApplicationWorkspace(); \n        } else {\n            showToast(res.data || 'Echec de l\\'enregistrement. Vous devez \u00eatre administrateur du Site web.', 'error');\n        }\n    }).catch(() => {\n        showToast(\"Erreur r\u00e9seau. L'absence n'a pas pu \u00eatre enregistr\u00e9e.\", 'error');\n    }).finally(() => {\n        if (submitBtn) submitBtn.disabled = false;\n    });\n}\n\n\/\/ \u2500\u2500 Enregistrement multiple : un appel s\u00e9quentiel par \u00e9l\u00e8ve coch\u00e9 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction publishAbsencesMulti() {\n    const targetClass = document.getElementById('abs-classe').value;\n    const absDate = document.getElementById('abs-date').value;\n    const periode = document.getElementById('abs-periode').value;\n    const statut = document.getElementById('abs-statut').value;\n    const motif = document.getElementById('abs-motif').value;\n\n    if (!targetClass) {\n        showToast(\"Le niveau et la classe sont obligatoires.\", 'error');\n        return;\n    }\n    \/\/ [FIX#9] : le format attendu ET valid\u00e9 est AAAA-MM-JJ (valeur native du <input type=\"date\">),\n    \/\/ pas JJ\/MM\/AAAA comme indiqu\u00e9 par erreur dans le message pr\u00e9c\u00e9dent.\n    if (!absDate || !\/^\\d{4}-\\d{2}-\\d{2}$\/.test(absDate)) {\n        showToast(\"Veuillez saisir une date valide (format AAAA-MM-JJ).\", 'error');\n        return;\n    }\n\n    const checkedBoxes = [...document.querySelectorAll('#abs-multi-list input[type=\"checkbox\"]:checked')];\n    const nomsEleves = checkedBoxes.map(cb => cb.value);\n\n    if (nomsEleves.length === 0) {\n        showToast(\"Cochez au moins un \u00e9l\u00e8ve dans la liste.\", 'error');\n        return;\n    }\n\n    const submitBtn = document.getElementById('abs-submit-btn');\n    if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = `Enregistrement (0\/${nomsEleves.length})\u2026`; }\n\n    let successCount = 0;\n    let failCount = 0;\n\n    function saveNext(i) {\n        if (i >= nomsEleves.length) {\n            \/\/ Termin\u00e9 : afficher le bilan\n            if (failCount === 0) {\n                showToast(`${successCount} absence(s) enregistr\u00e9e(s) avec succ\u00e8s.`, 'success');\n            } else {\n                showToast(`${successCount} enregistr\u00e9e(s), ${failCount} \u00e9chec(s). V\u00e9rifiez et r\u00e9essayez si besoin.`, 'error');\n            }\n            resetAbsenceFormAfterSave();\n            if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Enregistrer les absences'; }\n            loadApplicationWorkspace();\n            return;\n        }\n\n        if (submitBtn) submitBtn.textContent = `Enregistrement (${i+1}\/${nomsEleves.length})\u2026`;\n\n        let p = new URLSearchParams();\n        p.append('action', 'ouzoud_ajax_save_data'); p.append('action_type', 'save_absence');\n        p.append('nonce', OuzoudAjax.nonce);\n        p.append('nom_eleve', nomsEleves[i]); p.append('classe', targetClass);\n        p.append('date', absDate); p.append('periode', periode);\n        p.append('statut', statut); p.append('motif', motif);\n\n        fetch(OuzoudAjax.url, { method: 'POST', body: p }).then(r => r.text()).then(text => {\n            let res; try { res = JSON.parse(text); } catch(e) { res = { success: false }; }\n            if (res.success) successCount++; else failCount++;\n        }).catch(() => {\n            failCount++;\n        }).finally(() => {\n            saveNext(i + 1);\n        });\n    }\n\n    saveNext(0);\n}\n\n\/\/ R\u00e9initialise le formulaire apr\u00e8s un enregistrement. Par d\u00e9faut, remet tout \u00e0 z\u00e9ro\n\/\/ (niveau, classe, \u00e9l\u00e8ve, dates, etc.) ; keepClass permet de conserver Niveau\/Classe\n\/\/ pr\u00e9-remplis pour faciliter la saisie de plusieurs absences cons\u00e9cutives sur la m\u00eame classe.\n\/\/ [FIX#6] : param\u00e8tre keepMulti supprim\u00e9 \u2014 il \u00e9tait d\u00e9clar\u00e9 mais jamais lu dans la fonction,\n\/\/ ce qui rendait la signature trompeuse. Le mode simple\/multiple est intentionnellement\n\/\/ conserv\u00e9 entre deux saisies (commentaire en fin de fonction), sans param\u00e8tre n\u00e9cessaire.\nfunction resetAbsenceFormAfterSave({ keepClass = false } = {}) {\n    if (!keepClass) {\n        const niveauAbsEl = document.getElementById('abs-niveau');\n        if(niveauAbsEl) niveauAbsEl.value = '';\n        document.getElementById('abs-classe').value = '';\n        document.getElementById('abs-classe').disabled = true;\n        updateAbsencesStudentsDropdown();\n        \/\/ [FIX] : effacer la m\u00e9morisation pour qu'un rechargement de page ne\n        \/\/ r\u00e9applique pas l'ancienne classe apr\u00e8s une r\u00e9initialisation compl\u00e8te.\n        clearLastAbsenceClass();\n    } else {\n        \/\/ Garder niveau + classe : juste r\u00e9initialiser le choix d'\u00e9l\u00e8ve \/ les cases coch\u00e9es\n        const studentSel = document.getElementById('abs-nom');\n        if (studentSel) studentSel.value = '';\n        toggleAllAbsenceCheckboxes(false);\n    }\n    document.getElementById('abs-date').value = getTodaySQLDate();\n    document.getElementById('abs-periode').value = 'matin';\n    document.getElementById('abs-statut').value = 'unjustified';\n    document.getElementById('abs-motif').value = '';\n    const eleveSearchEl = document.getElementById('abs-eleve-search');\n    if(eleveSearchEl) eleveSearchEl.value = '';\n    const eleveSearchResEl = document.getElementById('abs-eleve-search-results');\n    if(eleveSearchResEl) { eleveSearchResEl.classList.remove('open'); eleveSearchResEl.innerHTML = ''; }\n    \/\/ Le mode (simple\/multiple) n'est volontairement pas r\u00e9initialis\u00e9 : on respecte le choix de l'admin\n    \/\/ pour qu'il puisse encha\u00eener plusieurs saisies dans le m\u00eame mode.\n}\n\nfunction publishAnnonce() {\n    const btnSubmit = document.querySelector('[onclick=\"publishAnnonce()\"]');\n    \/\/ [FIX#19] : garde anti-double-soumission \u2014 coh\u00e9rent avec save_parent \/ save_absence\n    if (btnSubmit && btnSubmit.disabled) return;\n\n    let titre = document.getElementById('ann-titre').value.trim();\n    let fileInput = document.getElementById('ann-fichier');\n\n    if (!titre) {\n        showToast(\"Le titre de l'annonce est obligatoire.\", 'error');\n        return;\n    }\n\n    let formData = new FormData();\n    formData.append('action', 'ouzoud_ajax_save_data');\n    formData.append('action_type', 'save_annonce');\n    formData.append('nonce', OuzoudAjax.nonce);\n    formData.append('titre', titre);\n    formData.append('priorite', document.getElementById('ann-priorite').value);\n    formData.append('contenu', document.getElementById('ann-contenu').value);\n\n    if (fileInput.files.length > 0) {\n        let file = fileInput.files[0];\n        if (file.size > 2 * 1024 * 1024) {\n            showToast('Le fichier joint d\u00e9passe la limite de 2 Mo.', 'error');\n            return;\n        }\n        formData.append('ann_fichier', file);\n    }\n\n    \/\/ Verrouiller le bouton\n    if (btnSubmit) { btnSubmit.disabled = true; btnSubmit.textContent = '\u23f3 Publication en cours\u2026'; }\n\n    fetch(OuzoudAjax.url, { method: 'POST', body: formData })\n    .then(r => r.text())\n    .then(text => {\n        let res; try { res = JSON.parse(text); } catch(e) { res = { success: false, data: null }; }\n        \/\/ [FIX#19] : d\u00e9verrouiller dans tous les cas avant de traiter le r\u00e9sultat\n        if (btnSubmit) { btnSubmit.disabled = false; btnSubmit.textContent = 'Lancer la publication'; }\n        if(res.success) { \n            showToast('Annonce publi\u00e9e avec succ\u00e8s.', 'success');\n            document.getElementById('ann-titre').value = ''; \n            document.getElementById('ann-contenu').value = '';\n            \/\/ [FIX#X2] : r\u00e9initialiser la priorit\u00e9 au d\u00e9faut 'normale' apr\u00e8s publication\n            document.getElementById('ann-priorite').value = 'normale';\n            fileInput.value = ''; \n            loadApplicationWorkspace(); \n        } else {\n            showToast(res.data || 'Echec de l\\'enregistrement. Vous devez \u00eatre administrateur du Site web.', 'error');\n        }\n    })\n    \/\/ [FIX#5] : gestion des erreurs r\u00e9seau manquante sur publishAnnonce\n    .catch(() => {\n        if (btnSubmit) { btnSubmit.disabled = false; btnSubmit.textContent = 'Lancer la publication'; }\n        showToast(\"Erreur r\u00e9seau. L'annonce n'a pas pu \u00eatre publi\u00e9e.\", 'error');\n    });\n}\n\n\/\/ [FIX#22] : nettoyer les donn\u00e9es pour les bo\u00eetes de dialogue confirm() \u2014\n\/\/ le HTML n'est pas interpr\u00e9t\u00e9 dans confirm(), mais les backticks et guillemets\n\/\/ peuvent tronquer ou d\u00e9former le message dans certains moteurs JS. On normalise\n\/\/ en rempla\u00e7ant les caract\u00e8res de contr\u00f4le et les guillemets probl\u00e9matiques.\nfunction _safeConfirmStr(str) {\n    if (typeof str !== 'string') return String(str || '');\n    return str.replace(\/[\\u0000-\\u001F\\u007F]\/g, ' ').replace(\/`\/g, \"'\");\n}\n\nfunction deleteItemRecord(target, id) {\n    \/\/ [FIX#4] : message de confirmation informatif selon la cible\n    let confirmMsg = \"Confirmer la suppression d\u00e9finitive ?\";\n    if (target === 'parent' && ouzoudSessionData && ouzoudSessionData.parents) {\n        let p = ouzoudSessionData.parents.find(x => x.id == id);\n        if (p) {\n            \/\/ [FIX#22] : _safeConfirmStr appliqu\u00e9 sur toutes les valeurs dynamiques\n            confirmMsg = `Supprimer d\u00e9finitivement le parent \"${_safeConfirmStr(p.nom_complet)}\" (${_safeConfirmStr(p.code_parent)}) ?`\n                + (p.enfants ? `\\n\\nEnfants et absences associ\u00e9s supprim\u00e9s : ${_safeConfirmStr(p.enfants.replace(\/\\|\/g, ', '))}` : '')\n                + `\\n\\nCette action est irr\u00e9versible.`;\n        }\n    } else if (target === 'absence' && ouzoudSessionData && ouzoudSessionData.absences) {\n        let a = ouzoudSessionData.absences.find(x => x.id == id);\n        if (a) confirmMsg = `Supprimer l'absence de ${_safeConfirmStr(a.nom_eleve)} du ${formatToFrenchDate(a.date_absence)} ?`;\n    } else if (target === 'devoir' && ouzoudSessionData && ouzoudSessionData.devoirs) {\n        let d = ouzoudSessionData.devoirs.find(x => x.id == id);\n        if (d) confirmMsg = `Supprimer le devoir \"${_safeConfirmStr(d.titre)}\" (${_safeConfirmStr(d.classe)}) ?`;\n    } else if (target === 'annonce' && ouzoudSessionData && ouzoudSessionData.annonces) {\n        let a = ouzoudSessionData.annonces.find(x => x.id == id);\n        if (a) confirmMsg = `Supprimer l'annonce \"${_safeConfirmStr(a.titre)}\" ?`;\n    }\n    if(confirm(confirmMsg)) {\n        let p = new URLSearchParams();\n        p.append('action', 'ouzoud_ajax_delete_data');\n        p.append('nonce', OuzoudAjax.nonce);\n        p.append('target', target);\n        p.append('id', id);\n        fetch(OuzoudAjax.url, { method: 'POST', body: p }).then(r => r.text()).then(text => {\n            let res; try { res = JSON.parse(text); } catch(e) { res = { success: false, data: 'Echec de la suppression. Vous devez \u00eatre administrateur du Site web.' }; }\n            if(res.success) { showToast('Suppression effectu\u00e9e.', 'info'); loadApplicationWorkspace(); }\n            else { showToast(res.data || 'Echec de la suppression. Vous devez \u00eatre administrateur du Site web.', 'error'); }\n        }).catch(() => {\n            showToast(\"Erreur r\u00e9seau. La suppression n'a pas pu \u00eatre effectu\u00e9e.\", 'error');\n        });\n    }\n}\n\nfunction openCsvImportModal() {\n    document.getElementById('csv-file-input').value = '';\n    let resultDiv = document.getElementById('csv-import-result');\n    resultDiv.style.display = 'none';\n    resultDiv.innerHTML = '';\n    let btn = document.getElementById('btn-launch-import');\n    btn.disabled = false;\n    btn.innerText = '\ud83d\ude80 Lancer l\\'importation';\n    btn.onclick = function() { launchCsvImport(); };\n    document.getElementById('m-csv-import').style.display = 'flex';\n}\n\nfunction downloadCsvTemplate() {\n    let csv = 'nom_complet,code_parent,email,enfants\\n';\n    csv += 'Mme N. Benali,OUZ-' + new Date().getFullYear() + '-1234,benali@email.ma,\"Yassine Benali (CE2-1)|Nour Benali (PS-1)\"\\n';\n    csv += 'M. K. Alaoui,,alaoui@email.ma,\"Karim Alaoui (CM1-1)\"\\n';\n    csv += 'Mme F. Tazi,,,\\n';\n    let blob = new Blob([csv], { type: 'text\/csv;charset=utf-8;' });\n    let url = URL.createObjectURL(blob);\n    let a = document.createElement('a');\n    a.href = url;\n    a.download = 'modele_import_parents.csv';\n    document.body.appendChild(a);\n    a.click();\n    document.body.removeChild(a);\n    URL.revokeObjectURL(url);\n}\n\nfunction launchCsvImport() {\n    let fileInput = document.getElementById('csv-file-input');\n    if (!fileInput.files || fileInput.files.length === 0) {\n        showToast('Veuillez s\u00e9lectionner un fichier CSV.', 'error');\n        return;\n    }\n\n    let btn = document.getElementById('btn-launch-import');\n    btn.disabled = true;\n    btn.innerText = '\u23f3 Importation en cours...';\n\n    let formData = new FormData();\n    formData.append('action', 'ouzoud_ajax_import_csv');\n    formData.append('nonce', OuzoudAjax.nonce);\n    formData.append('csv_file', fileInput.files[0]);\n\n    fetch(OuzoudAjax.url, { method: 'POST', body: formData })\n    .then(r => r.text())\n    .then(text => {\n        let res;\n        try { res = JSON.parse(text); } catch(e) {\n            res = { success: false, data: 'Echec de l\\'importation. Vous devez \u00eatre administrateur du Site web.' };\n        }\n        let resultDiv = document.getElementById('csv-import-result');\n        resultDiv.style.display = 'block';\n        if (res.success) {\n            let d = res.data;\n            let errorsHtml = '';\n            \/\/ [FIX#8] : afficher les erreurs de lignes m\u00eame si l'import est globalement r\u00e9ussi\n            if (d.errors && d.errors.length > 0) {\n                errorsHtml = `<div style=\"margin-top:10px;padding:10px 12px;background:#FEF3C7;border:1px solid #FCD34D;border-radius:8px;\">\n                    <p style=\"font-weight:700;color:#92400E;margin-bottom:6px;\">\u26a0\ufe0f ${d.errors.length} ligne(s) avec erreur(s) :<\/p>\n                    <ul style=\"font-size:12px;color:#78350F;padding-left:16px;line-height:1.7;\">${d.errors.map(e => `<li>${escHtml(e)}<\/li>`).join('')}<\/ul>\n                <\/div>`;\n            }\n            resultDiv.innerHTML = `\n                <div style=\"padding:14px;background:#ECFDF5;border:1px solid #6EE7B7;border-radius:10px;\">\n                    <p style=\"font-weight:700;color:#065F46;margin-bottom:8px;\">\u2705 Importation r\u00e9ussie !<\/p>\n                    <div style=\"display:flex;gap:16px;flex-wrap:wrap;\">\n                        <span style=\"font-size:13px;color:#047857;\"><b>${d.inserted}<\/b> parent(s) ajout\u00e9(s)<\/span>\n                        <span style=\"font-size:13px;color:#0369A1;\"><b>${d.updated}<\/b> fiche(s) mise(s) \u00e0 jour<\/span>\n                        ${d.skipped > 0 ? `<span style=\"font-size:13px;color:var(--muted);\"><b>${d.skipped}<\/b> ligne(s) ignor\u00e9e(s)<\/span>` : ''}\n                        ${d.empty_lines > 0 ? `<span style=\"font-size:13px;color:var(--muted);\"><b>${d.empty_lines}<\/b> ligne(s) vide(s) ignor\u00e9e(s)<\/span>` : ''}\n                    <\/div>\n                <\/div>${errorsHtml}`;\n            \/\/ Renouveler le nonce apr\u00e8s l'import (l'op\u00e9ration WP peut l'invalider)\n            let nonceP = new URLSearchParams();\n            nonceP.append('action', 'ouzoud_ajax_refresh_nonce');\n            nonceP.append('nonce', OuzoudAjax.nonce);\n            fetch(OuzoudAjax.url, { method: 'POST', body: nonceP })\n                .then(r => r.json())\n                .then(nr => { if (nr.success && nr.data && nr.data.nonce) OuzoudAjax.nonce = nr.data.nonce; })\n                .catch(() => {})\n                .finally(() => loadApplicationWorkspace());\n            btn.disabled = false;\n            btn.innerText = '\u2705 Termin\u00e9';\n            btn.onclick = function() { closeOuzoudModal('m-csv-import'); };\n        } else {\n            resultDiv.innerHTML = `<div style=\"padding:14px;background:var(--red-light);border:1px solid #FCA5A5;border-radius:10px;\"><p style=\"font-weight:700;color:var(--red-dark);\">\u274c Erreur : ${escHtml(res.data || 'Echec de l\\'importation. Vous devez \u00eatre administrateur du Site web.')}<\/p><\/div>`;\n            btn.disabled = false;\n            btn.innerText = '\ud83d\ude80 Lancer l\\'importation';\n        }\n    })\n    .catch(() => {\n        btn.disabled = false;\n        btn.innerText = '\ud83d\ude80 Lancer l\\'importation';\n        showToast('Erreur r\u00e9seau lors de l\\'importation.', 'error');\n    });\n}\n\n\/\/ \u2500\u2500 Recherche dans la liste des parents \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction filterParentsTable(query) {\n    \/\/ [CORR Bug#B10] : normalisation des accents pour que \"benali\" trouve \"B\u00e9nali\"\n    function normalizeAccents(str) {\n        return str.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\n    }\n    query = normalizeAccents(query.trim());\n\n    if (!query) {\n        _parentFilteredList = null;\n    } else {\n        const all = ouzoudSessionData ? ouzoudSessionData.parents : [];\n        _parentFilteredList = all.filter(p => {\n            const text = normalizeAccents([p.nom_complet, p.code_parent, p.email, p.enfants].filter(Boolean).join(' '));\n            return text.includes(query);\n        });\n    }\n\n    _parentsRenderPage(1);\n}\n\n\/\/ \u2500\u2500 Export CSV de la liste des parents \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction exportParentsCsv() {\n    \/\/ [CORR Bug#B11] : recharger les donn\u00e9es depuis le serveur avant d'exporter\n    \/\/ pour \u00e9viter un export bas\u00e9 sur une snapshot obsol\u00e8te\n    let p = new URLSearchParams();\n    p.append('action', 'ouzoud_ajax_refresh_data');\n    p.append('nonce', OuzoudAjax.nonce);\n\n    fetch(OuzoudAjax.url, { method: 'POST', body: p })\n    .then(r => r.json())\n    .then(res => {\n        if (res.success) {\n            let savedMeta = {\n                _loginType: ouzoudSessionData ? ouzoudSessionData._loginType : null\n            };\n            ouzoudSessionData = res.data;\n            Object.assign(ouzoudSessionData, savedMeta);\n        }\n        _doExportParentsCsv();\n    })\n    .catch(() => {\n        showToast(\"Erreur r\u00e9seau. Export annul\u00e9.\", 'error');\n    });\n}\n\n\/\/ [SEC] Neutralise l'injection de formule CSV (Excel\/LibreOffice\/Google Sheets) :\n\/\/ si un champ commence par =, +, -, @, tabulation ou retour chariot, le tableur peut\n\/\/ l'interpr\u00e9ter comme une formule \u00e0 l'ouverture du fichier (risque RCE\/exfiltration\n\/\/ c\u00f4t\u00e9 poste de l'administrateur). On pr\u00e9fixe d'une apostrophe pour forcer un texte brut,\n\/\/ en plus de l'\u00e9chappement des guillemets d\u00e9j\u00e0 en place.\nfunction csvSafeField(val) {\n    let s = String(val || '').replace(\/\"\/g, '\"\"');\n    if (\/^[=+\\-@\\t\\r]\/.test(s)) s = \"'\" + s;\n    return s;\n}\n\nfunction _doExportParentsCsv() {\n    if (!ouzoudSessionData || !ouzoudSessionData.parents || ouzoudSessionData.parents.length === 0) {\n        showToast('Aucun parent \u00e0 exporter.', 'info');\n        return;\n    }\n\n    const sep = ';';\n    const bom = '\\uFEFF'; \/\/ BOM UTF-8 pour compatibilit\u00e9 Excel\n    let csv = bom + ['nom_complet', 'code_parent', 'email', 'enfants'].join(sep) + '\\n';\n\n    ouzoudSessionData.parents.forEach(p => {\n        const nom     = csvSafeField(p.nom_complet);\n        const code    = csvSafeField(p.code_parent);\n        const email   = csvSafeField(p.email);\n        \/\/ [FIX#6] : p.enfants est d\u00e9j\u00e0 au format \"Nom (Classe)|Nom2 (Classe2)\" c\u00f4t\u00e9 serveur\n        const enfants = csvSafeField(p.enfants);\n        csv += `\"${nom}\"${sep}\"${code}\"${sep}\"${email}\"${sep}\"${enfants}\"\\n`;\n    });\n\n    const blob = new Blob([csv], { type: 'text\/csv;charset=utf-8;' });\n    const url  = URL.createObjectURL(blob);\n    const a    = document.createElement('a');\n    const today = new Date();\n    const dateStr = `${today.getFullYear()}${String(today.getMonth()+1).padStart(2,'0')}${String(today.getDate()).padStart(2,'0')}`;\n    a.href     = url;\n    a.download = `parents_ecole_ouzoud_${dateStr}.csv`;\n    document.body.appendChild(a);\n    a.click();\n    document.body.removeChild(a);\n    URL.revokeObjectURL(url);\n    showToast(`${ouzoudSessionData.parents.length} parent(s) export\u00e9(s) en CSV.`, 'success');\n}\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\/\/ [FIX#4 v4.3] : gestionnaire d\u00e9l\u00e9gu\u00e9 pour les boutons \"Ouvrir fichier\".\n\/\/ R\u00e9sout le token \u00e9ph\u00e9m\u00e8re au moment du clic (pas \u00e0 la construction du DOM),\n\/\/ ce qui \u00e9vite d'exposer le nonce dans les URLs et les tokens trop en avance.\ndocument.addEventListener('click', async function(e) {\n    const btn = e.target.closest('.ouzoud-file-btn');\n    if (!btn) return;\n    e.preventDefault();\n    const rawUrl = btn.getAttribute('data-fileurl');\n    if (!rawUrl) return;\n    const origText = btn.textContent;\n    btn.disabled = true;\n    btn.textContent = '\u23f3';\n    const secureUrl = await buildSecureFileUrl(rawUrl);\n    btn.disabled = false;\n    btn.textContent = origText;\n    if (secureUrl) {\n        window.open(secureUrl, '_blank', 'noopener,noreferrer');\n    } else {\n        showToast('Impossible d\\'ouvrir le fichier. Veuillez r\u00e9essayer.', 'error');\n    }\n});\n\n\/\/ [FIX#20] : remet \u00e0 z\u00e9ro toutes les listes\/\u00e9tats en cache (pagination, filtres,\n\/\/ recherches rapides) pour \u00e9viter qu'une session suivante (m\u00eame r\u00f4le ou non)\n\/\/ n'affiche des pages, filtres ou r\u00e9sultats de recherche d'une session pr\u00e9c\u00e9dente.\nfunction _resetOuzoudCachedState() {\n    \/\/ Pagination espace parent\n    _pAbsCurrentPage = 1;\n    _pDevCurrentPage = 1;\n    _pAnnCurrentPage = 1;\n\n    \/\/ Pagination + filtre espace admin\n    _absCurrentPage = 1;\n    _annCurrentPage = 1;\n    _devCurrentPage = 1;\n    _parentCurrentPage = 1;\n    _parentFilteredList = null;\n\n    \/\/ Pagination espace enseignant\n    _tDevCurrentPage = 1;\n\n    \/\/ Recherche rapide classe (formulaires \"Publier un devoir\" admin & enseignant)\n    _classeSearchActiveIdx = {};\n    _classeSearchMatches = {};\n\n    \/\/ Recherche rapide \u00e9l\u00e8ve\/classe (formulaire \"D\u00e9clarer une absence\")\n    _absEleveSearchActiveIdx = -1;\n    _absEleveSearchMatches = [];\n\n    \/\/ Champ de recherche parents (admin) s'il est encore dans le DOM\n    const parentsSearchInput = document.getElementById('parents-search-input');\n    if (parentsSearchInput) parentsSearchInput.value = '';\n    const parentsSearchEmpty = document.getElementById('parents-search-empty');\n    if (parentsSearchEmpty) parentsSearchEmpty.style.display = 'none';\n\n    \/\/ Champ de recherche \u00e9l\u00e8ve\/classe (absences) s'il est encore dans le DOM\n    const absEleveSearch = document.getElementById('abs-eleve-search');\n    if (absEleveSearch) absEleveSearch.value = '';\n    const absEleveSearchResults = document.getElementById('abs-eleve-search-results');\n    if (absEleveSearchResults) { absEleveSearchResults.classList.remove('open'); absEleveSearchResults.innerHTML = ''; }\n}\n\nfunction triggerLogout() {\n    let p = new URLSearchParams();\n    p.append('action', 'ouzoud_ajax_logout');\n    p.append('nonce', OuzoudAjax.nonce);\n    fetch(OuzoudAjax.url, { method: 'POST', body: p }).catch(() => {});\n    ouzoudSessionData = null;\n    currentProfileType = 'parent';\n    _resetOuzoudCachedState();\n    clearParentField();\n    clearAdminField();\n    \/\/ Effacer le nom et les classes enseignant de la topbar pour ne pas les exposer \u00e0 la session suivante\n    const nameBarOut = document.getElementById('teacher-name-bar');\n    if (nameBarOut) nameBarOut.innerText = '';\n    const classesBarOut = document.getElementById('teacher-classes-bar');\n    if (classesBarOut) classesBarOut.innerHTML = '';\n    document.getElementById('ouzoud-parent-app').classList.remove('active');\n    document.getElementById('ouzoud-admin-app').classList.remove('active');\n    document.getElementById('ouzoud-teacher-app').classList.remove('active');\n    document.getElementById('ouzoud-login').style.display = 'flex';\n}\n\n\/\/ \u2500\u2500 Workspace Enseignant : remplir le dropdown Niveau selon les classes de l'enseignant \u2500\u2500\nfunction updateTDevoirNiveauDropdown() {\n    const niveauSel = document.getElementById('t-dev-niveau');\n    if (!niveauSel) return;\n    const config = (ouzoudSessionData && ouzoudSessionData.classes_config) ? ouzoudSessionData.classes_config : {};\n    const teacherClasses = (ouzoudSessionData && Array.isArray(ouzoudSessionData.classes) && ouzoudSessionData.classes.length > 0)\n        ? ouzoudSessionData.classes : null;\n    const ALL_NIVEAUX = ['PS','MS','GS','CP','CE1','CE2','CM1','CM2','6AP'];\n\n    \/\/ Niveaux ayant au moins une classe assign\u00e9e \u00e0 l'enseignant\n    const niveaux = ALL_NIVEAUX.filter(n => {\n        const cls = config[n] || [];\n        return teacherClasses ? cls.some(c => teacherClasses.includes(c)) : cls.length > 0;\n    });\n\n    niveauSel.innerHTML = '<option value=\"\" disabled selected>-- Choisir le niveau --<\/option>';\n    niveaux.forEach(n => {\n        const opt = document.createElement('option');\n        opt.value = n; opt.textContent = n;\n        niveauSel.appendChild(opt);\n    });\n}\n\n\/\/ \u2500\u2500 Workspace Enseignant : remplir le dropdown Mati\u00e8re selon les mati\u00e8res de l'enseignant \u2500\u2500\nfunction updateTDevoirMatiereDropdown() {\n    updateTDevoirNiveauDropdown();\n    const matSel = document.getElementById('t-dev-matiere');\n    if (!matSel) return;\n    \/\/ Utiliser les mati\u00e8res de la session (filtr\u00e9es sur cet enseignant si disponibles,\n    \/\/ sinon toutes les mati\u00e8res configur\u00e9es dans les R\u00e9glages WP)\n    const teacherMatieres = (ouzoudSessionData && Array.isArray(ouzoudSessionData.matieres) && ouzoudSessionData.matieres.length > 0)\n        ? ouzoudSessionData.matieres : null;\n    if (teacherMatieres) {\n        matSel.innerHTML = '';\n        teacherMatieres.forEach((m, idx) => {\n            const opt = document.createElement('option');\n            opt.value = m; opt.textContent = m;\n            if (idx === 0) opt.selected = true;\n            matSel.appendChild(opt);\n        });\n    } else {\n        \/\/ Fallback : toutes les mati\u00e8res configur\u00e9es (R\u00e9glages Espace Parents)\n        _buildMatiereOptions(matSel);\n    }\n}\n\n\/\/ \u2500\u2500 Workspace Enseignant : cascade niveau \u2192 classe (filtr\u00e9e sur les classes de l'enseignant) \u2500\u2500\nfunction updateTDevoirClasseDropdown() {\n    const niveauSel = document.getElementById('t-dev-niveau');\n    const classeSel = document.getElementById('t-dev-classe');\n    if (!niveauSel || !classeSel) return;\n    const selectedNiveau = niveauSel.value;\n    classeSel.innerHTML = '';\n    if (!selectedNiveau) {\n        classeSel.innerHTML = '<option value=\"\" disabled selected>S\u00e9lectionnez d\\'abord un niveau<\/option>';\n        classeSel.disabled = true;\n        return;\n    }\n    const config = (ouzoudSessionData && ouzoudSessionData.classes_config) ? ouzoudSessionData.classes_config : {};\n    const allClasses = config[selectedNiveau] || [];\n\n    \/\/ Filtrer selon les classes assign\u00e9es \u00e0 l'enseignant (si d\u00e9fini)\n    const teacherClasses = (ouzoudSessionData && Array.isArray(ouzoudSessionData.classes) && ouzoudSessionData.classes.length > 0)\n        ? ouzoudSessionData.classes : null;\n    const classes = teacherClasses ? allClasses.filter(c => teacherClasses.includes(c)) : allClasses;\n\n    if (classes.length === 0) {\n        classeSel.innerHTML = '<option value=\"\" disabled selected>Aucune classe assign\u00e9e pour ce niveau<\/option>';\n        classeSel.disabled = true;\n    } else {\n        classeSel.innerHTML = '<option value=\"\" disabled selected>-- Choisir la classe --<\/option>';\n        classes.forEach(c => {\n            let opt = document.createElement('option');\n            opt.value = c; opt.textContent = c;\n            classeSel.appendChild(opt);\n        });\n        classeSel.disabled = false;\n    }\n}\n\n\/\/ \u2500\u2500 Workspace Enseignant : charger l'historique des devoirs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadTeacherWorkspace() {\n    loadApplicationWorkspace(); \/\/ refresh ouzoudSessionData puis appelle renderTeacherPanels()\n}\n\n\/\/ \u2500\u2500 Workspace Enseignant : publier un devoir \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction publishTDevoir() {\n    const btnSubmit = document.querySelector('[onclick=\"publishTDevoir()\"]');\n    \/\/ [FIX#19] : garde anti-double-soumission\n    if (btnSubmit && btnSubmit.disabled) return;\n\n    const classes  = _getDevClasses('t-dev');\n    let matiere = _getDevMatiere('t-dev');\n    let titre   = document.getElementById('t-dev-titre').value.trim();\n    let devDate = document.getElementById('t-dev-date').value;\n    let fileInput = document.getElementById('t-dev-fichier');\n\n    const cbAll = document.getElementById('t-dev-all-classes');\n    const allClasses = cbAll && cbAll.checked;\n\n    if (!allClasses && classes.length === 0) { showToast(\"Le niveau et la classe sont obligatoires.\", 'error'); return; }\n    if (allClasses && classes.length === 0) { showToast(\"Aucune classe trouv\u00e9e pour ce niveau.\", 'error'); return; }\n    if (!matiere) { showToast(\"La mati\u00e8re est obligatoire.\", 'error'); return; }\n    if (!titre) { showToast(\"Le titre est obligatoire.\", 'error'); return; }\n    if (!devDate || !\/^\\d{4}-\\d{2}-\\d{2}$\/.test(devDate)) { showToast(\"Veuillez saisir une date de remise valide.\", 'error'); return; }\n\n    let file = null;\n    if (fileInput.files.length > 0) {\n        file = fileInput.files[0];\n        if (file.size > 2 * 1024 * 1024) { showToast('Le fichier d\u00e9passe la limite de 2 Mo.', 'error'); return; }\n    }\n\n    \/\/ Verrouiller le bouton\n    if (btnSubmit) { btnSubmit.disabled = true; btnSubmit.textContent = '\u23f3 Diffusion en cours\u2026'; }\n\n    const desc = document.getElementById('t-dev-desc').value;\n\n    function sendOne(classe, cb) {\n        let formData = new FormData();\n        formData.append('action', 'ouzoud_ajax_save_data');\n        formData.append('action_type', 'save_devoir');\n        formData.append('nonce', OuzoudAjax.nonce);\n        formData.append('classe', classe);\n        formData.append('matiere', matiere);\n        formData.append('titre', titre);\n        formData.append('desc', desc);\n        formData.append('date', devDate);\n        if (file) formData.append('dev_fichier', file);\n        fetch(OuzoudAjax.url, { method: 'POST', body: formData })\n            .then(r => r.text())\n            .then(text => { let res; try { res = JSON.parse(text); } catch(e) { res = { success: false }; } cb(res.success); })\n            .catch(() => cb(false));\n    }\n\n    let pending = classes.length;\n    let errors = 0;\n    classes.forEach(function(classe) {\n        sendOne(classe, function(ok) {\n            if (!ok) errors++;\n            pending--;\n            if (pending === 0) {\n                \/\/ [FIX#19] : d\u00e9verrouiller le bouton dans tous les cas\n                if (btnSubmit) { btnSubmit.disabled = false; btnSubmit.textContent = '\\u{1F4E4} Diffuser le devoir'; }\n                if (errors === 0) {\n                    showToast(classes.length > 1 ? 'Devoir diffus\u00e9 \u00e0 ' + classes.length + ' classes avec succ\u00e8s.' : 'Devoir publi\u00e9 avec succ\u00e8s.', 'success');\n                    document.getElementById('t-dev-niveau').value = '';\n                    document.getElementById('t-dev-classe').innerHTML = '<option value=\"\" disabled selected>S\u00e9lectionnez d\\'abord un niveau<\/option>';\n                    document.getElementById('t-dev-classe').disabled = true;\n                    document.getElementById('t-dev-matiere').value = '';\n                    const tAutreWrap = document.getElementById('t-dev-autre-wrap');\n                    if (tAutreWrap) { tAutreWrap.style.display = 'none'; document.getElementById('t-dev-matiere-autre').value = ''; }\n                    const cbAllEl = document.getElementById('t-dev-all-classes');\n                    if (cbAllEl) cbAllEl.checked = false;\n                    document.getElementById('t-dev-titre').value = 'Devoir maison';\n                    document.getElementById('t-dev-desc').value = '';\n                    document.getElementById('t-dev-date').value = getTomorrowSQLDate();\n                    fileInput.value = '';\n                    const tDevSearchEl = document.getElementById('t-dev-classe-search');\n                    if(tDevSearchEl) tDevSearchEl.value = '';\n                    const tDevSearchResEl = document.getElementById('t-dev-classe-search-results');\n                    if(tDevSearchResEl) { tDevSearchResEl.classList.remove('open'); tDevSearchResEl.innerHTML = ''; }\n                    loadTeacherWorkspace();\n                } else {\n                    showToast(errors + ' erreur(s) lors de l\\'envoi.', 'error');\n                }\n            }\n        });\n    });\n}\n\n\/\/ \u2500\u2500 Workspace Enseignant : supprimer un devoir \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction deleteTDevoir(id) {\n    if (!confirm('Supprimer ce devoir ?')) return;\n    let p = new URLSearchParams();\n    p.append('action', 'ouzoud_ajax_delete_data');\n    p.append('target', 'devoir');\n    p.append('nonce', OuzoudAjax.nonce);\n    p.append('id', id);\n    fetch(OuzoudAjax.url, { method: 'POST', body: p })\n    .then(r => r.json())\n    .then(res => {\n        if (res.success) { showToast('Devoir supprim\u00e9.', 'success'); loadTeacherWorkspace(); }\n        else showToast('\u00c9chec de la suppression.', 'error');\n    })\n    .catch(() => showToast('Erreur r\u00e9seau.', 'error'));\n}\n<\/script>[\/et_pb_fullwidth_code][\/et_pb_section][et_pb_section fb_built=\u00a0\u00bb1&Prime; _builder_version=\u00a0\u00bb4.27.6&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb background_color=\u00a0\u00bb#1964aa\u00a0\u00bb background_image=\u00a0\u00bbhttps:\/\/ecoleouzoud.ac.ma\/wp-content\/uploads\/2026\/05\/day_care_bg_02.png\u00a0\u00bb custom_padding=\u00a0\u00bb40px||40px|||\u00a0\u00bb locked=\u00a0\u00bboff\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][et_pb_row column_structure=\u00a0\u00bb1_3,1_3,1_3&Prime; _builder_version=\u00a0\u00bb4.27.6&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb custom_margin=\u00a0\u00bb|auto|3px|auto||\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][et_pb_column type=\u00a0\u00bb1_3&Prime; _builder_version=\u00a0\u00bb4.27.6&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][et_pb_code _builder_version=\u00a0\u00bb4.27.6&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb]<\/p>\n<div class=\"contact-col-infos\"><!-- [et_pb_line_break_holder] -->    <!-- [et_pb_line_break_holder] -->  <\/p>\n<div class=\"contact-card\"><!-- [et_pb_line_break_holder] -->    <span class=\"contact-icon\" aria-hidden=\"true\"><!-- [et_pb_line_break_holder] -->      <svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z\"><\/path><circle cx=\"12\" cy=\"10\" r=\"3\"><\/circle><\/svg><!-- [et_pb_line_break_holder] -->    <\/span><!-- [et_pb_line_break_holder] -->    <\/p>\n<div><!-- [et_pb_line_break_holder] -->      <\/p>\n<h4>Adresse<\/h4>\n<p><!-- [et_pb_line_break_holder] -->      <pee>64, 65 et 66 Dar Chaoui,<!\u2013- [et_pb_br_holder] -\u2013>Hay El Ousra, A\u00efn Chok,<!\u2013- [et_pb_br_holder] -\u2013>Casablanca, Maroc<\/pee><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] -->  <\/div>\n<p><!-- [et_pb_line_break_holder] -->  <\/p>\n<div class=\"contact-card\"><!-- [et_pb_line_break_holder] -->    <span class=\"contact-icon\" aria-hidden=\"true\"><!-- [et_pb_line_break_holder] -->      <svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z\"><\/path><\/svg><!-- [et_pb_line_break_holder] -->    <\/span><!-- [et_pb_line_break_holder] -->    <\/p>\n<div><!-- [et_pb_line_break_holder] -->      <\/p>\n<h4>T\u00e9l\u00e9phone<\/h4>\n<p><!-- [et_pb_line_break_holder] -->      <pee><a href=\"tel:0522523740\" class=\"contact-link\">0522 523 740<\/a><\/pee><!-- [et_pb_line_break_holder] -->      <pee class=\"fax\">Fax : 0522 219 384<\/pee><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] -->  <\/div>\n<p><!-- [et_pb_line_break_holder] -->  <\/p>\n<div class=\"contact-card\"><!-- [et_pb_line_break_holder] -->    <span class=\"contact-icon\" aria-hidden=\"true\"><!-- [et_pb_line_break_holder] -->      <svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z\"><\/path><polyline points=\"22,6 12,13 2,6\"><\/polyline><\/svg><!-- [et_pb_line_break_holder] -->    <\/span><!-- [et_pb_line_break_holder] -->    <\/p>\n<div><!-- [et_pb_line_break_holder] -->      <\/p>\n<h4>Email<\/h4>\n<p><!-- [et_pb_line_break_holder] -->      <pee><a href=\"mailto:ecoleouzoud@gmail.com\" class=\"contact-link\">ecoleouzoud@gmail.com<\/a><\/pee><!-- [et_pb_line_break_holder] -->    <\/div>\n<p><!-- [et_pb_line_break_holder] -->  <\/div>\n<p><!-- [et_pb_line_break_holder] --><\/div>\n<p><!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] --><\/p>\n<style><!-- [et_pb_line_break_holder] -->:root {<!-- [et_pb_line_break_holder] -->  --color-primary: #1D6E4F;<!-- [et_pb_line_break_holder] -->  --color-primary-light: #f4faf7;<!-- [et_pb_line_break_holder] -->  --color-primary-border: #ddeee7;<!-- [et_pb_line_break_holder] -->  --color-text: #333333;<!-- [et_pb_line_break_holder] -->  --color-text-muted: #666666;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.contact-col-infos {<!-- [et_pb_line_break_holder] -->  display: flex;<!-- [et_pb_line_break_holder] -->  flex-direction: column;<!-- [et_pb_line_break_holder] -->  gap: 12px;<!-- [et_pb_line_break_holder] -->  width: 100%;<!-- [et_pb_line_break_holder] -->  max-width: 460px;<!-- [et_pb_line_break_holder] -->  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;<!-- [et_pb_line_break_holder] -->  box-sizing: border-box;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.contact-card {<!-- [et_pb_line_break_holder] -->  display: flex;<!-- [et_pb_line_break_holder] -->  align-items: flex-start;<!-- [et_pb_line_break_holder] -->  gap: 16px;<!-- [et_pb_line_break_holder] -->  background: var(--color-primary-light);<!-- [et_pb_line_break_holder] -->  border-left: 4px solid var(--color-primary);<!-- [et_pb_line_break_holder] -->  border-radius: 8px;<!-- [et_pb_line_break_holder] -->  padding: 16px;<!-- [et_pb_line_break_holder] -->  box-sizing: border-box;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.contact-icon {<!-- [et_pb_line_break_holder] -->  color: var(--color-primary);<!-- [et_pb_line_break_holder] -->  display: flex;<!-- [et_pb_line_break_holder] -->  align-items: center;<!-- [et_pb_line_break_holder] -->  justify-content: center;<!-- [et_pb_line_break_holder] -->  flex-shrink: 0;<!-- [et_pb_line_break_holder] -->  margin-top: 2px;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.contact-card h4 {<!-- [et_pb_line_break_holder] -->  margin: 0 0 8px 0;<!-- [et_pb_line_break_holder] -->  font-size: 13px;<!-- [et_pb_line_break_holder] -->  text-transform: uppercase;<!-- [et_pb_line_break_holder] -->  letter-spacing: 0.05em;<!-- [et_pb_line_break_holder] -->  color: var(--color-primary);<!-- [et_pb_line_break_holder] -->  font-weight: 700;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.contact-card p {<!-- [et_pb_line_break_holder] -->  margin: 0;<!-- [et_pb_line_break_holder] -->  font-size: 14.5px;<!-- [et_pb_line_break_holder] -->  color: var(--color-text);<!-- [et_pb_line_break_holder] -->  line-height: 1.5;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.contact-link {<!-- [et_pb_line_break_holder] -->  color: var(--color-primary);<!-- [et_pb_line_break_holder] -->  text-decoration: none;<!-- [et_pb_line_break_holder] -->  font-weight: 600;<!-- [et_pb_line_break_holder] -->  transition: color 0.2s ease;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.contact-link:hover {<!-- [et_pb_line_break_holder] -->  text-decoration: underline;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><!-- [et_pb_line_break_holder] -->.fax {<!-- [et_pb_line_break_holder] -->  color: var(--color-text-muted) !important;<!-- [et_pb_line_break_holder] -->  font-size: 12.5px !important;<!-- [et_pb_line_break_holder] -->  margin-top: 4px !important;<!-- [et_pb_line_break_holder] -->}<!-- [et_pb_line_break_holder] --><\/style>\n<p>[\/et_pb_code][\/et_pb_column][et_pb_column type=\u00a0\u00bb1_3&Prime; _builder_version=\u00a0\u00bb4.27.6&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][\/et_pb_column][et_pb_column type=\u00a0\u00bb1_3&Prime; _builder_version=\u00a0\u00bb4.27.6&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][dsm_lottie lottie_url=\u00a0\u00bbhttps:\/\/ecoleouzoud.ac.ma\/wp-content\/uploads\/2026\/06\/Animation-1719808567444.json\u00a0\u00bb _builder_version=\u00a0\u00bb4.27.6&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb width=\u00a0\u00bb60%\u00a0\u00bb max_width=\u00a0\u00bb60%\u00a0\u00bb module_alignment=\u00a0\u00bbleft\u00a0\u00bb global_colors_info=\u00a0\u00bb{}\u00a0\u00bb][\/dsm_lottie][dsm_menu title=\u00a0\u00bbLiens rapides\u00a0\u00bb menu_id=\u00a0\u00bb15&Prime; menu_link_text_color=\u00a0\u00bb#FFFFFF\u00a0\u00bb list_style_color=\u00a0\u00bb#FFFFFF\u00a0\u00bb _builder_version=\u00a0\u00bb4.27.6&Prime; _module_preset=\u00a0\u00bbdefault\u00a0\u00bb header_font=\u00a0\u00bb&#8211;et_global_body_font|700|||||||\u00a0\u00bb header_text_color=\u00a0\u00bbrgba(255,255,255,0.78)\u00a0\u00bb header_font_size=\u00a0\u00bb20px\u00a0\u00bb menu_font=\u00a0\u00bb&#8211;et_global_heading_font|700|||||||\u00a0\u00bb menu_line_height=\u00a0\u00bb1.5em\u00a0\u00bb hover_enabled=\u00a0\u00bb0&Prime; header_text_shadow_style=\u00a0\u00bbpreset1&Prime; global_colors_info=\u00a0\u00bb{}\u00a0\u00bb list_style_type=\u00a0\u00bbsquare\u00a0\u00bb sticky_enabled=\u00a0\u00bb0&Prime;][\/dsm_menu][\/et_pb_column][\/et_pb_row][\/et_pb_section]<\/p>\n","protected":false},"excerpt":{"rendered":"<p><div class=\"et_pb_module dsm_lottie dsm_lottie_0\">\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t<div class=\"et_pb_module_inner\">\n\t\t\t\t\t<div class=\"dsm_lottie_wrapper\" data-percent-viewport=\"\" data-params=[{\"path\":\"https:\\\/\\\/ecoleouzoud.ac.ma\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/Animation-1719808567444.json\",\"loop\":true,\"loop_no\":\"\",\"delay\":\"0ms\",\"direction\":\"1\",\"speed\":\"1\",\"hover\":false,\"trigger\":\"on_hover\",\"scroll\":\"on_hover\",\"mouseout\":\"no_action\",\"viewport\":\"80%\"}]>\n\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t<\/div><div class=\"et_pb_module dsm_menu dsm_menu_0  et_pb_text_align_left et_pb_bg_layout_light\">\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t<div class=\"et_pb_module_inner\">\n\t\t\t\t\t<h4 class=\"dsm-menu-title et_pb_module_header\">Liens rapides<\/h4><nav class=\"dsm-menu-container\" role=navigation itemscope itemtype=http:\/\/schema.org\/SiteNavigationElement><ul id=\"menu-main-2\" class=\"dsm-menu dsm-menu-style-type-square dsm-menu-layout-vertical\"><li id=\"menu-item-260808\" class=\"et_pb_menu_page_id-home menu-item menu-item-type-post_type menu-item-object-page menu-item-home menu-item-260808\"><a href=\"https:\/\/ecoleouzoud.ac.ma\/\" itemprop=\"url\"><span itemprop=\"name\">Accueil<\/span><\/a><\/li>\n<li id=\"menu-item-260809\" class=\"et_pb_menu_page_id-22 menu-item menu-item-type-post_type menu-item-object-page menu-item-260809\"><a href=\"https:\/\/ecoleouzoud.ac.ma\/?page_id=22\" itemprop=\"url\"><span itemprop=\"name\">Espace Parents<\/span><\/a><\/li>\n<li id=\"menu-item-260810\" class=\"et_pb_menu_page_id-260793 menu-item menu-item-type-post_type menu-item-object-page menu-item-260810\"><a href=\"https:\/\/ecoleouzoud.ac.ma\/?page_id=260793\" itemprop=\"url\"><span itemprop=\"name\">R\u00e9glement int\u00e9rieur<\/span><\/a><\/li>\n<\/ul><\/nav>\n\t\t\t\t<\/div>\n\t\t\t<\/div><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_et_pb_use_builder":"on","_et_pb_old_content":"<!-- wp:shortcode -->\n[espace_parent]\n<!-- \/wp:shortcode -->","_et_gb_content_width":"","footnotes":""},"class_list":["post-22","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ecoleouzoud.ac.ma\/index.php?rest_route=\/wp\/v2\/pages\/22","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ecoleouzoud.ac.ma\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/ecoleouzoud.ac.ma\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/ecoleouzoud.ac.ma\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ecoleouzoud.ac.ma\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=22"}],"version-history":[{"count":14,"href":"https:\/\/ecoleouzoud.ac.ma\/index.php?rest_route=\/wp\/v2\/pages\/22\/revisions"}],"predecessor-version":[{"id":260844,"href":"https:\/\/ecoleouzoud.ac.ma\/index.php?rest_route=\/wp\/v2\/pages\/22\/revisions\/260844"}],"wp:attachment":[{"href":"https:\/\/ecoleouzoud.ac.ma\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=22"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}