/* =============================================================
   Nasser Al Badi — Portfolio · Drive CMS styles
   Loaded AFTER style.css. Purely ADDITIVE: every selector here is
   prefixed (dl- / gal- / cms-) and none of those prefixes exist in
   style.css, so nothing on the existing 8 routes can be affected.
   Design tokens (--bg, --surface, --border, --text, --accent, --gold,
   --radius, --shadow…) are REUSED, never redefined.
   No user-visible text is generated from CSS — all strings come from
   main.js through the bilingual { ar, en } shape.
   ============================================================= */

/* -------------------------------------------------------------
   MARKUP CONTRACT (what main.js is expected to emit)
   -------------------------------------------------------------
   DOWNLOADS PAGE  (route "downloads" → #page-downloads)
   <section class="page" id="page-downloads"><div class="section"><div class="container">
     <div class="section-head reveal">…</div>            <- existing house class
     <section class="dl-group reveal">
       <h3 class="dl-cat">
         <span class="dl-cat-ic">{ICONS.doc}</span>
         <span class="dl-cat-t">{t(category)}</span>
         <span class="dl-cat-n" dir="ltr">4</span>       <- optional count
       </h3>
       <div class="dl-grid">
         <article class="dl-card" data-ext="pdf">        <- data-ext drives the colour
           <div class="dl-card-top">
             <span class="dl-ext">PDF</span>             <- data-ext may be repeated here
             <span class="dl-size" dir="ltr">1.4 MB</span>
           </div>
           <h4 class="dl-title">{t(item.title)}</h4>
           <p class="dl-desc">{t(item.desc)}</p>
           <div class="dl-meta">{t(item.meta)}</div>     <- optional extra line
           <div class="dl-actions">
             <a class="dl-btn" href="{fileDownloadUrl}" download>{ICONS.download}{t(UI.cms.download)}</a>
             <a class="dl-btn-ghost" href="{filePreviewUrl}" target="_blank" rel="noopener">{ICONS.doc}{t(UI.cms.preview)}</a>
           </div>
         </article>
       </div>
     </section>
   </div></div></section>

   Recognised data-ext values (anything else falls back to --accent):
     pdf · doc docx rtf · xls xlsx csv · ppt pptx · zip rar 7z
     jpg jpeg png webp gif svg · txt md · mp4 mov · link

   GALLERY PAGE  (route "gallery" → #page-gallery)
   <div class="gal-grid">
     <figure class="gal-item" data-span="wide|tall">     <- data-span optional (masonry rhythm)
       <a class="gal-link" href="{filePreviewUrl}" data-lb="0">
         <span class="gal-fallback">{ICONS.star}</span>   <- sits UNDER the image
         <img class="gal-img" src="{imageUrl(id,900)}" alt="{t(title)}" loading="lazy"
              onerror="…thumbnail fallback… / this.closest('.gal-item').classList.add('is-broken')">
         <figcaption class="gal-cap">
           <b class="gal-cap-t">{t(title)}</b>
           <span class="gal-cap-s">{t(caption)}</span>
         </figcaption>
       </a>
     </figure>
   </div>

   LIGHTBOX (style only — main.js owns all behaviour)
   <div class="gal-lightbox" id="galLightbox" role="dialog" aria-modal="true">
     <div class="gal-lb-backdrop" data-lb-close></div>
     <figure class="gal-lb-stage">
       <img class="gal-lb-img" src="" alt="">
       <figcaption class="gal-lb-cap"><b>…</b><span>…</span></figcaption>
     </figure>
     <button class="gal-lb-close" data-lb-close>{ICONS.close}</button>
     <button class="gal-lb-nav" data-lb-dir="prev">{ICONS.arrow}</button>
     <button class="gal-lb-nav" data-lb-dir="next">{ICONS.arrow}</button>
     <span class="gal-lb-count" dir="ltr">3 / 12</span>
   </div>
   Open it with .is-open on .gal-lightbox (and .gal-lock on <body> to lock scroll).

   SHARED STATES (both pages)
   <div class="cms-loading"><div class="cms-sk-card">
        <span class="cms-skeleton" data-sk="badge"></span>
        <span class="cms-skeleton" data-sk="title"></span>
        <span class="cms-skeleton" data-sk="line"></span>
        <span class="cms-skeleton" data-sk="line-sm"></span>
        <span class="cms-skeleton" data-sk="btn"></span>
   </div>…</div>                                        <- data-sk: thumb|badge|title|line|line-sm|btn
   <div class="cms-empty">   <span class="cms-state-ic">…</span><b class="cms-state-t">…</b><p class="cms-state-p">…</p></div>
   <div class="cms-error" role="alert"><span class="cms-state-ic">…</span>
        <div class="cms-state-body"><b class="cms-state-t">…</b><p class="cms-state-p">…</p></div>
        <button class="cms-retry">…</button></div>

   SYNC BADGE (footer)
   <span class="cms-badge" data-state="live|cached|offline|loading" title="{t(UI.cms.syncedTitle)}">
     <span class="cms-badge-dot"></span>
     <span class="cms-badge-t">{t(UI.cms.synced)}</span>
     <time class="cms-badge-time" dir="ltr">10:24</time>
   </span>

   Strings main.js must add (bilingual, { ar, en }): download, preview, size,
   emptyTitle, emptyText, errorTitle, errorText, retry, synced, cached, offline, loading.

   OPTIONAL THEME OVERRIDES (a tenant may set these; all have safe fallbacks):
     --ext-any --ext-pdf --ext-doc --ext-sheet --ext-slide --ext-archive
     --ext-image --ext-text --ext-video --ext-link --cms-live --cms-warn
   ------------------------------------------------------------- */


/* ============================================================
   1 · DOWNLOADS PAGE
   ============================================================ */

/* ---------- Category heading ---------- */
.dl-group { margin-bottom: 44px; }
.dl-group:last-child { margin-bottom: 0; }

.dl-cat {
  display: flex; align-items: center; gap: 12px;
  font-size: 1.15rem; font-weight: 800; color: var(--text);
  margin-bottom: 18px; padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.dl-cat .dl-cat-ic {
  width: 38px; height: 38px; flex: none; border-radius: var(--radius-sm);
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  color: var(--accent);
}
.dl-cat .dl-cat-ic svg { width: 19px; height: 19px; }
.dl-cat .dl-cat-t { min-width: 0; }
.dl-cat .dl-cat-n {
  margin-inline-start: auto; flex: none;
  font-family: var(--ff-en); font-size: .78rem; font-weight: 700;
  /* --text-soft, not --muted: at .78rem this counts as small text, and
     --muted only reaches 3.42:1 on --surface-2 in the light theme. */
  color: var(--text-soft); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 999px; padding: 2px 10px;
}

/* ---------- Grid: 1 col mobile · 2 tablet · 3 desktop ---------- */
.dl-grid {
  display: grid; gap: 20px;
  grid-template-columns: 1fr;
  align-items: stretch;
}

/* ---------- Card ---------- */
.dl-card {
  /* default file-type colour — overridden per extension below */
  --ext: var(--ext-any, var(--accent));

  display: flex; flex-direction: column; gap: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 22px;
  box-shadow: var(--shadow);
  position: relative; overflow: hidden;
  transition: transform .25s, box-shadow .25s, border-color .25s;
}
/* thin tinted rail on the inline-start edge (RTL-safe) */
.dl-card::before {
  content: ""; position: absolute; inset-block: 0; inset-inline-start: 0;
  width: 3px; background: var(--ext); opacity: .75;
}
.dl-card:hover,
.dl-card:focus-within {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
  border-color: color-mix(in srgb, var(--ext) 45%, var(--border));
}

.dl-card-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* ---------- File-type badge ---------- */
.dl-ext {
  flex: none; display: inline-grid; place-items: center;
  min-width: 52px; padding: 5px 10px;
  border-radius: var(--radius-sm);
  font-family: var(--ff-en); font-size: .72rem; font-weight: 800;
  letter-spacing: .09em; text-transform: uppercase; line-height: 1.3;
  /* 48% and not 72%: pulled further towards --text so the WHOLE palette
     clears AA 4.5:1 against the tinted badge below. At 72% the slide,
     image, text and link hues measured 4.45 / 4.19 / 4.35 / 3.29:1. */
  color: color-mix(in srgb, var(--ext) 48%, var(--text));
  background: color-mix(in srgb, var(--ext) 15%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--ext) 34%, var(--border));
}
html[data-theme="dark"] .dl-ext {
  color: color-mix(in srgb, var(--ext) 55%, var(--text));
  background: color-mix(in srgb, var(--ext) 22%, var(--surface));
}

/* Extension palette — hex appears ONLY as a var() fallback.
   Put data-ext on .dl-card (preferred) and/or on .dl-ext.
   The blog badge (.blog-ext, section A7) is listed here too: it needs the same
   ten hues, and adding a selector to the rule that already holds them is the
   only way to share them — CSS cannot reference another rule's declarations,
   and a second copy of the palette would be a second thing to keep in step. */
.dl-card[data-ext="pdf"],  .dl-ext[data-ext="pdf"],   .blog-ext[data-ext="pdf"]  { --ext: var(--ext-pdf, #d0453b); }
.dl-card[data-ext="doc"],  .dl-ext[data-ext="doc"],
.dl-card[data-ext="docx"], .dl-ext[data-ext="docx"],
.dl-card[data-ext="rtf"],  .dl-ext[data-ext="rtf"],
.blog-ext[data-ext="doc"], .blog-ext[data-ext="docx"], .blog-ext[data-ext="rtf"]     { --ext: var(--ext-doc, #3a6ea8); }
.dl-card[data-ext="xls"],  .dl-ext[data-ext="xls"],
.dl-card[data-ext="xlsx"], .dl-ext[data-ext="xlsx"],
.dl-card[data-ext="csv"],  .dl-ext[data-ext="csv"],
.blog-ext[data-ext="xls"], .blog-ext[data-ext="xlsx"], .blog-ext[data-ext="csv"]     { --ext: var(--ext-sheet, #2f8f5b); }
.dl-card[data-ext="ppt"],  .dl-ext[data-ext="ppt"],
.dl-card[data-ext="pptx"], .dl-ext[data-ext="pptx"],
.blog-ext[data-ext="ppt"], .blog-ext[data-ext="pptx"]                              { --ext: var(--ext-slide, #cc6b33); }
.dl-card[data-ext="zip"],  .dl-ext[data-ext="zip"],
.dl-card[data-ext="rar"],  .dl-ext[data-ext="rar"],
.dl-card[data-ext="7z"],   .dl-ext[data-ext="7z"],
.blog-ext[data-ext="zip"], .blog-ext[data-ext="rar"], .blog-ext[data-ext="7z"]       { --ext: var(--ext-archive, #8a6bbf); }
.dl-card[data-ext="jpg"],  .dl-ext[data-ext="jpg"],
.dl-card[data-ext="jpeg"], .dl-ext[data-ext="jpeg"],
.dl-card[data-ext="png"],  .dl-ext[data-ext="png"],
.dl-card[data-ext="webp"], .dl-ext[data-ext="webp"],
.dl-card[data-ext="gif"],  .dl-ext[data-ext="gif"],
.dl-card[data-ext="svg"],  .dl-ext[data-ext="svg"],
.blog-ext[data-ext="jpg"], .blog-ext[data-ext="jpeg"], .blog-ext[data-ext="png"],
.blog-ext[data-ext="webp"], .blog-ext[data-ext="gif"], .blog-ext[data-ext="svg"]    { --ext: var(--ext-image, #2f97a8); }
.dl-card[data-ext="txt"],  .dl-ext[data-ext="txt"],
.dl-card[data-ext="md"],   .dl-ext[data-ext="md"],
.blog-ext[data-ext="txt"], .blog-ext[data-ext="md"]                                  { --ext: var(--ext-text, var(--muted)); }
.dl-card[data-ext="mp4"],  .dl-ext[data-ext="mp4"],
.dl-card[data-ext="mov"],  .dl-ext[data-ext="mov"],
.blog-ext[data-ext="mp4"], .blog-ext[data-ext="mov"]                                { --ext: var(--ext-video, #b0568f); }
.dl-card[data-ext="link"], .dl-ext[data-ext="link"],  .blog-ext[data-ext="link"] { --ext: var(--ext-link, var(--gold)); }

/* ---------- Card text ---------- */
.dl-size {
  margin-inline-start: auto;
  font-family: var(--ff-en); font-size: .78rem; font-weight: 600;
  /* --text-soft for the same AA reason as .dl-cat-n: --muted measures
     3.61:1 on the card's --surface in the light theme, this clears 7:1. */
  color: var(--text-soft); unicode-bidi: plaintext;
}
.dl-title {
  font-size: 1.05rem; font-weight: 800; line-height: 1.5; color: var(--text);
  overflow-wrap: anywhere;
}
.dl-desc {
  color: var(--text-soft); font-size: .94rem; margin: 0;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.dl-meta {
  display: flex; flex-wrap: wrap; gap: 4px 14px;
  font-size: .8rem; color: var(--muted); unicode-bidi: plaintext;
}

/* ---------- Actions ---------- */
.dl-actions {
  display: flex; flex-wrap: wrap; gap: 10px;
  margin-top: auto; padding-top: 14px;
}
.dl-btn,
.dl-btn-ghost {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 16px; border-radius: 11px;
  font-family: inherit; font-weight: 700; font-size: .88rem; line-height: 1.2;
  border: 1px solid transparent; cursor: pointer;
  transition: transform .2s, box-shadow .2s, background .2s, color .2s, border-color .2s;
}
.dl-btn svg,
.dl-btn-ghost svg { width: 17px; height: 17px; flex: none; }

.dl-btn {
  background: linear-gradient(145deg, var(--navy-600), var(--navy-900));
  color: #fff;
  box-shadow: 0 8px 20px color-mix(in srgb, var(--navy-900) 26%, transparent);
}
.dl-btn:hover { transform: translateY(-2px); box-shadow: 0 12px 26px color-mix(in srgb, var(--navy-900) 34%, transparent); }

.dl-btn-ghost {
  background: var(--surface); border-color: var(--border); color: var(--text-soft);
}
.dl-btn-ghost:hover { color: var(--accent); border-color: var(--accent); transform: translateY(-2px); }

.dl-btn:focus-visible,
.dl-btn-ghost:focus-visible { outline: 3px solid var(--ring); outline-offset: 2px; }
.dl-btn:active,
.dl-btn-ghost:active { transform: translateY(0); }


/* ============================================================
   2 · GALLERY PAGE
   ============================================================ */

.gal-grid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(min(250px, 100%), 1fr));
  grid-auto-flow: dense;              /* masonry-ish: spans backfill holes */
}

.gal-item {
  --gal-ratio: 4 / 3;
  position: relative; margin: 0;
  aspect-ratio: var(--gal-ratio);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  transition: transform .3s, box-shadow .3s, border-color .3s;
}
@supports not (aspect-ratio: 1 / 1) {
  .gal-item { min-height: 220px; }
}
.gal-item[data-span="wide"] { grid-column: span 2; --gal-ratio: 16 / 9; }
.gal-item[data-span="tall"] { grid-row: span 2;   --gal-ratio: 3 / 4; }

.gal-item:hover,
.gal-item:focus-within {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}

.gal-link { position: absolute; inset: 0; display: block; cursor: zoom-in; }
.gal-link:focus-visible { outline: 3px solid var(--ring); outline-offset: -3px; }

/* Image sits on top of the fallback placeholder (same pattern as .portrait-inner) */
.gal-img {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  transition: transform .5s ease, filter .3s ease;
  background: var(--bg-alt);
}
.gal-item:hover .gal-img { transform: scale(1.05); }

/* Graceful broken / not-yet-loaded image state */
.gal-fallback {
  position: absolute; inset: 0; z-index: 0;
  display: grid; place-items: center;
  color: color-mix(in srgb, var(--accent) 60%, var(--muted));
  background:
    repeating-linear-gradient(45deg,
      color-mix(in srgb, var(--border) 45%, transparent) 0 10px,
      transparent 10px 20px),
    var(--surface-2);
}
.gal-fallback svg { width: 34px; height: 34px; opacity: .65; }
.gal-item.is-broken .gal-img { display: none; }
.gal-item.is-broken { border-style: dashed; }
.gal-item.is-broken:hover { transform: none; box-shadow: var(--shadow); }

/* ---------- Caption overlay ---------- */
.gal-cap {
  position: absolute; inset-inline: 0; bottom: 0; z-index: 2;
  padding: 14px 16px;
  display: grid; gap: 3px;
  color: #fff;
  background: linear-gradient(to top,
    color-mix(in srgb, var(--navy-900) 92%, transparent),
    color-mix(in srgb, var(--navy-900) 55%, transparent) 55%,
    transparent);
  opacity: 0; transform: translateY(12px);
  transition: opacity .28s ease, transform .28s ease;
  text-align: start;
}
.gal-item:hover .gal-cap,
.gal-item:focus-within .gal-cap { opacity: 1; transform: none; }

.gal-cap-t { font-weight: 800; font-size: .96rem; line-height: 1.45; }
.gal-cap-s {
  font-size: .82rem; color: rgba(255, 255, 255, .82);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* Touch devices + small screens: caption is always visible (no hover to rely on) */
@media (hover: none) {
  .gal-cap { opacity: 1; transform: none; }
  .gal-item:hover .gal-img { transform: none; }
  .gal-link { cursor: pointer; }
}


/* ============================================================
   3 · LIGHTBOX (style only — behaviour lives in main.js)
   ============================================================ */

.gal-lightbox { position: fixed; inset: 0; z-index: 100; display: none; }
.gal-lightbox.is-open {
  display: grid; place-items: center;
  padding: clamp(12px, 4vw, 40px);
  animation: cms-fade .22s ease;
}
body.gal-lock { overflow: hidden; }

.gal-lb-backdrop {
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--navy-900) 88%, transparent);
  backdrop-filter: blur(6px) saturate(120%);
  cursor: zoom-out;
}

.gal-lb-stage {
  position: relative; z-index: 1; margin: 0;
  display: grid; gap: 14px; justify-items: center;
  width: min(1100px, 100%); max-height: 100%;
  animation: gal-zoom .26s cubic-bezier(.2, .7, .2, 1);
}
.gal-lb-img {
  max-width: 100%; max-height: 72vh;
  width: auto; height: auto;
  object-fit: contain;
  border-radius: var(--radius);
  background: var(--bg-alt);
  box-shadow: var(--shadow-lg);
}
.gal-lb-cap {
  display: grid; gap: 4px; text-align: center;
  color: #fff; max-width: 70ch;
}
.gal-lb-cap b { font-weight: 800; font-size: 1.02rem; }
.gal-lb-cap span { font-size: .88rem; color: rgba(255, 255, 255, .78); }

/* Controls */
.gal-lb-close,
.gal-lb-nav {
  position: absolute; z-index: 2;
  display: grid; place-items: center;
  width: 46px; height: 46px; border-radius: 14px;
  color: #fff;
  background: color-mix(in srgb, var(--navy-900) 55%, transparent);
  border: 1px solid rgba(255, 255, 255, .22);
  backdrop-filter: blur(6px);
  cursor: pointer; transition: background .2s, border-color .2s, transform .2s;
}
/* `hidden` has to beat the display value above: main.js hides both arrows
   when a gallery holds fewer than two photos, and a display declaration
   would otherwise leave two dead, still-focusable buttons on screen.
   Attribute selectors, so .gal-lb-close[hidden] works the same way. */
.gal-lb-close[hidden],
.gal-lb-nav[hidden] { display: none; }

.gal-lb-close svg,
.gal-lb-nav svg { width: 22px; height: 22px; }
.gal-lb-close:hover,
.gal-lb-nav:hover { background: color-mix(in srgb, var(--accent) 70%, transparent); border-color: transparent; }
.gal-lb-close:focus-visible,
.gal-lb-nav:focus-visible { outline: 3px solid var(--ring); outline-offset: 2px; }

.gal-lb-close { top: clamp(10px, 2.5vw, 26px); inset-inline-end: clamp(10px, 2.5vw, 26px); }
.gal-lb-nav  { top: 50%; transform: translateY(-50%); }
.gal-lb-nav[data-lb-dir="prev"] { inset-inline-start: clamp(6px, 2vw, 26px); }
.gal-lb-nav[data-lb-dir="next"] { inset-inline-end:   clamp(6px, 2vw, 26px); }
.gal-lb-nav:hover { transform: translateY(-50%) scale(1.06); }
/* The shared arrow glyph points inline-end; flip it for "prev" in both directions */
.gal-lb-nav[data-lb-dir="prev"] svg { transform: scaleX(-1); }
html[dir="rtl"] .gal-lb-nav svg { transform: scaleX(-1); }
html[dir="rtl"] .gal-lb-nav[data-lb-dir="prev"] svg { transform: none; }

.gal-lb-count {
  position: absolute; z-index: 2;
  bottom: clamp(8px, 2vw, 22px); inset-inline: 0; margin-inline: auto; width: fit-content;
  font-family: var(--ff-en); font-size: .8rem; font-weight: 700;
  color: rgba(255, 255, 255, .8);
  background: color-mix(in srgb, var(--navy-900) 55%, transparent);
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 999px; padding: 4px 12px;
}

@keyframes gal-zoom { from { opacity: 0; transform: scale(.965); } to { opacity: 1; transform: none; } }
@keyframes cms-fade { from { opacity: 0; } to { opacity: 1; } }


/* ============================================================
   4 · LOADING / EMPTY / ERROR STATES (shared by both pages)
   ============================================================ */

/* ---------- Skeletons ---------- */
.cms-loading { display: grid; gap: 20px; grid-template-columns: 1fr; }

.cms-sk-card {
  display: grid; gap: 12px; align-content: start;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 22px; box-shadow: var(--shadow);
}

.cms-skeleton {
  display: block; height: 14px; width: 100%;
  border-radius: 8px;
  background-color: var(--surface-2);
  background-image: linear-gradient(90deg,
    transparent 0%,
    color-mix(in srgb, var(--border) 75%, transparent) 45%,
    transparent 90%);
  background-size: 220% 100%;
  background-repeat: no-repeat;
  animation: cms-shimmer 1.5s linear infinite;
}
.cms-skeleton[data-sk="thumb"]   { height: auto; aspect-ratio: 4 / 3; border-radius: var(--radius); }
.cms-skeleton[data-sk="badge"]   { height: 26px; width: 62px; border-radius: var(--radius-sm); }
.cms-skeleton[data-sk="title"]   { height: 20px; width: 72%; }
.cms-skeleton[data-sk="line"]    { height: 12px; width: 100%; }
.cms-skeleton[data-sk="line-sm"] { height: 12px; width: 55%; }
.cms-skeleton[data-sk="btn"]     { height: 40px; width: 140px; border-radius: 11px; margin-top: 6px; }

@keyframes cms-shimmer { from { background-position: 120% 0; } to { background-position: -120% 0; } }
html[dir="rtl"] .cms-skeleton { animation-direction: reverse; }

/* ---------- Shared state bits ---------- */
.cms-state-ic {
  flex: none; display: grid; place-items: center;
  width: 54px; height: 54px; border-radius: 16px;
}
.cms-state-ic svg { width: 26px; height: 26px; }
.cms-state-t { display: block; font-weight: 800; font-size: 1.06rem; color: var(--text); line-height: 1.5; }
.cms-state-p { color: var(--text-soft); font-size: .94rem; margin: 6px 0 0; }
.cms-state-body { min-width: 0; }

/* ---------- Empty ---------- */
.cms-empty {
  display: grid; justify-items: center; gap: 8px; text-align: center;
  padding: 46px 26px;
  background: var(--surface);
  border: 1px dashed color-mix(in srgb, var(--accent) 35%, var(--border));
  border-radius: var(--radius);
}
.cms-empty .cms-state-ic {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
  margin-bottom: 6px;
}
.cms-empty .cms-state-p { max-width: 46ch; }
.cms-empty .dl-btn,
.cms-empty .dl-btn-ghost { margin-top: 14px; }

/* ---------- Error (soft warning, deliberately not alarming) ---------- */
.cms-error {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 20px 22px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--cms-warn, #d98b2b) 9%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--cms-warn, #d98b2b) 32%, var(--border));
  box-shadow: var(--shadow);
}
.cms-error .cms-state-ic {
  width: 44px; height: 44px; border-radius: 13px;
  background: color-mix(in srgb, var(--cms-warn, #d98b2b) 18%, transparent);
  color: color-mix(in srgb, var(--cms-warn, #d98b2b) 78%, var(--text));
}
.cms-error .cms-state-t { font-size: 1rem; }
.cms-error .cms-state-p { font-size: .9rem; }
.cms-error .cms-state-body { flex: 1 1 240px; }

.cms-retry {
  flex: none; font-family: inherit; font-weight: 700; font-size: .86rem;
  padding: 9px 16px; border-radius: 11px; cursor: pointer;
  color: color-mix(in srgb, var(--cms-warn, #d98b2b) 82%, var(--text));
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--cms-warn, #d98b2b) 42%, var(--border));
  transition: background .2s, color .2s, transform .2s;
}
.cms-retry:hover { background: color-mix(in srgb, var(--cms-warn, #d98b2b) 14%, var(--surface)); transform: translateY(-1px); }
.cms-retry:focus-visible { outline: 3px solid var(--ring); outline-offset: 2px; }

html[data-theme="dark"] .cms-error {
  background: color-mix(in srgb, var(--cms-warn, #d98b2b) 14%, var(--surface));
}
html[data-theme="dark"] .cms-error .cms-state-ic,
html[data-theme="dark"] .cms-retry { color: color-mix(in srgb, var(--cms-warn, #d98b2b) 52%, var(--text)); }


/* ============================================================
   5 · SYNC BADGE  ("content synced from Drive")
   ============================================================ */

.cms-badge {
  --cms-tone: var(--muted);
  display: inline-flex; align-items: center; gap: 8px;
  padding: 4px 12px; border-radius: 999px;
  font-size: .74rem; font-weight: 700; line-height: 1.6;
  color: var(--text-soft);
  background: var(--surface-2);
  border: 1px solid var(--border);
  white-space: nowrap; max-width: 100%;
}
.cms-badge .cms-badge-dot {
  width: 7px; height: 7px; flex: none; border-radius: 50%;
  background: var(--cms-tone);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cms-tone) 22%, transparent);
}
.cms-badge .cms-badge-t { overflow: hidden; text-overflow: ellipsis; }
.cms-badge .cms-badge-time { font-family: var(--ff-en); opacity: .7; unicode-bidi: plaintext; }

.cms-badge[data-state="live"]    { --cms-tone: var(--cms-live, #35c07f); }
.cms-badge[data-state="cached"]  { --cms-tone: var(--gold); }
.cms-badge[data-state="offline"] { --cms-tone: var(--muted); }
.cms-badge[data-state="loading"] { --cms-tone: var(--accent); }
.cms-badge[data-state="loading"] .cms-badge-dot { animation: cms-pulse 1.4s ease-in-out infinite; }
@keyframes cms-pulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }

/* On the footer's fixed navy band the badge goes translucent-light */
.footer .cms-badge {
  color: rgba(255, 255, 255, .68);
  background: rgba(255, 255, 255, .07);
  border-color: rgba(255, 255, 255, .14);
}


/* ============================================================
   6 · RESPONSIVE
   ============================================================ */

/* Tablet — 2 columns */
@media (min-width: 620px) {
  .dl-grid { grid-template-columns: repeat(2, 1fr); }
  .cms-loading { grid-template-columns: repeat(2, 1fr); }
}
/* Desktop — 3 columns */
@media (min-width: 980px) {
  .dl-grid { grid-template-columns: repeat(3, 1fr); }
  .cms-loading { grid-template-columns: repeat(3, 1fr); }
}

/* Small screens */
@media (max-width: 640px) {
  .gal-grid { gap: 12px; grid-template-columns: repeat(auto-fill, minmax(min(170px, 100%), 1fr)); }
  /* spans would overflow a 1–2 column grid */
  .gal-item[data-span="wide"] { grid-column: auto; --gal-ratio: 4 / 3; }
  .gal-item[data-span="tall"] { grid-row: auto;    --gal-ratio: 4 / 3; }
  /* no hover affordance worth waiting for — show the caption */
  .gal-cap { opacity: 1; transform: none; padding: 11px 13px; }
  .gal-lb-img { max-height: 60vh; }
  .gal-lb-nav { width: 40px; height: 40px; }
}

@media (max-width: 420px) {
  .dl-card { padding: 18px; }
  .dl-actions { gap: 8px; }
  .dl-btn, .dl-btn-ghost { flex: 1 1 100%; }
  .dl-cat { font-size: 1.04rem; gap: 10px; }
  .dl-cat .dl-cat-ic { width: 34px; height: 34px; }
  .cms-empty { padding: 34px 18px; }
  .cms-error { padding: 16px; gap: 12px; }
  .cms-retry { flex: 1 1 100%; }
  .gal-cap-s { -webkit-line-clamp: 1; }
  .gal-lb-count { font-size: .72rem; }
}


/* ============================================================
   7 · REDUCED MOTION
   (style.css already neutralises durations globally; these rules
    remove the animations outright so nothing flickers.)
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .cms-skeleton {
    animation: none !important;
    background-image: none;
    background-color: var(--surface-2);
  }
  .cms-badge[data-state="loading"] .cms-badge-dot { animation: none !important; opacity: .8; }
  .gal-lightbox.is-open,
  .gal-lb-stage { animation: none !important; }
  .dl-card:hover, .dl-card:focus-within,
  .gal-item:hover, .gal-item:focus-within,
  .dl-btn:hover, .dl-btn-ghost:hover, .cms-retry:hover { transform: none; }
  .gal-item:hover .gal-img { transform: none; }
  .gal-lb-nav:hover { transform: translateY(-50%); }
}


/* ============================================================
   8 · PRINT
   ============================================================ */
@media print {
  /* Interactive-only chrome disappears */
  .gal-lightbox,
  .cms-badge,
  .cms-loading,
  .cms-skeleton,
  .cms-retry,
  .dl-btn-ghost { display: none !important; }

  body.gal-lock { overflow: visible !important; }

  /* Download cards: flat, unbroken, and the file URL stays readable on paper */
  .dl-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .dl-group { margin-bottom: 18px; }
  .dl-card {
    break-inside: avoid; page-break-inside: avoid;
    box-shadow: none !important; transform: none !important;
    border: 1px solid #999; padding: 12px;
  }
  .dl-card::before { display: none; }
  .dl-desc { -webkit-line-clamp: unset; display: block; }
  .dl-actions { padding-top: 6px; }
  .dl-btn {
    background: none !important; box-shadow: none !important;
    color: inherit !important; padding: 0; font-size: .72rem; font-weight: 600;
  }
  .dl-btn svg { display: none; }
  .dl-btn::after { content: " — " attr(href); overflow-wrap: anywhere; font-weight: 400; }

  /* Gallery: captions always printed under the image.
     The screen layout is an aspect-ratio box with an absolutely-positioned
     image (z-index 1) and an absolutely-positioned caption on top of it.
     On paper the whole thing has to become normal flow, otherwise a static
     caption is painted UNDER the still-absolute image and clipped away by
     the item's aspect-ratio + overflow:hidden. */
  .gal-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .gal-item {
    break-inside: avoid; page-break-inside: avoid;
    box-shadow: none !important; transform: none !important; border: 1px solid #999;
    aspect-ratio: auto !important;   /* no fixed box — height follows content */
    overflow: visible !important;    /* the caption must not be clipped */
    min-height: 0 !important;
    padding: 0 0 6px;
  }
  .gal-link { position: static !important; }
  .gal-img {
    position: static !important; z-index: auto;
    width: 100%; height: auto; max-height: 300px; object-fit: contain;
  }
  .gal-fallback { display: none !important; }  /* decorative placeholder — no ink */
  .gal-cap {
    opacity: 1 !important; transform: none !important;
    background: none !important; color: #000 !important;
    position: static; padding: 6px 10px 0;
  }
  .gal-cap-s { color: #333 !important; -webkit-line-clamp: unset; display: block; }

  .cms-empty, .cms-error { box-shadow: none !important; border: 1px solid #999; }
}

/* ============================================================
   5 · SPARSE GALLERY
   With only one or two photos the auto-fill grid leaves the items
   stranded at the inline-start edge. main.js adds .is-sparse in that
   case so they sit centred at a sensible size instead.
   ============================================================ */
.gal-grid.is-sparse {
  grid-template-columns: repeat(auto-fit, minmax(260px, 380px));
  justify-content: center;
}

/* ============================================================
   6 · IN-CELL LINE BREAKS
   Editors are taught to press Ctrl/⌘+Enter for a new line inside a sheet
   cell (for bullet lists). In long free-text fields that newline should
   survive into the page instead of collapsing into one run-on line.
   ============================================================ */
.hero .lead,
.tl-intro,
.tl-note,
.dl-desc,
.comm-card p,
.card > p { white-space: pre-line; }

/* ============================================================
   7 · PUBLICATION LINK
   The Research page lists papers; those whose PDF is in Drive get a
   direct link beside the "Research paper" tag. Styled as a quiet
   secondary action so the titles stay the focus.
   ============================================================ */
.pub-meta {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 10px; margin-top: 8px;
}
/* style.css gives .pub .tag its own margin-top; the flex row owns spacing now. */
.pub .pub-meta .tag { margin-top: 0; }
.pub-link {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 999px;
  font-size: .8rem; font-weight: 700;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
  transition: background .2s, border-color .2s, color .2s;
}
.pub-link svg { width: 15px; height: 15px; }
.pub-link:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
.pub-link:focus-visible { outline: 3px solid var(--ring); outline-offset: 2px; }
@media print { .pub-link { display: none; } }


/* =============================================================
   ADDENDUM — 19 Aug 2026  (docs/SPEC-2026-08-19.md §6)
   Styles for the components the new pages need. Same rules as the
   rest of this file: tokens from style.css are REUSED and never
   redefined, every selector is a NEW class so no existing page can
   change, logical properties only (Arabic/RTL is the default), and
   nothing here produces user-visible text.
   Sections are lettered (A1…A9) because the numbering above is
   already used twice.
   -------------------------------------------------------------
   MARKUP CONTRACT (what main.js is expected to emit)

   A1 HERO           inside .hero-copy, after .role
     <div class="hero-position">{positionLine}</div>
     <a class="hero-orcid" href="{orcidUrl}" target="_blank" rel="noopener">
       <span class="orcid-mark">{ICONS.orcid}</span><span dir="ltr">0009-0005-7559-1727</span></a>

   A2 NEXT SECTION   last child of a CV page's .container
     <div class="sec-next">
       <a class="btn btn-ghost" href="#experience" data-route="experience">{icon}{label}{ICONS.arrow}</a>
     </div>

   A3 CERTIFICATES   page level, and inline on one Education row
     <div class="cert-row">                        <- the page-level wrapper
       <a class="cert-btn" href="{url}" target="_blank" rel="noopener">{ICONS.award}<span>{label}</span></a>
     </div>
     <a class="cert-btn cert-btn-sm" …>            <- the inline, per-row variant

   A4 PUBLICATIONS   published / unpublished groups
     <section class="pub-group">
       <h3 class="pub-group-t"><span class="pub-group-ic">{ICONS.book}</span>{label}
           <span class="pub-group-n" dir="ltr">4</span></h3>
       <div class="grid-2"> …existing .pub cards… </div>
     </section>
     …and inside a .pub card's .pub-meta, beside .pub-link:
     <a class="pub-cert" …>{ICONS.award}<span>{label}</span></a>

   A5 RESEARCH PROPOSAL  (#page-proposal)
     <ol class="prop-list">
       <li class="prop-item reveal">
         <span class="prop-n" dir="ltr">01</span>
         <div class="prop-body"><h3 class="prop-t">{title}</h3>
              <div class="prop-p">{body}</div>
              <a class="cert-btn" …>{attachment}</a></div>
       </li>
     </ol>

   A6 REFLECTIVE PHD JOURNEY  (#page-journey)
     the existing .timeline / .tl-item markup, plus
     <div class="jr-body">{body}</div>

   A7 BLOG  (#page-blog)
     <div class="blog-grid">
       <article class="blog-card reveal">
         <div class="blog-card-top">
           <span class="blog-ext" data-ext="pdf">PDF</span>          <- data-ext drives the hue
           <time class="blog-date" dir="ltr">…</time></div>
         <h3 class="blog-t">{title}</h3><p class="blog-x">{excerpt}</p>
         <div class="blog-body">{body}</div>
         <a class="dl-btn" …>{open}</a>                              <- existing button
       </article>
     </div>

   A8 NAV GROUP ("Research ▾") — desktop dropdown + drawer variant
     <div class="nav-group">
       <button class="nav-group-btn" aria-expanded="false" aria-controls="navGroupResearch">
         {label}{ICONS.chevronD}</button>
       <div class="nav-group-menu" id="navGroupResearch">
         <a href="#publications" data-route="publications">…</a> …
       </div>
     </div>
     open state: .nav-group.is-open  (aria-expanded="true" alone also works)
     drawer:     <div class="drawer-group"><span class="drawer-group-t">…</span>…</div>

   CONTRAST — every text/background pair below was measured in Chrome
   with getComputedStyle (color-mix serialises as color(srgb …)) and
   clears WCAG AA in BOTH themes. Where a token would have failed the
   chosen replacement is noted on the line.
   ============================================================= */


/* ============================================================
   A1 · HERO — position line + ORCID chip
   ============================================================ */

/* Sits under .role (the gold job title), one step quieter than it.
   pre-line so an editor's Ctrl+Enter line break survives, like .hero .lead. */
.hero-position {
  margin-block-start: 8px;
  font-size: 1.02rem; font-weight: 600; line-height: 1.6;
  /* --text-soft, not --muted: --muted is only 3.61:1 on --bg in the light
     theme, which fails AA for body text. This measures 7.41:1. */
  color: var(--text-soft);
  white-space: pre-line;
}

/* ORCID identifier chip. Shaped like .hero-badge but neutral, because the
   badge's accent colour would fight ORCID's green mark. */
.hero-orcid {
  /* Component-local, NOT a design token: it is the same green the ORCID mark
     paints itself with (ICONS.orcid hard-codes it, per ORCID's guidelines). */
  --orcid-green: #a6ce39;

  display: inline-flex; align-items: center; gap: 9px;
  margin-block-start: 14px;
  padding-block: 6px; padding-inline: 8px 14px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  /* 7.41:1 light / 8.27:1 dark on --surface */
  color: var(--text-soft);
  font-family: var(--ff-en); font-weight: 700; font-size: .84rem; line-height: 1.5;
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
  white-space: nowrap;
  transition: border-color .2s, color .2s, transform .2s, box-shadow .2s;
}
.hero-orcid:hover {
  color: var(--text);
  border-color: color-mix(in srgb, var(--orcid-green) 62%, var(--border));
  transform: translateY(-1px);
  box-shadow: var(--shadow-lg);
}
/* The mark is a logotype (green disc, white "iD") with its own fills, so it is
   exempt from the contrast rules and CSS only sizes it. */
.orcid-mark { flex: none; display: grid; place-items: center; inline-size: 20px; block-size: 20px; }
.orcid-mark svg { width: 20px; height: 20px; border-radius: 50%; display: block; }


/* ============================================================
   A2 · NEXT-SECTION BUTTON
   Closes every CV page: "continue to <next section>". Uses the house
   .btn .btn-ghost, so only the row around it is styled here.
   ============================================================ */
.sec-next {
  display: flex; flex-wrap: wrap; gap: 12px;
  justify-content: flex-end;          /* flex-end follows the writing direction */
  margin-block-start: 44px;
  padding-block-start: 26px;
  border-block-start: 1px dashed var(--border);
}
/* The shared arrow glyph points inline-end in LTR; flip it for RTL.
   Same trick the lightbox arrows use above. */
html[dir="rtl"] .sec-next .btn > svg:last-child { transform: scaleX(-1); }


/* ============================================================
   A3 · CERTIFICATE BUTTON
   "View certificates" — a gold-toned action, quieter than .btn-primary
   so it never competes with the page's own call to action.
   ============================================================ */
.cert-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 16px; border-radius: 11px;
  font-family: inherit; font-weight: 700; font-size: .88rem; line-height: 1.25;
  /* Bare --gold as text is 2.41:1 on white — a straight AA failure. Pulling it
     42% of the way towards --text keeps the gold read and measures 5.69:1
     light / 7.72:1 dark. The 12% tint is mixed with `transparent` so the same
     declaration is correct on --bg and on a card's --surface. */
  color: color-mix(in srgb, var(--gold) 42%, var(--text));
  background: color-mix(in srgb, var(--gold) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--gold) 38%, var(--border));
  transition: background .2s, border-color .2s, color .2s, transform .2s, box-shadow .2s;
}
.cert-btn svg { width: 17px; height: 17px; flex: none; }
.cert-btn:hover {
  background: var(--gold); border-color: var(--gold);
  /* white on gold is 2.41:1; --navy-900 on gold is 5.37:1 in both themes */
  color: var(--navy-900);
  transform: translateY(-2px);
  box-shadow: 0 10px 22px color-mix(in srgb, var(--gold) 30%, transparent);
}
.cert-btn:active { transform: translateY(0); }
.cert-btn:focus-visible { outline: 3px solid var(--ring); outline-offset: 2px; }

/* Inline, per-row variant (one Education qualification, one paper…): the pill
   metrics of .pub-link so a row of small links stays even. */
.cert-btn-sm {
  gap: 6px; padding: 5px 11px; border-radius: 999px;
  font-size: .8rem; line-height: 1.5;
}
.cert-btn-sm svg { width: 15px; height: 15px; }

/* The page-level button arrives wrapped in <div class="cert-row"> (main.js's
   certRow()), directly under the centred .section-head. A bare div would leave
   the button hugging the inline-start edge with nothing between it and the block
   below, so the wrapper carries the centring and the breathing room.
   The 26px start margin COLLAPSES with .section-head's own 48px bottom margin,
   so the button does not drift away from a heading it follows. */
.cert-row {
  display: flex; flex-wrap: wrap; gap: 12px;
  justify-content: center;
  margin-block: 26px;
}

/* Spacing where the button is dropped into existing blocks. The subject of
   each rule is the new class, so nothing existing can pick these up. */
.tl-item .cert-btn { margin-block-start: 12px; }
.section-head .cert-btn { margin-block-start: 18px; }


/* ============================================================
   A4 · PUBLICATIONS — published / unpublished groups
   Same anatomy as .dl-cat above (heading + icon + count) so the two
   grouped pages feel like one system.
   ============================================================ */
.pub-group { margin-block-end: 42px; }
.pub-group:last-child { margin-block-end: 0; }

.pub-group-t {
  display: flex; align-items: center; gap: 12px;
  font-size: 1.15rem; font-weight: 800; color: var(--text);
  margin-block-end: 18px; padding-block-end: 12px;
  border-block-end: 1px solid var(--border);
}
.pub-group-ic {
  inline-size: 38px; block-size: 38px; flex: none; border-radius: var(--radius-sm);
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  color: var(--accent);
}
.pub-group-ic svg { width: 19px; height: 19px; }
.pub-group-n {
  margin-inline-start: auto; flex: none;
  font-family: var(--ff-en); font-size: .78rem; font-weight: 700;
  /* --text-soft for the reason given on .dl-cat-n: --muted only reaches
     3.42:1 on --surface-2 in the light theme. This is 7.02:1. */
  color: var(--text-soft); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 999px; padding: 2px 10px;
  unicode-bidi: plaintext;
}

/* The per-paper participation certificate, beside .pub-link in .pub-meta.
   Gold where .pub-link is accent, so the two are told apart at a glance. */
.pub-cert {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 999px;
  font-size: .8rem; font-weight: 700; line-height: 1.5;
  color: color-mix(in srgb, var(--gold) 42%, var(--text));      /* 5.69:1 / 7.72:1 */
  background: color-mix(in srgb, var(--gold) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--gold) 38%, var(--border));
  transition: background .2s, border-color .2s, color .2s;
}
.pub-cert svg { width: 15px; height: 15px; flex: none; }
.pub-cert:hover { background: var(--gold); border-color: var(--gold); color: var(--navy-900); }
.pub-cert:focus-visible { outline: 3px solid var(--ring); outline-offset: 2px; }


/* ============================================================
   A5 · RESEARCH PROPOSAL  (#page-proposal)
   An ordered list of numbered sections. The numbers are supplied as
   text ("01"), so the <ol> marker is switched off — note that
   style.css resets `ul`, not `ol`.
   ============================================================ */
.prop-list { list-style: none; padding: 0; display: grid; gap: 18px; }

.prop-item {
  display: flex; align-items: flex-start; gap: 18px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 24px; box-shadow: var(--shadow);
  transition: border-color .25s, box-shadow .25s;
}
.prop-item:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); box-shadow: var(--shadow-lg); }

/* Latin digits inside dir="ltr". flex:none plus an explicit inline-size stops
   the flex line from squeezing "01" to nothing when the Arabic body text is
   long, and tabular-nums keeps 01/02/10 the same width. rem (not px) so the
   box grows with the user's font size. */
.prop-n {
  flex: none;
  inline-size: 2.75rem; block-size: 2.75rem;
  display: grid; place-items: center;
  border-radius: 12px;
  font-family: var(--ff-en); font-weight: 800; font-size: 1rem; line-height: 1;
  font-variant-numeric: tabular-nums; white-space: nowrap;
  /* white on the house navy gradient — 8.44:1 against its lightest stop */
  color: #fff;
  background: linear-gradient(145deg, var(--navy-600), var(--navy-900));
}
.prop-body { flex: 1 1 auto; min-inline-size: 0; }   /* min-inline-size: long words may wrap */
.prop-t { font-size: 1.12rem; font-weight: 800; line-height: 1.5; color: var(--text); }
.prop-p {
  margin-block-start: 8px; color: var(--text-soft);
  white-space: pre-line;              /* editors' in-cell line breaks survive */
  overflow-wrap: anywhere;
}
.prop-body .cert-btn { margin-block-start: 14px; }


/* ============================================================
   A6 · REFLECTIVE PHD JOURNEY  (#page-journey)
   Reuses .timeline / .tl-item wholesale; only the body paragraph is new.
   ============================================================ */
.jr-body {
  margin-block-start: 10px; color: var(--text-soft);
  white-space: pre-line; overflow-wrap: anywhere;
}


/* ============================================================
   A7 · BLOG  (#page-blog)
   ============================================================ */
.blog-grid { display: grid; gap: 22px; grid-template-columns: 1fr; }

.blog-card {
  display: flex; flex-direction: column; gap: 10px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 22px; box-shadow: var(--shadow);
  transition: transform .25s, box-shadow .25s, border-color .25s;
}
.blog-card:hover,
.blog-card:focus-within {
  transform: translateY(-3px); box-shadow: var(--shadow-lg);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
}
.blog-card-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* The file-type hues are SHARED with the downloads badge, not copied: section 1's
   `[data-ext] -> --ext` rules list `.blog-ext[data-ext=…]` alongside
   `.dl-ext[data-ext=…]`, so a plain <span class="blog-ext" data-ext="pdf"> — the
   markup the spec asks for — already has its hue. Only the badge FORMULA is
   restated below, because it lives in a different rule; the ten colours are
   defined exactly once, in section 1.
   The tint percentages are section 1's (48%/15%, 55%/22% dark): those are the
   values tuned so the whole palette clears AA 4.5:1 on the tinted chip.
   var(--ext, var(--muted)) and not a bare var(--ext): an extension the palette
   does not know leaves --ext undefined, which would make the whole colour-mix
   invalid at computed-value time and strip the chip back to bare text. The
   fallback gives it a neutral grey hue instead. */
.blog-ext {
  flex: none; display: inline-grid; place-items: center;
  min-inline-size: 52px; padding: 4px 9px;
  border-radius: var(--radius-sm);
  font-family: var(--ff-en); font-size: .7rem; font-weight: 800;
  letter-spacing: .09em; text-transform: uppercase; line-height: 1.35;
}
.blog-ext[data-ext] {
  color: color-mix(in srgb, var(--ext, var(--muted)) 48%, var(--text));
  background: color-mix(in srgb, var(--ext, var(--muted)) 15%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--ext, var(--muted)) 34%, var(--border));
}
html[data-theme="dark"] .blog-ext[data-ext] {
  color: color-mix(in srgb, var(--ext, var(--muted)) 55%, var(--text));
  background: color-mix(in srgb, var(--ext, var(--muted)) 22%, var(--surface));
}
/* No data-ext, or an extension the palette does not know (so --ext is unset):
   a neutral chip rather than an unstyled word. */
.blog-ext:not([data-ext]),
.blog-ext[data-ext=""] {
  color: var(--text-soft);            /* 7.02:1 light / 7.73:1 dark on --surface-2 */
  background: var(--surface-2);
  border: 1px solid var(--border);
}

.blog-date {
  margin-inline-start: auto;
  font-family: var(--ff-en); font-size: .78rem; font-weight: 600;
  color: var(--text-soft);            /* --muted would be 3.61:1 on --surface */
  unicode-bidi: plaintext;
}
.blog-t { font-size: 1.05rem; font-weight: 800; line-height: 1.5; color: var(--text); overflow-wrap: anywhere; }
.blog-x {
  color: var(--text-soft); font-size: .94rem; white-space: pre-line;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.blog-body {
  color: var(--text-soft); font-size: .94rem;
  white-space: pre-line; overflow-wrap: anywhere;
}
/* The card's own action is the existing .dl-btn; pin it to the bottom edge so a
   row of cards with different text lengths still lines its buttons up. */
.blog-card .dl-btn { margin-block-start: auto; align-self: flex-start; }


/* ============================================================
   A8 · NAV GROUP — "Research ▾"
   Twelve routes do not fit the header, so three of them live in a
   dropdown. Desktop only: below the burger breakpoint the drawer
   variant (further down) is used instead.

   STACKING — .nav is `position: sticky; z-index: 60` AND has a
   backdrop-filter, so it is its own stacking context: the menu can
   never escape it, and everything inside is judged only against its
   siblings. z-index: 65 is therefore plenty; it is deliberately still
   below the drawer backdrop (70), the drawer (80) and the lightbox
   (100) so an overlay always covers the header. Nothing on the way up
   sets overflow, so the menu is not clipped — it simply paints over
   the page, which sits below the header's 60.
   ============================================================ */
.nav-group { position: relative; display: flex; align-items: center; }

.nav-group-btn {
  /* the metrics of .nav-links a, so the group sits in the row unnoticed */
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 14px; border-radius: 10px;
  font-weight: 600; font-size: .93rem; line-height: 1.6;
  color: var(--text-soft);
  transition: color .2s, background .2s;
}
.nav-group-btn:hover { color: var(--text); background: var(--surface-2); }
.nav-group-btn:focus-visible { outline: 3px solid var(--ring); outline-offset: 2px; }
.nav-group-btn svg { width: 16px; height: 16px; flex: none; }
/* Chevron points down when closed, up when open (rotation is symmetrical, so
   it needs no RTL variant). */
.nav-group.is-open > .nav-group-btn svg,
.nav-group-btn[aria-expanded="true"] svg { transform: rotate(180deg); }
/* The button carries the open state too, so it reads as pressed. */
.nav-group.is-open > .nav-group-btn,
.nav-group-btn[aria-expanded="true"] { color: var(--text); background: var(--surface-2); }
/* Decorative only, and :has() is not needed for the menu to work: when one of
   the grouped routes is current, tint the trigger as well. */
.nav-group.is-active > .nav-group-btn,
.nav-group:has(.nav-group-menu a.active) > .nav-group-btn {
  color: color-mix(in srgb, var(--accent) 62%, var(--text));   /* 5.34:1 light / 6.32:1 dark */
}

.nav-group-menu {
  position: absolute; z-index: 65;
  /* Anchored to the button, not to the viewport: inset-inline-start flips with
     the direction on its own, and 16px is what it takes for the card to clear
     the header's bottom rule (measured: the link row is 40px tall inside the
     68px --nav-h, so the button's bottom edge sits 14px above it). Measured
     from the button, so the relationship survives page zoom. */
  top: calc(100% + 16px);
  inset-inline-start: 0;
  inline-size: max-content;
  min-inline-size: 100%;
  /* rem first so the menu grows with the user's font size; the vw term is only
     a guard so a menu near the viewport edge can never be cut off by
     `body { overflow-x: hidden }`. */
  max-inline-size: min(22rem, calc(100vw - 2rem));
  display: grid; gap: 2px;
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  /* CLOSED: visibility:hidden takes it out of the accessibility tree and out of
     the tab order; pointer-events:none is belt and braces for the fade. */
  visibility: hidden; opacity: 0; pointer-events: none;
  transform: translateY(-6px);
}
/* Either hook opens it: main.js's .is-open, or aria-expanded on its own. */
.nav-group.is-open > .nav-group-menu,
.nav-group-btn[aria-expanded="true"] + .nav-group-menu {
  visibility: visible; opacity: 1; pointer-events: auto; transform: none;
}

/* Menu rows. Two classes deep so they beat `.nav-links a` from style.css
   without depending on file order. */
.nav-group .nav-group-menu a {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border-radius: 10px;
  font-size: .9rem; font-weight: 600; line-height: 1.6;
  color: var(--text-soft);
  background: none;
}
/* The route icons main.js puts in front of each label are ICONS.* SVGs, which
   carry a viewBox but no width/height — unsized they would each claim the whole
   row (measured: 68px tall). 17px matches .cert-btn's icon; the drawer's own
   rule (.drawer a svg) already covers the mobile variant. */
.nav-group .nav-group-menu a svg { width: 17px; height: 17px; flex: none; opacity: .8; }
.nav-group .nav-group-menu a:hover { color: var(--text); background: var(--surface-2); }
.nav-group .nav-group-menu a:hover svg { opacity: 1; }
.nav-group .nav-group-menu a.active {
  /* --accent alone is 4.31:1 on white — below AA for this text size. Mixed 62%
     into --text it measures 5.34:1 on the tinted row, and the tint carries the
     state a second way for anyone who cannot see the hue. */
  color: color-mix(in srgb, var(--accent) 62%, var(--text));
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
/* style.css draws an underline bar under the current top-level link; inside a
   dropdown row that bar makes no sense. */
.nav-group .nav-group-menu a.active::after { content: none; }

/* Animate only when the reader has not asked for less motion. */
@media (prefers-reduced-motion: no-preference) {
  .nav-group-menu {
    transition: opacity .18s ease, transform .18s ease, visibility .18s linear;
  }
  .nav-group-btn svg { transition: transform .2s ease; }
}

/* Below the burger breakpoint the header row is gone; the drawer variant takes
   over. Stated explicitly so the group disappears even if it is emitted as a
   direct child of .nav-inner rather than inside .nav-links. */
@media (max-width: 1080px) {
  .nav-group { display: none; }
}

/* ---------- Drawer variant (mobile) ----------
   A flat labelled group: no disclosure, everything visible, so there is
   nothing to open with a thumb. */
.drawer-group {
  display: grid; gap: 2px;
  margin-block: 6px;
  padding-inline-start: 12px;
  border-inline-start: 2px solid var(--border);
}
.drawer-group-t {
  padding-block: 8px 2px; padding-inline: 14px 0;
  font-size: .74rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  /* --muted is 3.61:1 on --bg and this is small text, so --text-soft (7.41:1). */
  color: var(--text-soft);
}


/* ============================================================
   A9 · RESPONSIVE · REDUCED MOTION · PRINT (addendum components)
   ============================================================ */

/* Blog: 1 column mobile · 2 tablet · 3 desktop, matching .dl-grid. */
@media (min-width: 620px) { .blog-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 980px) { .blog-grid { grid-template-columns: repeat(3, 1fr); } }

@media (max-width: 640px) {
  .sec-next { justify-content: stretch; margin-block-start: 34px; padding-block-start: 20px; }
  .sec-next .btn { flex: 1 1 100%; justify-content: center; }
}
@media (max-width: 460px) {
  .hero-orcid { font-size: .8rem; padding-inline: 7px 12px; }
  .prop-item { gap: 12px; padding: 18px; }
  .prop-n { inline-size: 2.25rem; block-size: 2.25rem; font-size: .9rem; }
  .prop-t { font-size: 1.05rem; }
  .blog-card { padding: 18px; }
  .blog-card .dl-btn { align-self: stretch; }
  .pub-group-t { font-size: 1.04rem; gap: 10px; }
  .pub-group-ic { inline-size: 34px; block-size: 34px; }
  .cert-btn { inline-size: 100%; }          /* full-width tap target */
  .cert-btn-sm { inline-size: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-orcid:hover,
  .cert-btn:hover,
  .blog-card:hover, .blog-card:focus-within { transform: none; }
  .nav-group-menu { transform: none; }
}

@media print {
  /* Navigation affordances carry nothing onto paper. */
  .nav-group, .nav-group-menu, .drawer-group,
  .hero-orcid, .sec-next { display: none !important; }

  /* Proposal / journey / blog are the reading matter — flatten the cards and
     let every line print. */
  .prop-item, .blog-card {
    break-inside: avoid; page-break-inside: avoid;
    box-shadow: none !important; transform: none !important;
    border: 1px solid #999; padding: 12px;
  }
  .prop-list { gap: 10px; }
  .blog-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .blog-x { -webkit-line-clamp: unset; display: block; }
  .prop-p, .blog-x, .blog-body, .jr-body { color: #222 !important; }
  .prop-n { background: none !important; color: #000 !important; border: 1px solid #999; }
  .blog-ext { border: 1px solid #999 !important; background: none !important; color: #000 !important; }
  /* Certificate links: the URL is the useful part on paper (same treatment as
     .dl-btn above). */
  .cert-btn, .pub-cert {
    background: none !important; border: none !important; box-shadow: none !important;
    color: inherit !important; padding: 0; font-size: .72rem; font-weight: 600;
  }
  .cert-row { justify-content: flex-start; margin-block: 10px; }
  .cert-btn svg, .pub-cert svg { display: none; }
  .cert-btn::after, .pub-cert::after {
    content: " — " attr(href); overflow-wrap: anywhere; font-weight: 400;
  }
}

/* ============================================================
   8 · REFLECTIVE PHD JOURNEY
   Each essay is its own card — they run to several thousand words, so the
   text needs a comfortable measure and clear paragraph rhythm.
   ============================================================ */
.jr-card { margin-bottom: 22px; }
.jr-card:last-child { margin-bottom: 0; }
.jr-head {
  display: flex; align-items: flex-start; gap: 14px;
  padding-bottom: 14px; margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.jr-n {
  flex: none; width: 40px; height: 40px; border-radius: 12px;
  display: grid; place-items: center;
  font-family: var(--ff-en); font-weight: 800; font-size: .9rem; color: #fff;
  background: linear-gradient(145deg, var(--navy-600), var(--navy-900));
}
.jr-t { font-size: 1.08rem; font-weight: 800; line-height: 1.5; color: var(--text); }
/* A readable measure for long-form Arabic, and blank lines between paragraphs
   (the text arrives as newline-separated paragraphs from the source document). */
.jr-body {
  max-width: 74ch; font-size: 1.02rem; line-height: 1.95;
}
@media (max-width: 560px) {
  .jr-head { gap: 10px; }
  .jr-n { width: 34px; height: 34px; font-size: .8rem; }
  .jr-body { font-size: .98rem; line-height: 1.85; }
}

/* ============================================================
   9 · A SECTION HEADING THAT OPENS ITS FILE
   Reads as a heading, behaves as a link. The icon is the affordance —
   without it a clickable heading is invisible to the reader.
   ============================================================ */
.head-link {
  display: inline-flex; align-items: center; gap: 10px;
  color: inherit; text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color .2s, border-color .2s;
}
.head-link svg {
  width: 20px; height: 20px; flex: none;
  color: var(--accent); opacity: .75; transition: opacity .2s, transform .2s;
}
.head-link:hover { color: var(--accent); border-bottom-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.head-link:hover svg { opacity: 1; transform: scale(1.08); }
.head-link:focus-visible { outline: 3px solid var(--ring); outline-offset: 4px; border-radius: 6px; }
@media (prefers-reduced-motion: reduce) { .head-link svg { transition: none; } }
@media print { .head-link svg { display: none; } }

/* ============================================================
   10 · A TITLE THAT OPENS ITS FILE
   The title is the obvious thing to click, so wherever an item has a
   document behind it the title is a link. It must still READ as a title:
   inherit the size and weight, and carry a small icon as the only hint.
   ============================================================ */
.file-title {
  display: inline-flex; align-items: baseline; gap: 8px;
  color: inherit; font: inherit; text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color .2s, border-color .2s;
}
.file-title svg {
  width: .95em; height: .95em; flex: none;
  align-self: center; color: var(--accent); opacity: .6;
  transition: opacity .2s;
}
.file-title:hover { color: var(--accent); border-bottom-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.file-title:hover svg { opacity: 1; }
.file-title:focus-visible { outline: 3px solid var(--ring); outline-offset: 3px; border-radius: 4px; }
/* inside a paper card the title is already bold and tight */
.pub .file-title, .jr-t .file-title, .prop-t .file-title, .blog-t .file-title { gap: 7px; }
@media print { .file-title svg { display: none; } .file-title { border-bottom: none; } }

/* ============================================================
   11 · DOCUMENT GROUPS
   Documents are grouped under the folder name their owner filed them in,
   so the page mirrors how he organised his own material.
   ============================================================ */
.doc-group { margin-top: 22px; }
.doc-group:first-of-type { margin-top: 26px; }
.doc-group-t {
  display: block; margin-bottom: 10px;
  font-size: .82rem; font-weight: 800; letter-spacing: .04em;
  color: var(--muted); text-transform: none;
}
.doc-group .cert-row { margin-top: 0; }

/* ============================================================
   12 · VIDEO IN THE GALLERY
   The gallery tile is built for images: a fixed aspect box with an
   overlaid caption and a hover zoom. A <video> needs the opposite —
   it must not be cropped, its controls must be clickable, and the
   caption must never sit on top of them.
   ============================================================ */
.gal-item.gal-video {
  /* let the player set its own height rather than forcing the image ratio */
  aspect-ratio: auto;
  display: flex; flex-direction: column;
  background: var(--navy-900);
  cursor: default;
}
.gal-item.gal-video:hover { transform: none; }        /* no zoom on a player */

.gal-vid {
  display: block; width: 100%; height: auto;
  max-height: 70vh;                                    /* a portrait clip must not run off-screen */
  object-fit: contain;
  background: #000;
  border: 0;
}
/* The tile clips its corners; the player must be allowed out of that when it
   goes fullscreen, or the fullscreen surface inherits the clip. */
.gal-item.gal-video { overflow: visible; }
.gal-vid:fullscreen,
.gal-vid:-webkit-full-screen {
  max-height: none; width: 100%; height: 100%; object-fit: contain;
}

.gal-vcap {
  padding: 10px 14px 12px;
  font-size: .86rem; font-weight: 700; line-height: 1.5;
  color: rgba(255, 255, 255, .92);
  background: var(--navy-900);
}
html[data-theme="light"] .gal-item.gal-video,
html[data-theme="light"] .gal-vcap { background: var(--navy-800); }

/* A video tile is worth more room than a photo thumbnail. */
@media (min-width: 700px) { .gal-item.gal-video { grid-column: span 2; } }
@media print { .gal-item.gal-video { display: none; } }

/* ============================================================
   13 · DOCUMENT CARDS
   A document is a card and the whole card is the link — clicking anywhere
   on it opens the file. Replaces the old detached button row, which read as
   if the files belonged to whatever section happened to sit above them.
   ============================================================ */
.doc-group { margin-top: 34px; }
.doc-group-t {
  font-size: 1.02rem; font-weight: 800; color: var(--text);
  margin-bottom: 14px; padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.doc-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.doc-card {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px; border-radius: var(--radius-sm);
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text); text-decoration: none;
  transition: border-color .2s, box-shadow .2s, transform .2s;
}
.doc-card:hover {
  border-color: var(--accent); box-shadow: var(--shadow); transform: translateY(-2px);
}
.doc-card:focus-visible { outline: 3px solid var(--ring); outline-offset: 2px; }
.doc-card.is-dead { opacity: .55; cursor: default; }

/* Shares the per-extension palette already defined for .dl-ext / .blog-ext. */
.doc-ext {
  flex: none; padding: 4px 9px; border-radius: 7px;
  font-family: var(--ff-en); font-size: .66rem; font-weight: 800; letter-spacing: .04em;
  color: color-mix(in srgb, var(--ext, var(--accent)) 72%, var(--text));
  background: color-mix(in srgb, var(--ext, var(--accent)) 14%, var(--surface));
}
html[data-theme="dark"] .doc-ext {
  color: color-mix(in srgb, var(--ext, var(--accent)) 62%, #fff);
  background: color-mix(in srgb, var(--ext, var(--accent)) 22%, var(--surface));
}
.doc-t { flex: 1; font-size: .93rem; font-weight: 700; line-height: 1.5; }
.doc-go { flex: none; color: var(--accent); opacity: .6; transition: opacity .2s; }
.doc-go svg { width: 18px; height: 18px; }
.doc-card:hover .doc-go { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .doc-card { transition: none; } }

/* The document card reuses the very same per-extension palette defined above
   for .dl-ext / .blog-ext — derived from it so there is one source of truth. */
.doc-ext[data-ext="pdf"] { --ext: var(--ext-pdf, #d0453b); }
.doc-ext[data-ext="doc"],
.doc-ext[data-ext="docx"],
.doc-ext[data-ext="rtf"] { --ext: var(--ext-doc, #3a6ea8); }
.doc-ext[data-ext="csv"],
.doc-ext[data-ext="xls"],
.doc-ext[data-ext="xlsx"] { --ext: var(--ext-sheet, #2f8f5b); }
.doc-ext[data-ext="ppt"],
.doc-ext[data-ext="pptx"] { --ext: var(--ext-slide, #cc6b33); }
.doc-ext[data-ext="7z"],
.doc-ext[data-ext="rar"],
.doc-ext[data-ext="zip"] { --ext: var(--ext-archive, #8a6bbf); }
.doc-ext[data-ext="gif"],
.doc-ext[data-ext="jpeg"],
.doc-ext[data-ext="jpg"],
.doc-ext[data-ext="png"],
.doc-ext[data-ext="svg"],
.doc-ext[data-ext="webp"] { --ext: var(--ext-image, #2f97a8); }
.doc-ext[data-ext="md"],
.doc-ext[data-ext="txt"] { --ext: var(--ext-text, var(--muted)); }
.doc-ext[data-ext="mov"],
.doc-ext[data-ext="mp4"] { --ext: var(--ext-video, #b0568f); }
.doc-ext[data-ext="link"] { --ext: var(--ext-link, var(--gold)); }

/* ============================================================
   14 · CAPABILITIES AND LANGUAGES, WITHOUT INVENTED NUMBERS
   The old skill bars implied a precision that did not exist. Skills are
   now a named list; languages state their level in words.
   ============================================================ */
.cap-list { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
.cap {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 0; font-size: .95rem; line-height: 1.6; color: var(--text-soft);
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.cap:last-child { border-bottom: 0; }
.cap-dot { flex: none; display: grid; place-items: center; width: 20px; height: 20px; margin-top: 1px; color: var(--accent); }
.cap-dot svg { width: 15px; height: 15px; }

.lang-row.lang-plain {
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
  padding: 11px 0; border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.lang-row.lang-plain:last-child { border-bottom: 0; }
.lang-name { font-weight: 700; color: var(--text); }
.lang-level { font-size: .88rem; font-weight: 700; color: var(--accent); }

/* ============================================================
   15 · LONG-FORM PROSE
   The journey essays and the proposal sections are the only real long-form
   Arabic on the site — thousands of words each. They need typesetting, not
   just a container: a comfortable measure, a lead paragraph that invites the
   reader in, and clear separation between paragraphs.
   ============================================================ */
.prose { max-width: 72ch; }
.prose-p {
  margin: 0 0 1.05em; font-size: 1.02rem; line-height: 2;
  color: var(--text-soft); text-align: start;
}
.prose-p:last-child { margin-bottom: 0; }
/* the opening paragraph carries the reader in */
.prose-lead {
  font-size: 1.1rem; line-height: 1.95; color: var(--text); font-weight: 500;
  padding-inline-start: 16px;
  border-inline-start: 3px solid color-mix(in srgb, var(--accent) 55%, transparent);
}
@media (max-width: 560px) {
  .prose-p { font-size: .98rem; line-height: 1.9; }
  .prose-lead { font-size: 1.02rem; padding-inline-start: 12px; }
}

/* ---------- Journey cards ---------- */
.jr-card { padding: 30px 32px; }
.jr-head { display: flex; align-items: flex-start; gap: 16px; padding-bottom: 16px; margin-bottom: 22px; border-bottom: 1px solid var(--border); }
.jr-headtext { flex: 1; min-width: 0; }
.jr-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 14px; margin-top: 7px; }
.jr-date, .jr-read {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .78rem; font-weight: 700; color: var(--muted);
}
.jr-read svg { width: 14px; height: 14px; opacity: .8; }
.jr-foot { margin-top: 22px; padding-top: 18px; border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); }
@media (max-width: 560px) { .jr-card { padding: 22px 20px; } }

/* ---------- Proposal sections ---------- */
.prop-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 18px; counter-reset: none; }
.prop-item { padding: 26px 30px; }
.prop-item::before { content: none; }              /* no list marker */
.prop-head { display: flex; align-items: center; gap: 14px; padding-bottom: 14px; margin-bottom: 18px; border-bottom: 1px solid var(--border); }
.prop-n {
  flex: none; width: 38px; height: 38px; border-radius: 11px;
  display: grid; place-items: center;
  font-family: var(--ff-en); font-weight: 800; font-size: .85rem; color: #fff;
  background: linear-gradient(145deg, var(--accent), var(--navy-700));
}
.prop-t { font-size: 1.06rem; font-weight: 800; line-height: 1.5; color: var(--text); margin: 0; }
.prop-body { min-width: 0; }
.prop-foot { margin-top: 20px; padding-top: 16px; border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); }
@media (max-width: 560px) {
  .prop-item { padding: 20px 18px; }
  .prop-n { width: 32px; height: 32px; font-size: .76rem; }
}

/* ---------- Community: the whole contribution card is a link ---------- */
.comm-card-link { text-decoration: none; color: inherit; display: block; transition: border-color .2s, box-shadow .2s, transform .2s; }
.comm-card-link:hover { border-color: var(--accent); box-shadow: var(--shadow); transform: translateY(-3px); }
.comm-card-link:focus-visible { outline: 3px solid var(--ring); outline-offset: 3px; }
.comm-doc {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 16px;
  padding: 7px 13px; border-radius: 999px;
  font-size: .78rem; font-weight: 700; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
}
.comm-doc svg { width: 15px; height: 15px; }
.comm-card-link:hover .comm-doc { background: var(--accent); border-color: var(--accent); color: #fff; }
@media (prefers-reduced-motion: reduce) { .comm-card-link { transition: none; } }
@media print { .comm-doc { display: none; } }

/* A sub-heading the source document carried inside its own body text. */
.prose-h {
  margin: 1.6em 0 .7em; font-size: .95rem; font-weight: 800;
  color: var(--accent); letter-spacing: .01em;
}
.prose-h:first-child { margin-top: 0; }
.prose-h + .prose-p, .prose-h + .prose-lead { margin-top: 0; }

/* An enumeration the source document introduced with a colon. */
.prose-ul { margin: .55em 0 1.15em; padding: 0; list-style: none; display: grid; gap: .5rem; }
.prose-li {
  position: relative; padding-inline-start: 1.45rem;
  font-size: .95rem; line-height: 1.85; color: var(--text-2, var(--muted));
}
.prose-li::before {
  content: ""; position: absolute; inset-inline-start: .3rem; top: .82em;
  width: .42rem; height: .42rem; border-radius: 50%;
  background: var(--accent); opacity: .75;
}
.prose-p + .prose-ul { margin-top: -.4em; }

/* The professional summary is one long paragraph; it needs measure and leading,
   not the inline font-size it used to carry. */
.sum-prose .prose-p { font-size: 1.01rem; line-height: 2.05; }
.card-title-2 { margin-top: 26px; }

/* ============================================================
   16 · LONG-FORM LAYOUT CORRECTIONS
   Two bugs this fixes, both visible on #proposal and #journey.

   (a) `.prop-item` was declared `display: flex` back when the markup was
       `<li><span class="prop-n"><div class="prop-body">` — a number beside a
       body. The card now leads with a `<header class="prop-head">`, so that
       inherited flex laid the HEADER and the BODY side by side: the section
       title sat in a narrow column to the side of its own text. It must be a
       block flow — header above, body below.

   (b) `.prose { max-width: 72ch }` is the right reading measure, but it was
       being applied INSIDE a 1116px card. The text hugged the inline-start
       edge and left a 480px void beside every paragraph, which read as
       "unstyled" and "squished". The measure belongs to the CARD: constrain
       the column, then let the prose fill it.
   ============================================================ */
.prop-item { display: block; }

.prop-list, .jr-list {
  max-inline-size: 50rem;      /* ≈70 Arabic characters per line inside the card */
  margin-inline: auto;
  display: grid; gap: 18px;
}
.jr-list { gap: 22px; }

/* The card is now the measure, so the prose fills it. */
.prose, .prop-p.prose, .jr-body.prose, .blog-body.prose { max-inline-size: 100%; }

/* The About card is now the reading column, so the summary fills it. */
.sum-prose { max-inline-size: 100%; }

/* ============================================================
   17 · LONG-FORM INDEX + READING VIEW  (#proposal, #journey)
   A summary card per entry, then one reading view per entry. Without this the
   reader had to scroll a whole essay to reach the next title.
   ============================================================ */
/* The INDEX spans the container so three tiles fit a line; the READING view
   keeps the narrow measure that long-form text needs. */
.lf-index { max-inline-size: 100%; }
.lf-read  { max-inline-size: 52rem; margin-inline: auto; }

.lf-doc { display: flex; justify-content: center; margin-bottom: 26px; }

.lf-list {
  display: grid; gap: 16px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 1000px) { .lf-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .lf-list { grid-template-columns: 1fr; } }

/* --- summary card (a tile) --- */
.lf-card {
  display: grid; gap: 10px; align-content: start;
  padding: 20px 22px 18px; text-decoration: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  transition: border-color .22s, box-shadow .22s, transform .22s;
}
.lf-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
/* pin the meta line to the bottom so every tile's footer lines up */
.lf-card .lf-meta { margin-block-start: auto; padding-block-start: 4px; }
.lf-card:hover, .lf-card:focus-visible {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  box-shadow: var(--shadow-lg); transform: translateY(-2px);
}
.lf-n {
  flex: none; inline-size: 2.5rem; block-size: 2.5rem;
  display: grid; place-items: center; border-radius: 11px;
  font-family: var(--ff-en); font-weight: 800; font-size: .85rem; line-height: 1;
  font-variant-numeric: tabular-nums; color: #fff;
  background: linear-gradient(145deg, var(--navy-600), var(--navy-900));
}
.lf-cardbody { flex: 1 1 auto; min-inline-size: 0; display: grid; gap: 7px; }
.lf-cardt {
  font-size: 1rem; font-weight: 800; line-height: 1.55; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.lf-x {
  font-size: .88rem; line-height: 1.8; color: var(--text-soft);
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
.lf-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 14px; }
.lf-date, .lf-mins {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .77rem; font-weight: 700; color: var(--muted);
}
.lf-mins svg { width: 14px; height: 14px; opacity: .8; }
/* The chevron points the way the language reads. */
.lf-go {
  flex: none; display: grid; place-items: center;
  inline-size: 2rem; block-size: 2rem; border-radius: 50%;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent);
  transition: background .22s, transform .22s;
}
.lf-go svg { width: 16px; height: 16px; }
html[dir="rtl"] .lf-go svg { transform: scaleX(-1); }
.lf-card:hover .lf-go { background: color-mix(in srgb, var(--accent) 18%, transparent); }
html[dir="rtl"] .lf-card:hover .lf-go { transform: translateX(-3px); }
html[dir="ltr"] .lf-card:hover .lf-go { transform: translateX(3px); }

/* --- reading view --- */
.lf-back {
  display: inline-flex; align-items: center; gap: 8px;
  margin-bottom: 18px; padding: 8px 14px 8px 12px;
  font-size: .84rem; font-weight: 800; text-decoration: none;
  color: var(--text-soft); background: var(--surface-2, var(--surface));
  border: 1px solid var(--border); border-radius: 999px;
  transition: color .2s, border-color .2s;
}
.lf-back:hover { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.lf-back svg { width: 15px; height: 15px; }
html[dir="ltr"] .lf-back svg { transform: scaleX(-1); }

.lf-paper { padding: 34px 36px; }
.lf-head {
  display: flex; align-items: flex-start; gap: 16px;
  padding-bottom: 18px; margin-bottom: 24px; border-bottom: 1px solid var(--border);
}
.lf-headtext { flex: 1 1 auto; min-inline-size: 0; }
.lf-t { margin: 0 0 8px; font-size: 1.3rem; font-weight: 800; line-height: 1.5; color: var(--text); }
.lf-foot { margin-top: 26px; padding-top: 20px; border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); }

/* --- prev / next --- */
.lf-pager {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 22px;
}
.lf-pg {
  display: grid; gap: 5px; padding: 16px 18px; text-decoration: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); transition: border-color .2s, box-shadow .2s;
}
.lf-pg:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); box-shadow: var(--shadow); }
.lf-pg-k { font-size: .72rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--accent); }
.lf-pg-t { font-size: .9rem; font-weight: 700; line-height: 1.6; color: var(--text); }
.lf-pg-next { text-align: end; }

@media (max-width: 620px) {
  .lf-paper { padding: 24px 20px; }
  .lf-card { padding: 16px 16px; gap: 12px; }
  .lf-t { font-size: 1.12rem; }
  .lf-pager { grid-template-columns: 1fr; }
  .lf-pg-next { text-align: start; }
}

/* ============================================================
   18 · MEDIA TILES, TRAINING CHIPS, BARE FILE TITLES
   ============================================================ */

/* --- a video is a tile like any other, with a play badge ---
   NB: do NOT set position on .gal-link here. It is already
   `position: absolute; inset: 0` (section 2), which is what gives it the
   .gal-item's aspect-ratio height. Overriding it to `relative` collapsed every
   tile to 0px and the whole media grid went blank. Absolute is already a
   containing block, so .gal-play needs nothing added. */
.gal-play {
  position: absolute; inset: 0; margin: auto;
  inline-size: 62px; block-size: 62px; border-radius: 50%;
  display: grid; place-items: center;
  color: #fff; background: color-mix(in srgb, #0b1622 55%, transparent);
  border: 2px solid rgba(255, 255, 255, .82);
  backdrop-filter: blur(3px);
  transition: transform .22s, background .22s;
  pointer-events: none;
}
.gal-play svg { width: 24px; height: 24px; margin-inline-start: 3px; }
.gal-is-video:hover .gal-play,
.gal-is-video:focus-visible .gal-play {
  transform: scale(1.08); background: color-mix(in srgb, var(--accent) 78%, transparent);
}
/* A poster is 16:9 while photos are portrait-ish; crop rather than letterbox so
   the grid keeps one rhythm instead of a black band. */
.gal-is-video .gal-img { object-fit: cover; }

/* --- one heading for a run of shots from the same occasion --- */
.gal-album {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 12px;
  margin: 10px 0 2px; font-size: 1.02rem; font-weight: 800; color: var(--text);
}
.gal-album::after {
  content: ""; flex: 1 1 auto; height: 1px;
  background: linear-gradient(to left, transparent, var(--border));
}
.gal-album-n {
  flex: none; font-family: var(--ff-en); font-size: .72rem; font-weight: 800;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
  padding: 3px 9px; border-radius: 999px;
}

/* --- video on the lightbox stage --- */
.gal-lb-vid {
  max-inline-size: min(94vw, 1400px); max-block-size: 82vh;
  inline-size: auto; block-size: auto;
  border-radius: 10px; background: #000;
  box-shadow: 0 30px 90px rgba(0, 0, 0, .55);
}

/* --- a training course that has its own certificate --- */
.chip-link { text-decoration: none; cursor: pointer; }
.chip-link:hover, .chip-link:focus-visible {
  border-color: color-mix(in srgb, var(--accent) 48%, var(--border));
  box-shadow: var(--shadow);
}
.chip-go {
  display: inline-grid; place-items: center; margin-inline-start: auto;
  inline-size: 1.35rem; block-size: 1.35rem; color: var(--accent); opacity: .75;
}
.chip-go svg { width: 14px; height: 14px; }
.chip-link:hover .chip-go { opacity: 1; }

/* --- a title that links its file without adding a second visible control --- */
.file-title-bare { color: inherit; text-decoration: none; }
.file-title-bare:hover, .file-title-bare:focus-visible {
  color: var(--accent); text-decoration: underline; text-underline-offset: 3px;
}

/* ============================================================
   19 · DRAWER: KEEP `visibility` OUT OF THE TRANSITION
   The closed drawer is hidden with `visibility: hidden` so its close button and
   its links leave the tab order and the accessibility tree. But `.icon-btn` and
   `.btn` both declare `transition: .2s` — a shorthand with no property list, so
   transition-property is `all`, and `visibility` is animatable. Its children
   therefore kept ANIMATING from hidden, computing `visibility: hidden` for the
   first ~200ms after the drawer opened, during which they could not take focus:
   openDrawer()'s focus() call silently did nothing and focus stayed on <body>.

   Declared here rather than in style.css because drive.css loads second, so
   this beats `.icon-btn` and `.btn` regardless of their order in that file.
   ============================================================ */
.drawer,
.drawer * {
  transition-property: transform, background-color, color, border-color, box-shadow, opacity;
}

/* A headline figure that carries the reader to the page it evidences. */
.stat-link { text-decoration: none; color: inherit; display: block; transition: transform .2s, box-shadow .2s, border-color .2s; }
.stat-link:hover, .stat-link:focus-visible { transform: translateY(-3px); }
.stat-link:hover .lbl { color: var(--accent); }

/* A contact card with nothing to open: same look, no click affordance. */
.contact-card-static { cursor: default; }
.contact-card-static:hover { transform: none; box-shadow: var(--shadow); border-color: var(--border); }

/* ============================================================
   20 · COMMUNITY GROUPS  (#community)
   Nasser set his contribution out in named groups — parents' councils, the
   cultural centre, training, working papers — and the training entries lead
   with a figure rather than a job title.
   ============================================================ */
.comm-group + .comm-group { margin-top: 38px; }
.comm-group-t {
  display: flex; align-items: center; gap: 12px;
  margin: 0 0 18px; font-size: 1.05rem; font-weight: 800; color: var(--text);
}
.comm-group-t::after {
  content: ""; flex: 1 1 auto; height: 1px;
  background: linear-gradient(to left, transparent, var(--border));
}
html[dir="ltr"] .comm-group-t::after { background: linear-gradient(to right, transparent, var(--border)); }

/* A contribution whose substance is its scale. */
.comm-card-metric { display: grid; gap: 10px; align-content: start; text-align: center; }
.comm-metric {
  font-family: var(--ff-en); font-size: clamp(2rem, 5vw, 2.7rem); font-weight: 800;
  line-height: 1; font-variant-numeric: tabular-nums;
  color: var(--accent);
}
.comm-metric-t {
  margin: 0; font-size: .94rem; line-height: 1.75; color: var(--text-soft);
}

/* The icon on a figure card: centred above the number, same badge treatment as
   the role cards so the two kinds of card still read as a set. */
.comm-metric-ic { justify-self: center; margin-bottom: 2px; }

/* A qualification row's own documents: its certificate, and anything pinned to
   it (the Master's thesis hangs off the MBA row rather than standing alone). */
.tl-docs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.tl-docs:empty { display: none; }
.tl-docs .cert-btn-sm { max-inline-size: 100%; }
.tl-docs .cert-btn-sm span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-inline-size: 34ch; }
