/*
 * TOA V2 — public site design system (P7).
 *
 * LIGHT MODE ONLY (NFR-016). No dark palette, no toggle, and deliberately no
 * prefers-color-scheme override: the approved design is one design.
 *
 * COLOUR SUPERSESSION — GOV-P7-06:
 *   PREVIOUS APPROVED PALETTE  #184A15 / #C09329   (docs/24, historical)
 *   CURRENT OWNER-APPROVED     #0D3B2E / #C79A2E
 * Ratios below were RECOMPUTED against the current palette; none was carried
 * over. The official gold measures 2.49:1 on the light canvas, so it is used
 * for rules, marks and borders — never as text on light. `--gold-text` is a
 * separate accessible token and is NOT the brand gold.
 *
 *   --green on --bg .......... 11.96:1   --text on --bg ..... 15.74:1
 *   #FFF on --green .......... 12.48:1   --muted on --bg .....  6.27:1
 *   --gold-text on --bg ......  6.10:1   --gold on --green ...  4.81:1
 */

:root {
  --green: #0D3B2E;
  --green-hover: #0A2E24;
  --green-soft: #F1F5F2;
  --gold: #C79A2E;
  --gold-text: #7A5A12;
  --gold-soft: #FBF4E4;

  --bg: #FBFAF7;
  --bg-alt: #F4F2EC;
  --surface: #FFFFFF;
  --text: #1A211C;
  --muted: #55605A;
  --border: #E2E5E0;

  --success: #1E6B34; --warning: #8A5A00; --error: #A32015;

  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 72px;

  --radius: 14px; --radius-lg: 22px; --radius-pill: 999px;
  --shadow: 0 1px 2px rgba(26,33,28,.05), 0 8px 24px rgba(26,33,28,.05);
  --shadow-lift: 0 2px 4px rgba(26,33,28,.06), 0 16px 40px rgba(26,33,28,.08);

  --measure: 66ch;
  --wrap: 1140px;
  --header-h: 60px;
}

@font-face {
  font-family: 'Cairo';
  src: url('/assets/fonts/cairo-variable-arabic.woff2') format('woff2-variations');
  font-weight: 400 800; font-display: swap;
}
@font-face {
  /* Montserrat is the OFFICIAL English identity typeface (owner decision,
     28 August 2026). Served first-party like Cairo: no Google Fonts, no CDN,
     no third-party runtime request of any kind. The declared range is the
     font's own 100-900 axis, so every weight the stylesheets ask for is a real
     instance rather than one the browser synthesises. */
  font-family: 'Montserrat';
  src: url('/assets/fonts/montserrat-variable-latin.woff2') format('woff2-variations');
  font-weight: 100 900; font-display: swap;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-padding-block-start: calc(var(--header-h) + var(--s4)); }

body {
  margin: 0;
  font-family: 'Montserrat', 'Cairo', system-ui, sans-serif;
  font-size: 1.0625rem; line-height: 1.7;
  color: var(--text); background: var(--bg);
}
html[dir="rtl"] body { font-family: 'Cairo', system-ui, sans-serif; line-height: 1.9; }
/* Cairo's INK BOX is 1.89x its font size — measured, not estimated: a heading
   at 31.2px paints 59px tall. The Latin 1.25 gives that heading a 39px line
   box, so 10px of Arabic glyph sits OUTSIDE the element, above and below it.
   Nothing clips it, but the ascenders reach past the box into whatever is
   there — under the sticky header, the heading loses its top edge and reads as
   sliced. Latin never shows this because Latin ink fits inside its own box.

   1.9 is the first value that contains the ink at every width (measured at
   620/768/1440: overflow 0.0px), and it is not a new number: it is the same
   ratio the RTL body already uses on line 74 for exactly the same reason. An
   earlier 1.45 was too small — it grew the box to 45px against 59px of ink and
   left 7px still outside, which is why the defect survived it.

   RTL only. The Latin headings keep 1.25. */
html[dir="rtl"] h1,
html[dir="rtl"] h2,
html[dir="rtl"] h3 { line-height: 1.9; }

img { max-width: 100%; height: auto; }
.icon { flex: none; vertical-align: middle; }

/* Logical properties throughout: one stylesheet serves both directions. */
.wrap { width: min(100% - var(--s5) * 2, var(--wrap)); margin-inline: auto; }

h1, h2, h3 { line-height: 1.25; margin-block: 0 var(--s4); text-wrap: balance; }
/* DISPLAY SCALE — desktop refinement, owner review 29 August 2026.
   Only the clamp MAXIMA move. At 1440 both headings sat pinned to their max,
   so the caps are the only thing a wide screen ever sees; the minima are what
   a phone sees, and they are untouched. The result: -10% where the owner found
   the type oversized, and byte-identical rendering at 620 and below.
   h1 caps from 985px up, h2 from 933px up. The 1.59 h1:h2 ratio the design
   system was built on is preserved (44.8:28.0 = 1.60). */
/* h1 is the PAGE TITLE scale. Every bare <h1> on the site is a page title —
   about, experience, projects, contact, project detail, 404, maintenance — so
   the generic rule carries that role and no page needs an override. The two
   exceptions already name themselves: `.hero-name` is the identity display
   below, and `.vcard-name` sets its own size on the card.
   Owner target: 35px on wide desktop, against 17px body. */
h1 { font-size: clamp(1.85rem, 1.2rem + 2.6vw, 2.1875rem); letter-spacing: -.01em; }
h2 { font-size: clamp(1.35rem, 1.05rem + 1.2vw, 1.75rem); }
h3 { font-size: 1.1rem; }
p { margin-block: 0 var(--s4); overflow-wrap: anywhere; }
.prose p { max-width: var(--measure); }
.muted { color: var(--muted); }

a { color: var(--green); text-underline-offset: 3px; }
a:hover { color: var(--green-hover); }
:focus-visible { outline: 3px solid var(--green); outline-offset: 2px; border-radius: 6px; }

/* Pinned with a PHYSICAL offset while hidden. A logical `inset-inline-start`
   resolves to `right` in RTL and parked this 1px link past the inline edge,
   adding 17px of horizontal scroll at 320px (measured). Direction is
   irrelevant to something that is clipped and invisible; it matters again on
   focus, where the logical properties come back. */
.skip {
  position: absolute; top: 0; left: 0; right: auto;
  width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}
.skip:focus {
  position: fixed; inset-block-start: 0; inset-inline-start: 0;
  width: auto; height: auto; margin: 0; clip-path: none; overflow: visible;
  padding: var(--s3) var(--s4); background: var(--green); color: #FFF; z-index: 100;
}

/* ---------- header: compact on phones, one row on wider screens ---------- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: rgba(251,250,247,.92);
  border-block-end: 1px solid var(--border);
  backdrop-filter: saturate(1.1) blur(6px);
}
.header-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); min-height: var(--header-h);
}
.brand { display: inline-flex; align-items: center; gap: var(--s3); text-decoration: none; min-height: 44px; }
/* The official lockup is wide (aspect ~4.7), so it is sized by HEIGHT and the
   width follows — never stretched to fit a box. */
.brand-mark { display: block; width: auto; height: 30px; }
@media (min-width: 700px) { .brand-mark { height: 36px; } }
/* The mark carries the brand on the narrowest screens; the wordmark joins it
   as soon as there is room, rather than being clipped mid-name. */
.brand-text { display: none; }
.brand-name { font-weight: 700; color: var(--green); font-size: 1rem; white-space: nowrap; }
@media (min-width: 460px) { .brand-text { display: block; } }
.header-actions { display: flex; align-items: center; gap: var(--s3); }
.lang-switch {
  display: inline-flex; align-items: center; min-height: 44px; padding-inline: var(--s3);
  text-decoration: none; font-weight: 600; font-size: .95rem;
  border: 1px solid var(--border); border-radius: var(--radius-pill); background: var(--surface);
}

/* Disclosure menu — no JavaScript, so nothing to allow in the CSP.
   The <details> holds only the open state; the nav is its SIBLING and CSS
   reveals it. That avoids relying on how each engine lays out a closed panel. */
.nav-toggle summary {
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: 44px; padding-inline: var(--s3);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--surface); font-weight: 600; cursor: pointer; list-style: none;
}
.nav-toggle summary::-webkit-details-marker { display: none; }
.nav-summary-text { font-size: .95rem; }
/* Hidden until the disclosure is opened (mobile default). */
.site-nav { display: none; }
/* The trigger says what it will DO: "Menu" when closed, "Close" when open. */
.nav-face { display: inline-flex; align-items: center; gap: var(--s2); }
.nav-face-close { display: none; }
.nav-toggle[open] summary .nav-face-open { display: none; }
.nav-toggle[open] summary .nav-face-close { display: inline-flex; }
.nav-toggle[open] summary { background: var(--green); color: #FFF; border-color: var(--green); }

/* THE OPEN STATE (owner visual finding). Previously a floating white box with a
   lot of empty height, sitting over a hero that still competed with it. It is
   now a panel anchored under the header that spans the width and is only as
   tall as its links, over a backdrop dark enough that the page behind clearly
   RECEDES rather than competes. */
.nav-toggle[open] ~ .site-nav {
  display: block;
  position: fixed;
  inset-inline: 0;
  inset-block-start: var(--header-h);
  max-height: calc(100dvh - var(--header-h));
  overflow-y: auto;
  background: var(--surface);
  border-block-start: 1px solid var(--border);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  box-shadow: 0 18px 44px rgba(26,33,28,.22);
  padding: var(--s3) var(--s5) var(--s5);
  z-index: 60;
}
/* A gold hairline ties the open panel to the brand rather than leaving it a
   plain white sheet. */
.nav-toggle[open] ~ .site-nav::before {
  content: ""; display: block; height: 3px; width: 56px;
  background: var(--gold); border-radius: 2px; margin: 0 auto var(--s3);
}
/* An open menu is a MODE, so the page behind it stops moving. The class is set
   by script only while the overlay breakpoint applies, so a wide screen — where
   the nav is an ordinary row — never has its scrolling touched. With no
   JavaScript the menu still opens and closes; it simply does not lock. */
html.nav-open { overflow: hidden; }
/* Frozen, not merely overflow-hidden: `overflow` alone was measured still
   allowing the page to move. `top` is set by script to the negative scroll
   offset so the page does not jump while it is held. */
html.nav-open body {
  position: fixed; inset-inline: 0; width: 100%; overflow: hidden;
}
/* A scrim makes the mode visible and gives the outside tap something to land
   on. Non-interactive to assistive technology: the menu itself is the content. */
/* THE OPEN MENU MUST NOT DEPEND ON SCROLL POSITION (P7-F15).
   Owner-reproduced defect: opening the menu after scrolling showed the scrim
   but no panel. Root cause, measured at 0 / 800 / 2683px:

     1. the scroll lock freezes the document with `body{position:fixed;top:-Y}`;
     2. `.site-header` is STICKY — with no scrollport left to stick to it falls
        back to its static position, which is now -Y, i.e. off screen;
     3. the header carries `backdrop-filter`, which makes it a CONTAINING BLOCK
        for `position:fixed` descendants, so the panel is anchored to the header
        and follows it off screen;
     4. the scrim is `body::after` and `position:fixed` on `body` creates no such
        containing block, so the scrim alone stayed put.

   The fix takes the header OUT of the frozen document while the menu is open,
   so it and the panel are anchored to the viewport at any scroll offset. The
   space it vacates is given back as body padding, so nothing shifts. Not fixed
   by scrolling the visitor anywhere, and the scrim is untouched. */
html.nav-open .site-header {
  position: fixed; inset-block-start: 0; inset-inline: 0;
  z-index: 70;
}
html.nav-open body { padding-block-start: var(--header-h); }
html.nav-open body::after {
  content: ""; position: fixed; inset: 0; z-index: 50;
  /* Deep enough that the hero behind reads as background, not as a competing
     layer. Tinted with the brand green rather than neutral black. */
  background: rgba(13,59,46,.62);
  backdrop-filter: blur(2px);
}
.site-nav ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.site-nav a {
  display: flex; align-items: center; min-height: 48px; padding-inline: var(--s3);
  text-decoration: none; border-radius: 10px; font-weight: 600;
}
.site-nav a:hover { background: var(--green-soft); }
.site-nav a[aria-current="page"] { color: var(--green); background: var(--green-soft); }

/* 900px, not 760: at 768 the English labels plus the brand and the language
   switch measured 804px against a 768px viewport. The breakpoint follows the
   content rather than a round number. */
@media (min-width: 900px) {
  /* Above this width the toggle disappears and the nav is simply a row. */
  .nav-toggle { display: none; }
  .site-nav,
  .nav-toggle[open] ~ .site-nav {
    display: block;
    position: static; inset: auto; margin: 0; padding: 0;
    background: none; border: 0; box-shadow: none;
  }
  .site-nav { min-width: 0; }
  .site-nav ul { display: flex; flex-wrap: wrap; gap: var(--s2); }
  .site-nav a { min-height: 44px; padding-inline: var(--s3); font-weight: 600; }
  .site-nav a[aria-current="page"] { background: none; box-shadow: inset 0 -2px 0 var(--gold); border-radius: 0; }
  .header-bar { gap: var(--s5); }
  .header-actions { gap: var(--s4); }
}

/* ---------- hero ---------- */
main { display: block; }
.hero { padding-block: var(--s7) var(--s6); background:
  radial-gradient(120% 80% at 100% 0, var(--green-soft) 0%, transparent 60%), var(--bg); }
.hero-grid { display: grid; gap: var(--s6); align-items: center; }
@media (min-width: 860px) { .hero-grid { grid-template-columns: 1.15fr .85fr; gap: var(--s8); } }

.hero-badge {
  display: inline-flex; align-items: center; gap: var(--s2);
  background: var(--gold-soft); color: var(--gold-text);
  border: 1px solid rgba(199,154,46,.35); border-radius: var(--radius-pill);
  padding: var(--s1) var(--s4); font-size: .92rem; font-weight: 600; margin-block-end: var(--s4);
}
/* IDENTITY DISPLAY — the one place the owner's name is the subject rather than
   a page's title, so it stays deliberately above the page-title scale: 40px to
   35px on wide desktop. Same floor and same slope as h1, so the two roles only
   diverge where the caps bite (800px here, 608px for a page title) and mobile
   is identical for both. */
.hero-name {
  color: var(--green); margin-block-end: var(--s1);
  font-size: clamp(1.85rem, 1.2rem + 2.6vw, 2.5rem);
}
/* The ROLE, in its own right. V1 stated the position immediately under the
   name and that is most of why it reads as a professional site rather than a
   portfolio template. */
.hero-role {
  color: var(--text); font-weight: 700; margin-block-end: var(--s3);
  font-size: clamp(1.05rem, .95rem + .55vw, 1.35rem);
}
/* The tagline is a statement, so it is set as one — V1 marked it with a gold
   rule, which separates the motto from the biography that follows. */
.hero-tagline {
  color: var(--gold-text); font-weight: 700;
  /* SYNTAX FIX, not a design change. CSS requires whitespace around `+` in a
     calc expression, so `.95rem+.4vw` made the whole clamp() invalid and the
     browser dropped the declaration — this line has been rendering at the
     inherited body size (17px) instead of its intended 19.2px max since it was
     written. Same values, now actually applied. */
  font-size: clamp(1.02rem, .95rem + .4vw, 1.2rem);
  border-inline-start: 3px solid var(--gold);
  padding-inline-start: var(--s4); margin-block-end: var(--s4);
}
.hero-intro { max-width: 56ch; font-size: 1.08rem; color: var(--muted); }

.portrait { position: relative; justify-self: center; }
/* V1's gold bracket. The previous faint arc was invisible at a glance; a
   bracket offset behind the frame reads as deliberate art direction. */
.portrait-bracket {
  position: absolute; inset-block-start: -16px; inset-inline-start: -16px;
  width: 56%; aspect-ratio: 1;
  border-inline-start: 3px solid var(--gold);
  border-block-start: 3px solid var(--gold);
  border-start-start-radius: var(--radius-lg);
}
/* `overflow` stays visible: the brand plate deliberately overlaps the lower
   edge, and clipping it would put the sticker back. The image itself is
   rounded by its own radius instead. */
.portrait-frame {
  position: relative;
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lift);
  border: 1px solid var(--border); background: var(--surface);
  max-width: 460px;
}
.portrait-frame img { display: block; width: 100%; border-radius: calc(var(--radius-lg) - 1px); }
/* The TOA mark ON the portrait (owner finding). The first attempt was a large
   translucent white box laid over the image — it read as a STICKER stuck on the
   photo rather than as part of the composition.
   
   It is now a defined plate that sits ON the portrait's lower edge, overlapping
   it rather than floating inside it: opaque (so the mark keeps its own colours
   against any part of the photograph), tight to the mark, lifted with the same
   shadow language as the frame, and finished with a gold hairline that answers
   the gold bracket on the opposite corner. Smaller, denser, and deliberate. */
.portrait-brand {
  position: absolute;
  inset-block-end: -14px;
  /* Diagonally opposite the gold bracket, which sits at the top inline-start
     corner. The two accents then frame the portrait instead of crowding the
     same corner — and the plate stays on the outer edge, away from the text. */
  inset-inline-end: 22px;
  display: inline-flex; align-items: center;
  padding: 10px 16px;
  background: var(--surface);
  border-radius: 12px;
  border-block-end: 2px solid var(--gold);
  box-shadow: 0 6px 18px rgba(26,33,28,.16);
}
.portrait-brand img { display: block; height: 26px; width: auto; }
@media (min-width: 860px) {
  .portrait-brand { inset-block-end: -16px; padding: 12px 18px; }
  .portrait-brand img { height: 30px; }
}

.stats {
  list-style: none; margin: var(--s7) 0 0; padding: 0;
  display: grid; gap: var(--s3); grid-template-columns: repeat(auto-fit, minmax(min(100%,180px), 1fr));
}
.stat {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--s4) var(--s5);
  border-inline-start: 3px solid var(--gold);
}
.stat-value { display: block; font-weight: 800; color: var(--green); }
.stat-label { display: block; color: var(--muted); font-size: .95rem; }

/* ---------- sections ---------- */
.section { padding-block: var(--s7); }
.section-alt { background: var(--bg-alt); border-block: 1px solid var(--border); }
.section-title { position: relative; padding-inline-start: var(--s4); margin-block-end: var(--s5); }
.section-title::before {
  content: ""; position: absolute; inset-inline-start: 0; inset-block: .15em .15em;
  width: 4px; border-radius: 2px; background: var(--gold);
}
.page-head { padding-block: var(--s7) var(--s3); }
.page-head h1 { color: var(--green); max-width: 28ch; }
.page-lede { color: var(--muted); max-width: var(--measure); font-size: 1.08rem; }
/* EXECUTIVE POSITIONING STATEMENT — one semantic role, one rule.
   About and Contact print the SAME approved tagline the home hero prints, in
   the same role, so they get the same design language. Experience does not:
   its .page-lede is the `experience-intro` narrative and stays neutral, which
   is the whole reason the role needed a name of its own rather than a page
   selector. Projects has no lede at all and none was invented.
   The reference is `.hero-tagline` — same gold, same weight, same 3px
   inline-start rule, same size expression — kept as a separate rule so the
   hero itself is not touched. Logical properties throughout, so the accent
   mirrors between RTL and LTR with nothing hard-coded left or right.
   Subordinate by design: 19.2px under a 35px page title. */
.page-lede.page-statement {
  color: var(--gold-text); font-weight: 700;
  font-size: clamp(1.02rem, .95rem + .4vw, 1.2rem);
  border-inline-start: 3px solid var(--gold);
  padding-inline-start: var(--s4);
}
.lede p { font-size: 1.08rem; }

.grid { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s4); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); }

.tile, .panel {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--s5); box-shadow: var(--shadow);
}
.tile-icon, .panel-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 10px;
  background: var(--green-soft); color: var(--green); margin-block-end: var(--s3);
}
.tile-title, .panel-title { margin-block-end: var(--s2); color: var(--green); }
.tile-text, .panel-text { color: var(--muted); margin: 0; }

/* ---------- timeline ---------- */
.timeline { list-style: none; margin: 0; padding: 0; position: relative; }
.timeline::before {
  content: ""; position: absolute; inset-block: 6px 6px; inset-inline-start: 7px;
  width: 2px; background: var(--border);
}
.timeline-item { position: relative; padding-inline-start: var(--s6); padding-block-end: var(--s5); }
.timeline-dot {
  position: absolute; inset-inline-start: 0; inset-block-start: 6px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--surface); border: 3px solid var(--gold);
}
.timeline-period { color: var(--gold-text); font-weight: 600; font-size: .95rem; margin-block-end: var(--s1); }
.timeline-title { color: var(--green); margin-block-end: var(--s1); }
/* Employer line (GOV-P7-07). Sits between the job title and the responsibilities
   because that is the order the fact is read in: this role, at this institution,
   doing this work. Body weight, not heading weight — it identifies, it does not
   compete with the title. */
.timeline-org {
  display: flex; align-items: center; gap: var(--s2);
  color: var(--text); font-weight: 600; font-size: 1rem;
  margin-block-end: var(--s2);
}
.timeline-org svg { color: var(--gold-text); flex: none; }
.timeline-text { color: var(--muted); max-width: var(--measure); margin: 0; }

/* ---------- screen-reader-only text ---------- */
/* Same technique as `.skip`, without the focus behaviour: this text is never
   focusable, it only tells a screen reader that a link opens a new tab. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

/* ---------- eyebrow ---------- */
/* V1 set a small label above the page title. It gives the heading a shoulder to
   sit on, which is most of the difference between a titled page and a slide. */
.eyebrow {
  margin: 0 0 var(--s2); font-size: .82rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--gold-text);
}

/* ---------- achievements: the name leads, the parts follow ----------
   The rule this encodes: the achievement's NAME is the primary object. It is
   the largest, greenest, first thing in the entry, and the three parts sit
   under it as labelled supporting facts — so the challenge can never read as
   though it were the accomplishment. */
.achievement-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s5); }
.achievement {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow);
  padding: var(--s5) var(--s6);
  display: grid; gap: var(--s4); align-items: start;
  border-inline-start: 3px solid var(--gold);
}
@media (min-width: 700px) { .achievement { grid-template-columns: auto minmax(0, 1fr); } }
.achievement-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; border-radius: 12px;
  background: var(--green-soft); color: var(--green); flex: none;
}
.achievement-body { min-width: 0; }
.achievement-title {
  margin: 0 0 var(--s4); color: var(--green);
  font-size: clamp(1.15rem, 1.02rem + .5vw, 1.4rem); line-height: 1.35;
}
.achievement-text { margin: 0; color: var(--muted); max-width: var(--measure); }

.achievement-parts { margin: 0; display: grid; gap: var(--s4); }
.achievement-part { display: grid; gap: var(--s1); }
/* The label is a real term, set quietly: it identifies the part without
   competing with the achievement's name above it. */
.achievement-part dt {
  font-size: .78rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--gold-text);
}
.achievement-part dd { margin: 0; color: var(--muted); max-width: var(--measure); }
/* The impact is the outcome, so it carries slightly more weight than the
   problem that preceded it — the entry ends on what changed. */
.achievement-part:last-child dd { color: var(--text); }

/* ---------- project case-study index ----------
   No boxes. The hierarchy comes from the sequence number, a hairline rule and
   the type scale — which is what makes an entry read as a piece of work rather
   than as a record in a list. */
.case-list { list-style: none; margin: 0; padding: 0; }
.case { padding-block: var(--s6); }
.case + .case { border-block-start: 1px solid var(--border); }
.case:first-child { padding-block-start: var(--s2); }

/* The number-and-rule band. The rule runs the full width of the column, so the
   entry is anchored across the page instead of huddling against one edge — the
   structure a case-study index needs, achieved with a hairline rather than a
   box. It is aria-hidden: rhythm for the eye, silence for a screen reader. */
.case-band {
  display: flex; align-items: center; gap: var(--s4);
  margin: 0 0 var(--s4);
}
.case-no {
  font-size: 2rem; font-weight: 800; line-height: 1;
  color: var(--gold); font-variant-numeric: tabular-nums; flex: none;
}
.case-rule { flex: 1; height: 1px; background: var(--border); }
.case-main { min-width: 0; display: grid; gap: var(--s3); }
.case-meta {
  margin: 0; font-size: .85rem; font-weight: 600; letter-spacing: .03em;
  color: var(--gold-text);
}
/* A portfolio title, never a hyperlink: no underline, no link colour. */
.case-title { margin: 0; color: var(--green); font-size: 1.35rem; line-height: 1.3; }
.case-text { margin: 0; color: var(--muted); max-width: var(--measure); }
.case-actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s2) var(--s5); margin: var(--s1) 0 0;
}
/* The action is a considered control, not default hyperlink styling: weight and
   a moving icon carry it, and there is no underline doing the work. */
.case-link, .case-visit {
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: 44px; font-weight: 700; text-decoration: none;
}
.case-link { color: var(--green); }
.case-visit { color: var(--gold-text); }
.case-link:hover, .case-visit:hover { gap: var(--s3); }
.case-visual { min-width: 0; }
.case-visual img {
  width: 100%; height: auto; display: block;
  border-radius: var(--radius-lg); border: 1px solid var(--border);
}

/* The lead entry is deliberately bigger. An index has a first item, and that
   difference is what stops two entries from reading as a grid of two. */
@media (min-width: 760px) {
  /* Same -10% as the display scale, and for the same reason: the lead numeral
     was 64px against a 49.6px h1, and the lead title 32px against a 31.2px
     section title — the card was out-shouting the page. Kept proportional to
     each other so the lead entry stays deliberately bigger than the rest. */
  .case-no { font-size: 2.35rem; }
  .case-lead .case-no { font-size: 3.6rem; }
  .case-lead .case-title { font-size: 1.8rem; }
  .case-lead .case-text { font-size: 1.06rem; }
  /* With a cover image the entry becomes a real two-part composition: the text
     keeps its measure and the visual takes the space that would be empty. */
  .case:has(.case-visual) {
    display: grid; gap: var(--s6);
    grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
    align-items: start;
  }
  .case:has(.case-visual) .case-band { grid-column: 1 / -1; }
}

/* The homepage digest must not compete with the hero: no numerals, no display
   type, tighter rhythm. Same content rules, quieter voice. */
.case-list-compact .case { padding-block: var(--s5); gap: var(--s2); }
.case-list-compact .case-title { font-size: 1.15rem; }

.detail-cover { width: 100%; border-radius: var(--radius-lg); border: 1px solid var(--border); }

/* ---------- channels ---------- */
.channels { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); max-width: 520px; }
.channel {
  display: flex; align-items: center; gap: var(--s4);
  min-height: 56px; padding-inline: var(--s4);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); text-decoration: none; font-weight: 600;
  box-shadow: var(--shadow);
}
.channel:hover { border-color: var(--green); box-shadow: var(--shadow-lift); }
.channel-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 10px;
  background: var(--green-soft); color: var(--green); flex: none;
}
.channel-label { flex: 1; }
/* A filled brand mark reads heavier than an outline icon at the same box size,
   so it is nudged down to sit on the same optical weight. A TRANSFORM, not a
   width override: the glyph keeps whatever size the caller asked for, so this
   correction works at every size instead of stretching it to fill its tile. */
.icon-brand { transform: scale(.88); }
.channel-go { color: var(--muted); }

/* ---------- contact: two kinds of information, presented differently ------- */
.contact-grid { display: grid; gap: var(--s7); align-items: start; }
@media (min-width: 900px) { .contact-grid { grid-template-columns: .9fr 1.1fr; gap: var(--s8); } }
.contact-details .section-title { margin-block-start: var(--s6); }
.contact-details .section-title:first-child { margin-block-start: 0; }

/* A social account is a pointer, not a piece of contact data, so it is a chip
   rather than another full-width row. Presenting both at the same weight is
   what made the old page read as one undifferentiated stack. */
.social-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s3); }
.social-link {
  display: inline-flex; align-items: center; gap: var(--s3);
  min-height: 48px; padding-inline: var(--s4);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-pill); text-decoration: none; font-weight: 600;
}
.social-link:hover { border-color: var(--green); box-shadow: var(--shadow); }
/* Optical alignment by CONTAINER, not by distorting the glyph: the mark keeps
   its official geometry and the box does the balancing. */
.social-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; color: var(--green); flex: none;
}

/* ---------- contact form ---------- */
.contact-form { display: grid; gap: var(--s4); }
.field { display: grid; gap: var(--s2); margin: 0; }
.field label { font-weight: 600; font-size: .95rem; }
.field input, .field textarea {
  font: inherit; color: var(--text);
  padding: var(--s3) var(--s4); min-height: 48px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); width: 100%;
}
.field textarea { min-height: 150px; resize: vertical; line-height: 1.7; }
.field input:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--green); outline-offset: 2px; border-color: var(--green);
}
.field [aria-invalid="true"] { border-color: var(--error); }
/* The honeypot is hidden from people AND from assistive technology, so only an
   automated filler ever completes it. Not `display:none`: some bots skip that.
   And NOT `left:-9999px` either — in an RTL document that pushes a 10,000px box
   off the far side and gives the whole page a horizontal scrollbar, which is
   exactly what the viewport sweep caught. Clipping keeps the field in the DOM
   without giving it any size to overflow with. */
.hp {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.form-note {
  display: flex; align-items: center; gap: var(--s2);
  margin: 0 0 var(--s4); padding: var(--s3) var(--s4);
  border-radius: var(--radius); font-weight: 600;
}
.form-ok { background: var(--green-soft); color: var(--success); border: 1px solid var(--success); }
.form-warn { background: var(--gold-soft); color: var(--warning); border: 1px solid var(--warning); }

/* ---------- about: story, statement, values ---------- */
/* About uses the hero's portrait system verbatim and only re-scales it: the two
   pages must read as one identity system, so the bracket, the frame and the TOA
   plate are the SAME rules, not a second treatment. */
.about-portrait { justify-self: stretch; }
.about-portrait .portrait-frame { max-width: 100%; }
.about-portrait .portrait-bracket { inset-block-start: -12px; inset-inline-start: -12px; width: 48%; }
/* THE PLATE HUGS THE SYMBOL (owner finding).
   Measured before: plate 66x40 with the artwork at 40x22 — the symbol filled
   only 33% of the plate, so two thirds was empty white and it read as a button
   rather than an identity signature. The container also had a FIXED size, so on
   a short landscape viewport, where the portrait itself is smaller, the plate
   stayed the same and grew relatively more dominant — which is why the owner saw
   it worsen in landscape.

   Corrected by sizing the CONTAINER to the artwork, not the artwork to the
   container: tight symmetric padding, a radius that matches that padding, and a
   ceiling relative to the portrait so the plate can never dominate a small one.
   The official artwork is untouched — it is not scaled up to fill anything. */
.about-portrait .portrait-brand {
  inset-block-end: -10px;
  inset-inline-end: 16px;
  padding: 6px 8px;
  border-radius: 8px;
  border-block-end-width: 2px;
  box-shadow: 0 4px 12px rgba(26,33,28,.14);
  /* Never wider than this share of the portrait, at any orientation. */
  max-width: 40%;
}
.about-portrait .portrait-brand img {
  /* Scales gently with the viewport instead of being pinned, so the plate keeps
     the same visual weight on a short landscape screen as on a tall one. */
  height: clamp(16px, 1.4vw + 12px, 24px);
  width: auto;
  max-width: 100%;
  /* Explicitly neutralise any inherited portrait decoration: the symbol is
     artwork, not a framed photograph. */
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.about-role { margin: var(--s4) 0 0; display: grid; gap: var(--s1); }
.about-role-title { font-weight: 700; color: var(--green); }
.about-role-org { display: flex; align-items: center; gap: var(--s2); color: var(--muted); font-size: .95rem; }
.about-role-org svg { color: var(--gold-text); flex: none; }

/* The professional philosophy: still a distinct editorial statement with its
   quotation mark and brand hierarchy — but READ, not posed. Centring it made it
   a poster/slide; Arabic prose is right-anchored and English left-anchored, so
   the block follows the natural reading edge and keeps a comfortable measure.
   Only the alignment changed; the treatment and its prominence are unchanged. */
.statement {
  margin: 0; display: grid; gap: var(--s3);
  justify-items: start; text-align: start;
  max-width: 62ch;
  /* A gold rule on the reading edge marks it as a statement without centring. */
  border-inline-start: 3px solid var(--gold);
  padding-inline-start: var(--s5);
}
.statement-mark { color: var(--gold); }
.statement blockquote { margin: 0; }
.statement blockquote p {
  margin: 0; color: var(--green); font-weight: 700;
  /* Max only, matching the display scale: -10% on desktop, unchanged below 800px. */
  font-size: clamp(1.1rem, 1rem + .7vw, 1.35rem); line-height: 1.7;
}
.statement figcaption { color: var(--gold-text); font-weight: 600; font-size: .92rem; }

/* Professional values: a balanced last row.

   `.grid-3` is `auto-fit` with a 240px minimum, which is right almost
   everywhere and wrong here: at a wide desktop it fits FOUR of these tiles on
   one line, so the owner's five values landed as 4 + 1 — a full row and an
   orphan. The fix is a fixed three-per-row rhythm and a centred remainder.

   SIX TRACKS, NOT THREE, so a leftover card can sit on a half-column boundary
   and end up centred. Each tile spans two tracks, which reads as three per
   row; a single remainder takes the middle two, and a pair starts one track in
   so it is inset equally at both edges.

   COUNTED, NOT HARDCODED. The rules key off each tile's position among its
   siblings, so 1, 2, 3, 4, 5 or 6 values all balance — and they count RENDERED
   tiles, which is what matters: the template skips a value with no title and
   no description, so the DOM is the only honest source of the number.

   Desktop only. Below this breakpoint `.grid-3` is untouched and remains
   authoritative, and nothing here is a physical left or right. */
@media (min-width: 860px) {
  .about-values-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }

  .about-values-grid > .tile { grid-column: span 2; }

  /* One card left over after full rows of three: centre it. */
  .about-values-grid > .tile:last-child:nth-child(3n + 1) { grid-column: 3 / span 2; }

  /* Two left over: start the first a track in, and let the second follow into
     tracks 4-5 on its own. */
  .about-values-grid > .tile:nth-last-child(2):nth-child(3n + 1) { grid-column: 2 / span 2; }
}

/* ---------- buttons ---------- */
.actions { display: flex; flex-wrap: wrap; gap: var(--s3); margin-block-start: var(--s5); }
.btn {
  /* A real <button> does not inherit the page font: browsers give form
     controls the system UI font instead. Anchor `.btn` elements inherit
     normally, so the submit control was the only one wearing a different
     typeface — visible on the Arabic contact form, where the page is Cairo and
     the button was not. `font: inherit` is what the text inputs above already
     use, and it makes every real control follow whichever font the locale set
     on the body. It stays FIRST so the weight below still wins. */
  font: inherit;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: 48px; padding: var(--s3) var(--s5);
  border-radius: var(--radius-pill); text-decoration: none; font-weight: 700;
  border: 1px solid transparent;
}
.btn-primary { background: var(--green); color: #FFF; box-shadow: var(--shadow); }
.btn-primary:hover { background: var(--green-hover); color: #FFF; }
.btn-secondary { background: var(--surface); color: var(--green); border-color: var(--border); }
.btn-secondary:hover { border-color: var(--green); color: var(--green); }
.btn-text { padding-inline: 0; min-height: 44px; color: var(--green); font-weight: 700; }

.cta-band { text-align: center; }
.cta-band .actions { justify-content: center; }
.cta-title { color: var(--green); }

.crumbs { padding-block-start: var(--s5); }
.crumbs ol { display: flex; align-items: center; gap: var(--s2); list-style: none; margin: 0; padding: 0; font-size: .95rem; }
.crumbs a { display: inline-flex; align-items: center; min-height: 44px; }
.crumbs li + li::before { content: "/"; margin-inline-end: var(--s2); color: var(--muted); }

/* ABOUT OPENING COMPOSITION.
   Mobile and tablet keep the accepted stacked layout, unchanged.

   DESKTOP (owner finding, Safari/macOS): a rigid two-column grid gives each
   column its own cell, so whichever column ends first leaves a large empty
   block beside the other — measured at 1024/1280/1440 with roughly 150px of
   dead space in the shorter column. Nothing was wrong with the content; the
   grid was reserving a cell it had no way to release.

   The fix is to stop making it a cell. The identity block FLOATS at the
   opening, so the biography sits beside it and then simply continues beneath it
   at full measure once the portrait ends — which is how an editorial page
   behaves. No filler, no stretched portrait, no moved sections; the opening
   composition and the reading order are unchanged. */
.about-grid { display: grid; gap: var(--s6); }
/* Mobile/tablet keep the ACCEPTED visual order — biography, then identity —
   even though the identity block now comes first in the source for the desktop
   float. Grid items honour `order`, so nothing about the phone layout changes. */
.about-grid > .prose { order: 1; }
.about-grid > .about-side { order: 2; }
@media (min-width: 860px) {
  /* `order` has no effect on a block container, so desktop follows source order
     and the float wraps the biography around the identity block. */
  .about-grid { display: block; }
  .about-side {
    width: min(34%, 340px);
    margin-block-end: var(--s5);
  }
  /* Direction-aware by attribute rather than by a logical float keyword or a
     direction pseudo-class, both of which are newer than the Safari versions
     this must serve. `dir` is set on <html> by the shell, so these resolve. */
  html[dir="rtl"] .about-side { float: left; margin-inline-end: var(--s7); }
  html[dir="ltr"] .about-side { float: right; margin-inline-start: var(--s7); }
  /* Beside the float the available width is already narrowed to a comfortable
     measure by the portrait itself, so the paragraph cap is relaxed slightly —
     otherwise the text stops short and reopens the very gap this fix closes.
     It is still capped, so lines below the float never run the full page. */
  .about-grid .prose p { max-width: 74ch; }
  /* Contain the float so the following sections are never pulled up beside it. */
  .about-grid::after { content: ""; display: block; clear: both; }
}
/* SOURCE OF THE FAINT OVAL AROUND THE SYMBOL (owner finding).
   This rule predates unifying the About portrait with the hero system, when the
   About photo was a bare <img>. It matched EVERY img inside .about-portrait —
   including the TOA symbol inside the identity plate — so the symbol was given a
   22px radius, a 1px border and a shadow. The official symbol contains four
   <path> elements and no circle, ellipse, rect or stroke, so the outline was
   never part of the artwork: it was this CSS. The photo already gets its radius
   from `.portrait-frame img`, so the rule is simply removed rather than
   re-scoped — nothing else depended on it. */

/* ---------- digital card ---------- */
.card-page { padding-block: var(--s7); display: flex; justify-content: center; }
.vcard {
  width: min(100%, 460px); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift); overflow: hidden;
}
.vcard-head {
  text-align: center; padding: var(--s6) var(--s5) var(--s5);
  background: linear-gradient(180deg, var(--green-soft), transparent);
  border-block-end: 1px solid var(--border);
}
/* The TOA mark leads the card. It is the owner's primary identity, so it is
   given real presence rather than being shrunk to save a few pixels. */
.vcard-logo { height: 46px; width: auto; }
.vcard-photo {
  display: block; margin: var(--s4) auto var(--s3); width: 116px; height: 116px;
  border-radius: 50%; object-fit: cover; object-position: top center;
  border: 3px solid var(--surface); box-shadow: var(--shadow-lift);
}
/* Name, role and tagline are THREE different facts and are set as three
   different things (P7-OPEN-03, resolved by owner direction). The role is the
   current position; the tagline is a motto and is never dressed as a job. */
.vcard-name { font-size: 1.5rem; color: var(--green); margin-block-end: var(--s1); }
.vcard-role { color: var(--text); font-weight: 700; margin: 0 0 var(--s1); }
.vcard-org {
  display: inline-flex; align-items: center; gap: var(--s2);
  color: var(--muted); font-size: .93rem; margin: 0;
}
.vcard-org svg { color: var(--gold-text); flex: none; }
.vcard-tagline {
  color: var(--gold-text); font-weight: 600; font-size: .95rem;
  margin: var(--s3) auto 0; max-width: 32ch;
}
.vcard-channels { padding: var(--s5) var(--s5) 0; max-width: none; }
.vcard-social {
  list-style: none; margin: 0; padding: var(--s4) var(--s5) 0;
  display: flex; justify-content: center; gap: var(--s3);
}
.vcard-social a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 48px; height: 48px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--surface); color: var(--green);
}
.vcard-social a:hover { border-color: var(--green); box-shadow: var(--shadow); }
.vcard-actions {
  padding-inline: var(--s5); margin-block-start: var(--s5);
  display: grid; gap: var(--s3);
}
.vcard-actions .btn { width: 100%; }
/* Copy feedback. Empty and invisible until something is announced, so it never
   reserves space or shifts the card layout. */
.share-status {
  margin: var(--s3) var(--s5) 0; text-align: center;
  color: var(--success); font-weight: 600; font-size: .9rem;
  min-height: 0;
}
.share-status:empty { display: none; }

/* The QR closes the card, as it did in V1: this is the thing a person actually
   points a phone at. It encodes the canonical URL and nothing else. */
.vcard-qr {
  margin: 0; padding: var(--s5);
  display: grid; justify-items: center; gap: var(--s3);
  border-block-start: 1px solid var(--border);
  background: var(--bg);
}
.vcard-qr img {
  width: 180px; height: 180px; display: block;
  padding: var(--s3); background: #FFF;
  border: 1px solid var(--border); border-radius: var(--radius);
}
.vcard-qr figcaption { display: grid; justify-items: center; gap: var(--s2); text-align: center; }
.vcard-qr-hint {
  display: inline-flex; align-items: center; gap: var(--s2);
  color: var(--muted); font-size: .92rem;
}
.vcard-qr-hint svg { color: var(--gold-text); flex: none; }
.vcard-url {
  color: var(--muted); font-size: .85rem; word-break: break-all;
  direction: ltr; unicode-bidi: embed;
}

/* ---------- footer ----------
   Green/gold direction APPROVED by the owner and kept. What was rejected was
   the MOBILE composition: one link per full-width row turned "تصفّح" into a long
   column and the whole footer into a second page. So the mobile layout is
   designed for mobile rather than inherited by stacking the desktop columns.

   Contrast on #0D3B2E, from the palette already approved in P7:
     #FFFFFF        12.48:1   name, links, copyright
     rgba(255,.86)  ~9.6:1    disclaimer, tagline, role
     #C79A2E        4.81:1    headings and accents only  */
.site-footer {
  margin-block-start: var(--s8);
  background: var(--green);
  color: #FFF;
  border-block-start: 3px solid var(--gold);
}
.footer-inner {
  display: grid; gap: var(--s6);
  padding-block: var(--s6) var(--s5);
  grid-template-columns: 1fr;
  /* Columns size to their own content. Stretching made every column as tall as
     the tallest, which turned one long list into empty space in the others. */
  align-items: start;
}
@media (min-width: 640px) {
  .footer-inner { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s7) var(--s6); }
  .footer-brandbox { grid-column: 1 / -1; }
}
@media (min-width: 1000px) {
  /* Padding reduced from --s8 (72px) to --s6: measured, that 72px WAS the large
     empty green field the owner saw above the content. The footer height now
     follows its content plus balanced padding, not a reserved block. */
  .footer-inner { grid-template-columns: 1.3fr .85fr 1fr; gap: var(--s7); padding-block: var(--s6) var(--s5); }
  .footer-brandbox { grid-column: auto; }
}

.footer-markplate {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface); border-radius: 10px;
  padding: 10px 14px; margin-block-end: var(--s3);
}
.footer-mark { display: block; height: 30px; width: auto; }
.footer-name { margin: 0; font-weight: 800; font-size: 1.15rem; color: #FFF; }
.footer-role { margin: var(--s1) 0 0; color: var(--gold); font-weight: 600; font-size: .92rem; }
.footer-tagline {
  margin: var(--s2) 0 0; color: rgba(255,255,255,.86);
  font-size: .92rem; max-width: 34ch; line-height: 1.7;
}

.footer-group { min-width: 0; }
.footer-heading {
  font-size: .74rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--gold); margin: 0 0 var(--s3);
  display: flex; align-items: center; gap: var(--s3);
}
.footer-heading::after { content: ""; flex: 1; height: 1px; background: rgba(199,154,46,.4); }

/* MOBILE DENSITY. Navigation is a two-column grid, not a stack of full-width
   rows: same six destinations in three rows instead of six, which is most of
   the height the owner objected to. Targets stay >=44px because the row height
   is the target — the saving comes from pairing, not from shrinking. */
.footer-explore ul {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 var(--s4);
}
.footer-channels { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s2); }
.footer-explore a {
  display: flex; align-items: center; min-height: 44px;
  color: rgba(255,255,255,.92); text-decoration: none; font-weight: 600; font-size: .95rem;
}
/* Channels become compact chips rather than another vertical block. */
.footer-channels a {
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: 44px; padding-inline: var(--s3);
  border: 1px solid rgba(255,255,255,.22); border-radius: var(--radius-pill);
  color: rgba(255,255,255,.92); text-decoration: none; font-weight: 600; font-size: .9rem;
}
.footer-explore a:hover, .footer-channels a:hover { color: var(--gold); }
.footer-channels a:hover { border-color: var(--gold); }
.footer-explore a:focus-visible, .footer-channels a:focus-visible {
  outline: 2px solid var(--gold); outline-offset: 3px; border-radius: 6px;
}
.footer-channel-icon { display: inline-flex; color: var(--gold); flex: none; }
/* Navigation stays a TWO-COLUMN grid on desktop too (owner finding): six links
   in a single column made that area unnecessarily tall and left the Contact and
   Identity areas visually unbalanced beside it. A previous round returned it to
   one column above 1000px; that override is removed. Six destinations now sit in
   three rows at every width, with the row height still the 44px target. */
@media (min-width: 1000px) {
  .footer-explore ul { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s5); }
}

/* ONE surface: the baseline is separated by a hairline, never by a second
   background. Disclaimer and copyright are one closing area. */
.footer-baseline {
  border-block-start: 1px solid rgba(255,255,255,.16);
  padding-block: var(--s4) var(--s5);
  display: grid; gap: var(--s2);
}
@media (min-width: 860px) {
  /* Where it fits comfortably, one balanced row. */
  .footer-baseline {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center; gap: var(--s6);
  }
}
.footer-disclaimer {
  margin: 0; color: rgba(255,255,255,.86);
  font-size: .88rem; line-height: 1.75; max-width: 68ch;
}
.footer-copy {
  margin: 0; color: rgba(255,255,255,.82); font-size: .88rem;
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
}
/* DISPLAY TREATMENT ONLY (owner direction): the domain is shown as brand text.
   `text-transform` changes what is PAINTED and nothing else — the href, the
   canonical origin, the QR target, Open Graph, structured data and the vCard
   all keep `https://toa.sa` exactly as they are. */
.footer-domain {
  font-weight: 700; direction: ltr; color: var(--gold);
  text-transform: uppercase; letter-spacing: .04em;
}

.preview-banner { margin: 0; padding: var(--s3) var(--s5); background: var(--warning); color: #FFF; font-weight: 600; text-align: center; }

/* A secondary UTILITY, not a call to action. It was reading as a primary CTA:
   a large shadowed pill with a label. It is now a compact square control —
   44x44 so the touch target is intact, but visually quiet: no lift, muted
   border, and the label is available to assistive technology rather than
   competing for attention on screen. */
.to-top {
  position: fixed; inset-block-end: var(--s5); inset-inline-end: var(--s5);
  display: none; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow);
  text-decoration: none; color: var(--muted);
}
.to-top:hover { color: var(--green); border-color: var(--green); }
.to-top span {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.to-top .icon { transform: rotate(180deg); }
.to-top.is-visible { display: inline-flex; }
/* Stood down while the footer is on screen, so it can never cover the
   disclaimer, a heading, the links or the copyright. */
.to-top.is-parked { display: none; }

body.minimal main { min-height: 55vh; padding-block: var(--s8); }

/* ---------- scroll reveal (the V1 interaction, restored) ----------
   NOTHING is hidden by CSS alone. `.reveal` on its own is fully visible, so a
   visitor with no JavaScript — or with JavaScript that failed — gets the whole
   page, not empty sections.

   The hidden state lives in `.reveal-pending`, which is applied by script and
   ONLY to elements that are already below the fold. An element that is on
   screen when the page loads is never hidden, so there is no flash of content
   disappearing and re-appearing, and no inline script is needed to prevent one.
   That matters: the public shell emits no inline executable script at all, and
   this enhancement does not become the reason to start. */
.reveal.reveal-pending { opacity: 0; transform: translateY(18px); }
.reveal.is-visible {
  opacity: 1;
  transform: none;
  transition: opacity .5s cubic-bezier(.22, .61, .36, 1),
              transform .5s cubic-bezier(.22, .61, .36, 1);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  /* Motion is a preference, not a gate: the content is simply there. */
  .reveal, .reveal.reveal-pending { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
  .btn, .channel, .project-cta { transition: background-color .18s, border-color .18s, gap .18s, box-shadow .18s; }
}
