/* =========================================================================
   منصة العقارات — لوحة تحكم الإدارة
   Real Estate Platform — Admin Control Panel
   -------------------------------------------------------------------------
   Design constraints (hard rules):
     1. Two colours only. Every colour token is the ink, the paper, or an alpha
        blend of the two. The ink is a very dark brown and the paper is white;
        that single hue is stated once, in --umber, and nowhere else.
     2. No emoji anywhere. All glyphs are vector icons from Lucide.
     3. Fully responsive, RTL + LTR, via CSS logical properties.
   ========================================================================= */

/* ------------------------------------------------------------- Typography */
/* خط الجزيرة العربي، مستضاف من public/assets/fonts/ لا من شبكة خارجية: لا طلب
   إلى نطاق آخر، ولا سقوطٌ إلى خطٍّ بديل إن انقطعت تلك الشبكة.

   والقيد الذي يحكم هذا التعريف هو أن الملف لا يحمل حرفاً لاتينياً واحداً — فيه
   الحروف العربية وأرقامها وعلاماتها فقط. فلو أُعطي كل النصوص لسقط كل حرف
   إنجليزي إلى الخط التالي حرفاً حرفاً، وهو ما يبدو كخطأ لا كاختيار. ولذلك
   يحدّه unicode-range بما يملكه فعلاً: العربية وحدها والمسافة معها، ويبقى
   ما عداها لـ Segoe UI بعده في السلسلة. وأما المسافة فلم تكن معها، وسببُ
   ضمّها مشروحٌ مع ascent-override أدناه.

   Al-Jazeera Arabic, hosted from public/assets/fonts/ rather than a third-party
   network: no request to another origin, and no fallback face if that network
   is unreachable.

   The constraint shaping this declaration is that the file carries no Latin
   letter at all — only Arabic letters, their digits and their punctuation. Were
   it handed every string, each English letter would drop to the next font one
   character at a time, which reads as a bug rather than a choice. So
   unicode-range confines it to what it actually holds — Arabic alone, and the
   space with it — while everything else stays with Segoe UI further down the
   stack. The space was not always in that list; why it joined is explained with
   ascent-override below.

   والأرقام اللاتينية (U+0030-0039) مستثناة عمداً وإن كانت في الملف: الجداول
   وبطاقات الأرقام كلها على font-variant-numeric: tabular-nums، وأرقام هذا الخط
   ليست متساوية العرض، فإدخالها يهزّ محاذاة كل عمود رقمي في اللوحة.

   The Latin digits (U+0030-0039) are left out deliberately even though the file
   has them: the tables and figure cards all run on font-variant-numeric:
   tabular-nums, and this face's digits are not monospaced — including them
   would unsettle the alignment of every numeric column in the console.

   ولا يوجد من العائلة إلا الوزن العادي (400). ومتى توفّر ملفا Bold وLight
   يُوضعان في المجلد نفسه ويُضاف لكلٍّ منهما @font-face بوزنه، ثم يُرفع
   font-synthesis من body أدناه.

   Only the regular weight (400) exists in the family. Should Bold and Light
   files arrive, they go in the same folder with an @font-face each at its own
   weight, and the font-synthesis line on body below is lifted.

   وأما ascent-override وأخواتها فتصف الخطَّ كما يرسم لا كما يعلن، والوصف
   الأول كان ناقصاً. قياساً بالمحرّك نفسه على أسماء المشاريع والإعلانات
   الحقيقية: أعلى ما ترسمه الكلمة العربية 1.04em فوق خطّ الكتابة — الهمزةُ على
   الألف — وأعمق ما تنزله 0.40em تحته، وهو ذيل الجيم والياء في شكلهما الأخير.
   أي أن حبرها يشغل 1.44em، بينما كان الوصف يعلن 1.31em (‎96%‎ و‎35%‎)، فيبقى من
   الذيل خارج الصندوق ما يقصّه أول عنصر عليه overflow: hidden.

   والسبب الثاني أدقّ من الأول وأثقل أثراً، وهو U+0020 في السطر الأخير:
   المسافة بين كلمتين عربيتين كانت خارج unicode-range، فتُؤخذ من Segoe UI،
   وصعودُه ‎107.9%‎ مقابل ‎96%‎ هنا. فكان الخطُّ اللاتيني — من أجل فراغٍ لا حبر
   فيه — هو من يحدّد موضع خطّ الكتابة في كل عنوانٍ عربي من كلمتين، يدفعه إلى
   أسفل الصندوق ويأكل ما تحته من مساحة الذيل. ولهذا كان اسم المشروع من كلمتين
   يُقصّ، والاسم من كلمة واحدة لا يُقصّ، وبينهما فراغٌ واحد. والمسافة الآن من
   هذا الملف: عرضها ‎0.256em‎ مقابل ‎0.274em‎ في Segoe UI — فرقٌ لا يُرى في
   اللاتينية، ومكسبُه أن سطر الكلمتين العربيتين صار من خطٍّ واحد.

   والقيمتان ‎110%‎ و‎50%‎: الصعود فوق صعود Segoe UI بهامش، فحيثما وقعت لاتينيةٌ
   في عنوان عربي بقي القرار للخط العربي؛ والهبوط فوق حبر الذيل (‎0.40em‎)
   بهامشٍ يبقى بعد أن يقرّب المحرّك القياسات إلى البكسل الصحيح.

   The ascent-override family describes the face as it draws rather than as it
   declares, and the first description was short. Measured through the engine
   itself over the real project and listing names: an Arabic word reaches
   1.04em above the baseline — the hamza over alef — and 0.40em below it, the
   tail of jeem and yaa in their final forms. Its ink occupies 1.44em, while
   the description declared 1.31em (96% and 35%), leaving enough of the tail
   outside the box for the first ancestor with overflow: hidden to cut off.

   The second cause is subtler than the first and weighs more, and it is the
   U+0020 on the last line. The space between two Arabic words fell outside
   unicode-range, so it came from Segoe UI, whose ascent is 107.9% against 96%
   here. For the sake of a blank carrying no ink, the Latin face was deciding
   where the baseline sat in every two-word Arabic heading — pushing it down
   the box and eating the room beneath it. Which is why a two-word project name
   was cut and a one-word name was not, with a single space between the two
   cases. The space now comes from this file: 0.256em wide against Segoe UI's
   0.274em — a difference invisible in Latin, and worth it because a line of
   two Arabic words is now set in one face.

   Hence 110% and 50%: the ascent clears Segoe UI's by a margin, so wherever
   Latin does land inside an Arabic heading the decision stays with the Arabic
   face; and the descent clears the 0.40em of tail by a margin that survives
   the engine rounding its metrics to whole pixels. */
@font-face {
  font-family: "Al-Jazeera-Arabic";
  src: local("Al-Jazeera-Arabic"),
       local("Al-Jazeera-Arabic-Regular"),
       url("../fonts/al-jazeera/al-jazeera-arabic-regular.woff2") format("woff2"),
       url("../fonts/al-jazeera/al-jazeera-arabic-regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  ascent-override: 110%;
  descent-override: 50%;
  line-gap-override: 0%;
  unicode-range: U+0020, U+0600-06FF, U+200C-200F, U+FB50-FDFF, U+FE70-FEFF;
}

/* ---------------------------------------------------------------- Tokens */
:root {
  /* لون اللوحة الواحد: بنّيٌّ غامق جداً يقوم مقام الأسود.
     -------------------------------------------------------------------------
     كان الحبر أسود خالصاً، فكانت اللوحة رماديّةً لا نسب لها: نهاراً حرفٌ أسود
     على ورقة، وليلاً سوادٌ تامّ تحت كل شيء. والمنصّة نفسها دافئة — ذهبها ورملها
     وبنّيها في بوابة الزبون — فكانت الإدارة وحدها هي التي لا تشبه ما تديره.

     والبديل درجةٌ واحدة لا لوحةُ ألوان: ‎#241509‎، بنّيٌّ في عمق السواد. يُقرأ
     حبراً في النص الصغير حيث لا يحتمل الحرف لوناً، ويُقرأ بنّيّاً صريحاً حين
     يملأ سطحاً أو شريطاً أو أرضَ الليل كلها. ونسبته إلى الأبيض ‎17.7:1‎، أي فوق
     ما كان يوفّره الأسود من احتياطٍ على أي حال.

     ولأن كل رمزٍ بعده مشتقٌّ منه — بالشفافية أو بالمزج — فهذا السطر وحده يحمل
     الدفء إلى كل حدٍّ وظلٍّ وسطحٍ في السمتين معاً، ولا يبقى في الملف لونٌ
     مكتوبٌ مرّتين.

     The console's one hue: a very dark brown standing where black stood.

     The ink was pure black, which left the console greyscale and unrelated to
     anything: black letters on paper by day, total black beneath everything by
     night. The platform itself is warm — its gold, sand and brown carry the
     customer portal — and the admin side alone looked nothing like what it
     administers.

     The replacement is one step rather than a palette: #241509, a brown deep
     enough to sit where black sat. It reads as ink at body size, where a letter
     cannot afford a colour, and as plain brown wherever it fills a surface, a
     rail, or the whole ground at night. Against white it measures 17.7:1 —
     above what black had in hand to spare anyway.

     And because every token below is derived from it, by alpha or by mix, this
     line alone carries the warmth into every border, shadow and surface in both
     themes — and no colour in this file is written down twice. */
  --umber: 36 21 9;

  /* The only two colours in the system, expressed as channels so that the
     dark theme can invert them with a single declaration. */
  --ink: var(--umber);
  --paper: 255 255 255;

  --fg: rgb(var(--ink));

  /* الأرض والسطح ليسا لوناً واحداً.
     -------------------------------------------------------------------------
     كانا كذلك: ‎--bg‎ و‎--surface‎ كلاهما الورقة نفسها، والبطاقة لا يفصلها عن
     أرضها إلا حدٌّ شعرة عرضُه بكسل وشفافيته ‎0.14‎. فكان اللوح يُقرأ صفحةً
     واحدة مسطّحة، ولا يُرى أين تبدأ البطاقة وأين تنتهي إلا بتحديق.

     والفصل درجةٌ واحدة لا لونٌ ثانٍ، ومأخوذة من الحبر نفسه بـcolor-mix، فيبقى
     النظام على لونيه: في النهار تنزل الأرضُ درجةً نحو الحبر ويبقى السطح ورقةً
     خالصة، وفي الليل تبقى الأرض حبراً خالصاً ويصعد السطح درجةً نحو الورقة.
     والقاعدة واحدة في الحالين: البطاقة أقرب إلى الورقة ممّا تحتها — فترتفع
     عن أرضها في السمتين معاً بالمنطق نفسه لا بقاعدتين متعاكستين.

     Ground and surface are not one colour.

     They were: --bg and --surface were both the paper itself, and nothing
     separated a card from what it sat on but a hairline one pixel wide at 0.14
     opacity. The console read as a single flat sheet, and where a card began
     and ended could not be seen without staring.

     The separation is one step rather than a second colour, taken from the ink
     itself with color-mix, so the system keeps its two: by day the ground steps
     toward the ink and the surface stays pure paper; by night the ground stays
     pure ink and the surface steps toward the paper. The rule is the same in
     both: the card is nearer the paper than what lies under it — so it lifts
     off its ground in either theme by one logic rather than two opposite ones. */
  --bg: color-mix(in srgb, rgb(var(--ink)) 7%, rgb(var(--paper)));

  --fg-1: rgb(var(--ink) / 1);
  --fg-2: rgb(var(--ink) / 0.66);
  --fg-3: rgb(var(--ink) / 0.46);
  --fg-4: rgb(var(--ink) / 0.30);

  --line: rgb(var(--ink) / 0.14);
  --line-strong: rgb(var(--ink) / 0.30);
  --line-soft: rgb(var(--ink) / 0.07);

  --surface: rgb(var(--paper));
  --surface-1: rgb(var(--ink) / 0.026);
  --surface-2: rgb(var(--ink) / 0.055);
  --surface-3: rgb(var(--ink) / 0.09);
  --surface-inv: rgb(var(--ink));
  --on-inv: rgb(var(--paper));

  --shadow-1: 0 1px 2px rgb(var(--ink) / 0.05);
  --shadow-2: 0 4px 16px rgb(var(--ink) / 0.08);
  --shadow-3: 0 18px 48px rgb(var(--ink) / 0.18);

  /* الحواف مقصوصة إلى خُمس ما كانت: كانت 6/9/14 و999 للكبسولة، فصارت الأشكال
     شبه قائمة الزوايا. والقيمة رمز واحد لا رقم مبعثر، فتغيير السطور الأربعة
     التالية يعيد ضبط استدارة كل زر وبطاقة ورقاقة في السطحين معاً.

     Radii cut to a fifth of what they were: 6/9/14 and 999 for the capsule,
     now near-square. The value is one token rather than a number scattered
     about, so these four lines reset the rounding of every button, card and
     chip across both surfaces at once. */
  --r-sm: 1px;
  --r-md: 2px;
  --r-lg: 3px;
  --r-pill: 4px;

  /* الدائرة شكلٌ لا مقدار استدارة، فلا تُقاس بالمقياس أعلاه ولا تتأثر به.
     A circle is a shape rather than an amount of rounding, so it is not
     measured by the scale above and does not move with it. */
  --r-circle: 50%;

  --sidebar-w: 268px;
  --sidebar-w-rail: 74px;
  --topbar-h: 62px;

  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 22px;
  --sp-6: 30px;
  --sp-7: 42px;

  /* الجزيرة أولاً للعربية وحدها بحكم unicode-range أعلاه، وما بعده هو الخط
     نفسه الذي كان: Segoe UI لللاتينية والأرقام، ثم بدائله على بقية الأنظمة.
     Al-Jazeera first, and for Arabic alone by the unicode-range above; what
     follows it is the same stack as before — Segoe UI for Latin and figures,
     then its counterparts on the other platforms. */
  --ff-sans: "Al-Jazeera-Arabic", "Segoe UI", "Noto Sans Arabic",
    "IBM Plex Sans Arabic", Tahoma, system-ui, -apple-system, "Helvetica Neue",
    Arial, sans-serif;
  --ff-mono: ui-monospace, "Cascadia Mono", "Consolas", "SFMono-Regular",
    "Liberation Mono", monospace;


  /* الشدّ الحرفي — letter tracking.

     السالب منه أداةٌ لاتينية: حروفٌ منفصلة يقرّبها المصمّم قليلاً فتبدو الكلمة
     كتلةً واحدة. أما العربية فحروفها موصولة أصلاً، والوصلة نفسها قياسٌ في
     الخط — فسحبها إلى الداخل يُقصّر الكشيدة ويدفع الحرف إلى جاره حتى تُقرأ
     الكلمة كخطٍّ واحد متعرّج. ولهذا تصير هذه الرموز normal تحت :lang(ar).

     Negative tracking is a Latin device: separate letters pulled slightly
     together so the word reads as one shape. Arabic letters are joined to begin
     with, and the join itself is a measured part of the design — pulling it
     inward shortens the connecting stroke and pushes each letter into the next
     until the word reads as a single jagged line. Which is why these tokens
     become normal under :lang(ar).

     والقاعدة على :root فيسري القرار على الصفحة كلها بلغة المستند — ولا تفقد
     اللاتينية شيئاً يُذكر حين تقع داخل صفحة عربية. */
  --tr-sm: -0.01em;
  --tr-md: -0.015em;
  --tr-lg: -0.02em;
  --tr-xl: -0.03em;

  --ease: cubic-bezier(0.32, 0.72, 0, 1);
  --dur: 0.22s;

  color-scheme: light;
}

/* لغة المستند عربية: يسقط الشدّ السالب — Arabic document: negative tracking off */
:root:lang(ar) {
  --tr-sm: normal;
  --tr-md: normal;
  --tr-lg: normal;
  --tr-xl: normal;
}

[data-theme="dark"] {
  /* والانقلاب تامّ: ما كان حبراً بالنهار صار ورقةً بالليل. فأرض الليل هي
     ‎--umber‎ نفسه لا سوادٌ ثانٍ يُكتب هنا — قيمةٌ واحدة في الملف تحكم السمتين،
     ولا تفترق إحداهما عن الأخرى إذا عُدِّلت.

     The inversion is exact: what is the ink by day becomes the paper by night.
     Night's ground is that same --umber rather than a second black written out
     here — one value in the file governing both themes, and neither drifting
     from the other when it is changed. */
  --ink: 255 255 255;
  --paper: var(--umber);

  /* الليل يقلب الاتجاه لا القاعدة: الأرض ورقةٌ خالصة (وهي هنا البنّي الغامق)
     والسطح يصعد درجةً نحو الحبر (وهو هنا البياض) — فتبقى البطاقة أفتح ممّا تحتها.
     Night reverses the direction rather than the rule: the ground is pure paper
     (the dark brown here) and the surface steps toward the ink (white here) — so
     the card stays lighter than what lies beneath it. */
  --bg: rgb(var(--paper));
  --surface: color-mix(in srgb, rgb(var(--ink)) 10%, rgb(var(--paper)));

  --line: rgb(var(--ink) / 0.18);
  --line-strong: rgb(var(--ink) / 0.38);
  --line-soft: rgb(var(--ink) / 0.09);
  --surface-1: rgb(var(--ink) / 0.04);
  --surface-2: rgb(var(--ink) / 0.08);
  --surface-3: rgb(var(--ink) / 0.13);
  --shadow-1: none;
  --shadow-2: 0 4px 16px rgb(0 0 0 / 0.7);
  --shadow-3: 0 18px 48px rgb(0 0 0 / 0.85);
  color-scheme: dark;
}

/* ------------------------------------------------------------- Baseline */
*,
*::before,
*::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg-1);
  font-family: var(--ff-sans);
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;

  /* لا ثقلَ مصنوعاً. الجزيرة لا تملك إلا وزناً واحداً، و110 قواعد في هذين
     الملفين تطلب 600 أو 650 أو 700 — فكان المتصفح يصنع الثقل بتغليظ الحدّ حول
     الحرف نفسه. وهذا في اللاتينية يُحتمل، أما في خطٍّ متصل فيملأ التغليظُ عيونَ
     الحروف ويلحم المتصل بجاره: تصير الكلمة كتلةً سوداء عريضة لا حروفاً. وهو
     الثقل والعرض والالتصاق التي شُكيت كلها من سببٍ واحد.

     والقاعدة تُلغي التصنيع لا الثقل: Segoe UI وConsolas تملك وزنَي Semibold
     وBold حقيقيين، فتأخذهما اللاتينية والأرقام كما كانت. ولا يمسّ هذا السطر
     إلا الخطَّ الذي لا وزن ثقيل له أصلاً — وفيه لا يبقى إلا وزنه الحقيقي،
     ويبقى تدرّج العناوين على المقاس واللون كما هو مبنيٌّ في هذين الملفين.

     No manufactured weight. Al-Jazeera has one weight, and 110 rules across
     these two files ask for 600, 650 or 700 — so the browser was making the
     weight by thickening a stroke around the letter itself. Latin survives
     that; a joined script does not. The thickening fills the letters' counters
     and welds each one to its neighbour, and the word becomes a wide black mass
     rather than letters. The thickness, the width and the crowding were all one
     cause.

     The rule cancels the synthesis rather than the weight: Segoe UI and
     Consolas carry real Semibold and Bold, so Latin and figures take them
     exactly as before. This line touches only the face that has no heavy weight
     to begin with — and there, nothing is left but its true one, while the
     hierarchy of the headings goes on resting on size and colour, which is
     where these two files already build it. */
  font-synthesis: style;
  font-synthesis-weight: none;
}

/* 1.55 لا 1.35: حبر الكلمة العربية في هذا الخط يشغل 1.44em — 1.04 فوق خطّ
   الكتابة و0.40 تحته — وما دون ذلك سطرٌ أضيق من الحرف الذي يحمله. والعنوان
   وحده هو الذي كان يُقصّ من النصوص كلها، لأنه وحده ما يُختصر بالنقاط الثلاث،
   والاختصار مبناه overflow: hidden. فكانت الفقرة تحته — وسطرها 1.55 — تظهر
   ذيولها كاملة والعنوان فوقها مقصوصاً.

   والقيمة مقيسة لا مقدّرة: جُرّبت 1.4 و1.45 و1.5 و1.55 على أسماء المشاريع
   الحقيقية عند كل مقاسٍ تستعمله الورقتان (11px إلى 16px)، فبقي الذيل مقصوصاً
   دون 1.5، وعند 1.5 نجا بربع بكسل — وهو هامشٌ لا يُعوَّل عليه بعد تقريب
   المحرّك. و1.55 تتركه سالماً بأربعة أخماس البكسل، وهي نفسها ارتفاع سطر
   المتن، فيستوي العنوان وفقرتُه على مقياسٍ واحد.

   1.55 rather than 1.35: an Arabic word's ink in this face occupies 1.44em —
   1.04 above the baseline and 0.40 below — and anything under that is a line
   narrower than the letter it carries. Of all the text, only headings were cut,
   because only headings are shortened with an ellipsis and the ellipsis is made
   of overflow: hidden. The paragraph beneath, on its 1.55 line, kept its tails
   whole while the heading above it lost them.

   The value is measured rather than judged: 1.4, 1.45, 1.5 and 1.55 were each
   run against the real project names at every size these two sheets use (11px
   to 16px). Below 1.5 the tail stays cut; at 1.5 it survives by a quarter of a
   pixel, which is no margin at all once the engine rounds. 1.55 leaves it whole
   by four fifths of a pixel — and it is the body's own line height, so a
   heading and its paragraph now sit on one measure. */
h1, h2, h3, h4, h5 { margin: 0; font-weight: 650; line-height: 1.55; letter-spacing: var(--tr-sm); }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

/* المتصفح يعطي figure هامشاً افتراضياً قدره 40px على كل جانب. وهو في مستند
   نصّي معقول، وفي شبكة عمودها 108px كارثة: لا يبقى للصورة إلا 28px.
   A browser gives figure a default 40px margin on each side. Reasonable in a
   prose document, ruinous in a grid whose column is 108px: it leaves the image
   28px to live in. */
figure, figcaption, dl, dd { margin: 0; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
img, svg { display: block; max-width: 100%; }
table { border-collapse: collapse; width: 100%; }

:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--fg-1);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

::selection { background: var(--fg-1); color: var(--surface); }

/* Scrollbars kept monochrome across engines */
* { scrollbar-width: thin; scrollbar-color: var(--fg-4) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--fg-4);
  border: 3px solid var(--bg);
  border-radius: var(--r-pill);
}
*::-webkit-scrollbar-thumb:hover { background: var(--fg-3); }

.skip-link {
  position: fixed;
  inset-block-start: -100px;
  inset-inline-start: var(--sp-4);
  z-index: 999;
  padding: 10px 18px;
  background: var(--fg-1);
  color: var(--surface);
  border-radius: var(--r-md);
  font-weight: 600;
}
.skip-link:focus { inset-block-start: var(--sp-4); }

.ico { flex: none; }
.mono { font-family: var(--ff-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.num { font-variant-numeric: tabular-nums; letter-spacing: var(--tr-md); }

/* علامة الدرهم الإماراتي.
   -------------------------------------------------------------------------
   رمزٌ أعلنه مصرف الإمارات المركزي في آذار ٢٠٢٥، وله في Unicode موضعٌ هو
   U+20C1 لم تلحق به الخطوط بعد — فكتابته حرفاً تُخرج مربّعاً فارغاً. ولذلك
   يُرسم شكلاً متجهاً، ومقاسه بـem لا بالبكسل فيكبر ويصغر مع الرقم الذي
   يلازمه، ولونه currentColor فلا يخرق أحاديّة لون اللوحة.

   The UAE dirham mark.

   A symbol the Central Bank unveiled in March 2025. Unicode has given it a
   place, U+20C1, that the fonts have not caught up with — typed as a
   character it comes out an empty box. So it is drawn as a vector, sized in
   em rather than pixels so it grows and shrinks with the figure beside it,
   and coloured by currentColor so it does not break the console's single
   colour.

   والشكل نفسه معرَّفٌ مرّةً في دفاتر المستند، وما هنا يشير إليه بـ<use>.
   The shape itself is defined once in the document's defs, and what is here
   points at it with a <use>. */
.money { white-space: nowrap; }

.aed {
  display: inline-block;
  /* نسبة الشكل ١١٥٠:١٠٠٠، فالعرض أوسع من الارتفاع — والمقاسان مكتوبان
     صراحةً فلا يقيسه المتصفح من viewBox في رمزٍ يشير إليه <use>.
     The shape is 1150:1000, so it is wider than it is tall — and both are
     stated outright, since a browser will not measure them off the viewBox of
     a symbol reached through <use>. */
  width: 0.9em;
  height: 0.78em;
  vertical-align: -0.035em;
  fill: currentColor;
  flex: none;
}

/* وفي الدبوس يصغر قليلاً: النص عليه أعرض من مكانه أصلاً.
   On a pin it shrinks a little: the text there is already wider than its
   room. */
.gm-tag .aed { width: 0.86em; height: 0.74em; }
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* بطاقة السطرين: عنوانٌ وسطرٌ صغير تحته.
   -------------------------------------------------------------------------
   وكانت معرَّفة داخل ‎table.data‎ وحدها. فحيثما استُعملت خارج جدول — في طابور
   المراجعة وفي ترويسة العارض — بقيت ‎.sub‎ عنصراً سطرياً، و‎.truncate‎ على
   عنصر سطريّ لا تقصّ شيئاً: الصندوق السطريّ لا يُقتطع ولا يقبل ‎text-overflow‎.
   فكان عنوانٌ طويل يمتدّ خارج عموده ويمرّ فوق ما بجانبه.

   The two-line cell: a heading with a small line beneath it.

   It was defined inside table.data alone. So wherever it was used outside a
   table — in the review queue and in the viewer's header — .sub stayed an inline
   element, and .truncate on an inline element clips nothing: an inline box is
   not clipped and does not take text-overflow. A long title ran out of its
   column and over whatever sat beside it.

   و‎min-width: 0‎ معه: العنصر المرن أو الشبكي لا ينكمش تحت مقاس محتواه ما لم
   يُقَل له ذلك، فيدفع عموده ويتجاوزه مهما قُصّ ما بداخله.

   And min-width: 0 with it: a flex or grid item does not shrink below its
   content's size unless told to, so it pushes its column wider and spills past
   it however well what is inside it is clipped. */
.primary-cell { min-width: 0; font-weight: 600; }
.primary-cell .sub {
  display: block;
  font-size: 11px;
  font-weight: 400;
  color: var(--fg-3);
}

/* ================================================================ Shell */
.app {
  display: flex;
  height: 100dvh;
  width: 100%;
  overflow: hidden;
}

/* ------------------------------------------------------------- Sidebar */
.sidebar {
  width: var(--sidebar-w);
  flex: none;
  display: flex;
  flex-direction: column;

  /* الشريط ورقةٌ خالصة لا أرضاً.
     -------------------------------------------------------------------------
     كان يأخذ ‎--bg‎ نفسها التي تحت المحتوى، فيُقرأ في النهار امتداداً رماديّاً
     للأرض لا يفصله عن اللوح إلا خطُّ الحدّ. وهو ليس أرضاً يُوضع عليها شيء، بل
     لوحُ تنقّلٍ قائمٌ بذاته — فحقُّه الورقة كالبطاقة، ترتفع عمّا جاورها.

     و‎rgb(var(--paper))‎ لا ‎var(--surface)‎: الورقة تنقلب مع السمت بنفسها،
     فتصير بياضاً خالصاً في النهار وسواداً خالصاً في الليل — وهو ما كان عليه
     الشريط ليلاً أصلاً. فقاعدةٌ واحدة تكفي، ولا حاجة إلى نقضٍ في ‎data-theme‎.

     The sidebar is paper, not ground.

     It took the same --bg that lies under the content, so by day it read as a
     grey continuation of the ground with only the hairline border telling the
     two apart. But it is not ground with things set on it — it is a navigation
     panel in its own right, and paper is its due, like a card lifting off what
     surrounds it.

     rgb(var(--paper)) rather than var(--surface): paper inverts with the theme
     on its own, giving pure white by day and pure black by night — which is
     what the sidebar already was at night. One declaration covers both themes,
     with no counter-rule needed under [data-theme="dark"]. */
  background: rgb(var(--paper));
  border-inline-end: 1px solid var(--line);
  transition: width var(--dur) var(--ease), transform var(--dur) var(--ease);
  z-index: 60;
  min-width: 0;
}

.brand {
  height: var(--topbar-h);
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding-inline: var(--sp-4);
  border-block-end: 1px solid var(--line);
}

/* زر إغلاق الدرج: مخفيٌّ على المكتب، إذ لا شيء يُغلق هناك — الشريط عمود.
   و‎margin-inline-start: auto‎ لا ‎justify-content: space-between‎ على الأب:
   الأول يدفع الزر وحده إلى الطرف فلا يتغيّر شيء إن غاب، والثاني يعيد توزيع
   الصفّ كلّه فيقفز الشعار حين لا يكون معه زرّ.
   The drawer's close button: hidden on the desktop, where nothing closes — the
   sidebar is a column. margin-inline-start: auto rather than space-between on
   the parent: the first pushes only this button to the edge, so nothing moves
   when it is absent; the second re-spreads the whole row, so the logo jumps
   whenever the button is not there.

   و‎.brand .brand-close‎ لا ‎.brand-close‎ وحدها: ‎.icon-btn‎ تُعلن ‎display: grid‎
   وهي أنزل في الملف، فتغلب المتساويةَ معها في الرجحان. فدرجةٌ واحدة تُقدَّم
   بها القاعدة على ترتيب الملف، بدل نقل القاعدة بعيداً عن بابها أو ‎!important‎.
   .brand .brand-close rather than .brand-close alone: .icon-btn declares
   display: grid further down the file, and would beat an equally weighted
   selector on order. One step of weight settles it in place, instead of exiling
   the rule from its own section or reaching for !important. */
.brand .brand-close {
  display: none;
  margin-inline-start: auto;
}

/* الشعار على الشفافية، وظلٌّ يرفعه عن السطح.

   والظلّ هنا drop-shadow لا box-shadow، والفرق ليس ذوقاً: box-shadow يظلّل
   صندوق العنصر، فيرسم مستطيلاً حول صورةٍ شفّافة — أي أنه يعيد اللوح الأبيض
   الذي أُزيل، مغمّقاً. أما drop-shadow فمرشِّحٌ يقرأ قناة الشفافية نفسها،
   فيتبع حرف الـZ وسقفَ البيت وحدهما.

   وظلّان لا واحد: الأول قريبٌ ضيّق يرسم الحافّة، والثاني أبعد وأوسع يعطي
   الارتفاع. وهذه هي فائدة الظلّ المضاعف — حافّة بلا ضباب، وعمقٌ بلا خطّ.

   The logo on transparency, with a shadow to lift it off the surface.

   The shadow is drop-shadow rather than box-shadow, and the difference is not
   taste: box-shadow shadows the element's box, drawing a rectangle around a
   transparent image — putting back the white plate that was removed, only
   darker. drop-shadow is a filter that reads the alpha channel itself, so it
   follows the Z's stroke and the little house's roof and nothing else.

   Two shadows rather than one: a tight near one that draws the edge, and a
   wider far one that gives the height. That is what stacking buys — an edge
   without haze, and depth without an outline. */
.brand-logo {
  flex: none;
  display: block;
  width: auto;
  height: 38px;
  object-fit: contain;
  filter: drop-shadow(0 1px 1px rgba(60, 42, 8, 0.28))
          drop-shadow(0 3px 6px rgba(60, 42, 8, 0.22));
}

/* على الداكن ينقلب مصدر الفصل: الظلّ الأسود يذوب في سوادٍ مثله، فيحلّ محلّه
   وهجٌ ذهبيّ خافت يفصل الحرف عن أرضيّته من فوقه لا من تحته.

   On dark the source of separation inverts: a black shadow dissolves into black
   like itself, so a faint gold glow takes its place — separating the letter
   from its ground from above rather than from below. */
[data-theme="dark"] .brand-logo {
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55))
          drop-shadow(0 0 10px rgba(226, 178, 74, 0.28));
}

.nav {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--sp-3) var(--sp-3) var(--sp-5);
}

.nav-group + .nav-group { margin-block-start: var(--sp-4); }

.nav-group-title {
  display: block;
  padding: var(--sp-2) var(--sp-3) var(--sp-1);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--fg-3);
  white-space: nowrap;
}

.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: 8px var(--sp-3);
  margin-block-end: 2px;
  border: 0;
  background: transparent;
  border-radius: var(--r-md);
  cursor: pointer;
  color: var(--fg-2);
  font-size: 13.5px;
  font-weight: 500;
  text-align: start;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.nav-item:hover { background: var(--surface-2); color: var(--fg-1); }
.nav-item .ico { opacity: 0.85; }

.nav-item[aria-current="page"] {
  background: var(--surface-inv);
  color: var(--on-inv);
  font-weight: 600;
}
.nav-item[aria-current="page"] .ico { opacity: 1; }

.nav-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.nav-count {
  flex: none;
  min-width: 20px;
  padding: 1px 6px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  font-size: 10.5px;
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.nav-item[aria-current="page"] .nav-count { border-color: rgb(var(--paper) / 0.5); }

.sidebar-foot {
  flex: none;
  padding: var(--sp-3);
  border-block-start: 1px solid var(--line);
}

.env-card {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  font-size: 11.5px;
  color: var(--fg-2);
  overflow: hidden;
}
.env-card strong { display: block; font-size: 12px; color: var(--fg-1); white-space: nowrap; }
.env-card span { white-space: nowrap; }

/* Collapsed rail (desktop) */
.app[data-rail="1"] .sidebar { width: var(--sidebar-w-rail); }
.app[data-rail="1"] .brand { justify-content: center; padding-inline: 0; }
.app[data-rail="1"] .nav-label,
.app[data-rail="1"] .nav-count,
.app[data-rail="1"] .nav-group-title,
.app[data-rail="1"] .env-card > div { display: none; }
.app[data-rail="1"] .nav-item { justify-content: center; padding-inline: 0; }
.app[data-rail="1"] .env-card { justify-content: center; padding: var(--sp-2); }
.app[data-rail="1"] .nav-group + .nav-group {
  margin-block-start: var(--sp-3);
  padding-block-start: var(--sp-3);
  border-block-start: 1px solid var(--line-soft);
}

/* Rail tooltips */
.app[data-rail="1"] .nav-item::after {
  content: attr(data-tip);
  position: absolute;
  inset-inline-start: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%) scale(0.96);
  padding: 5px 10px;
  background: var(--surface-inv);
  color: var(--on-inv);
  border-radius: var(--r-sm);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.14s var(--ease), transform 0.14s var(--ease);
  z-index: 70;
}
.app[data-rail="1"] .nav-item:hover::after,
.app[data-rail="1"] .nav-item:focus-visible::after { opacity: 1; transform: translateY(-50%) scale(1); }

.scrim {
  position: fixed;
  inset: 0;
  background: rgb(var(--ink) / 0.5);
  backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur) var(--ease), visibility var(--dur);
  z-index: 55;
}

/* -------------------------------------------------------------- Topbar */
.main-col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.topbar {
  height: var(--topbar-h);
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding-inline: var(--sp-4);
  border-block-end: 1px solid var(--line);

  /* والشريط العلويّ من الورقة نفسها، بحكم الشريط الجانبي وعلّته: هو إطارُ
     اللوح لا أرضَه. فيصير الإطار كلُّه — جانبيّاً وعلويّاً — سطحاً واحداً
     متّصلاً، وتبقى الأرض الرماديّة للمحتوى وحده تحته.
     The topbar is the same paper, by the sidebar's rule and for its reason: it
     is the console's frame, not its ground. The frame — side and top — becomes
     one continuous surface, and the grey ground is left to the content alone. */
  background: rgb(var(--paper));
}

.icon-btn {
  width: 36px;
  height: 36px;
  flex: none;
  display: grid;
  place-items: center;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--fg-2);
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}
.icon-btn:hover { background: var(--surface-2); color: var(--fg-1); }
.icon-btn.bordered { border-color: var(--line); }
.icon-btn[aria-pressed="true"] { background: var(--surface-inv); color: var(--on-inv); }

.topbar-title { min-width: 0; flex: 1; }
.topbar-title h1 {
  font-size: 15.5px;
  font-weight: 650;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.crumbs {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--fg-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.crumbs .ico { opacity: 0.55; }

.topbar-search {
  position: relative;
  flex: 0 1 320px;
  min-width: 0;
}
.topbar-search .ico {
  position: absolute;
  inset-inline-start: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--fg-3);
  pointer-events: none;
}
.topbar-search input {
  width: 100%;
  height: 36px;
  padding-inline: 34px 46px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-1);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.topbar-search input::placeholder { color: var(--fg-3); }
.topbar-search input:focus {
  outline: none;
  border-color: var(--fg-1);
  background: var(--surface);
}
.kbd {
  position: absolute;
  inset-inline-end: 8px;
  top: 50%;
  transform: translateY(-50%);
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: 4px;
  font-family: var(--ff-mono);
  font-size: 10px;
  color: var(--fg-3);
  pointer-events: none;
}

.topbar-actions { display: flex; align-items: center; gap: 2px; }

.lang-switch {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  padding-inline: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: transparent;
  cursor: pointer;
  font-size: 12px;
  font-weight: 650;
  letter-spacing: 0.02em;
  transition: background var(--dur) var(--ease);
}
.lang-switch:hover { background: var(--surface-2); }

.dot-badge { position: relative; }
.dot-badge::after {
  content: "";
  position: absolute;
  inset-block-start: 7px;
  inset-inline-end: 8px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--fg-1);
  border: 1.5px solid var(--bg);
}

.avatar-btn {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  height: 38px;
  padding-inline: 4px 10px;
  margin-inline-start: var(--sp-2);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: transparent;
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}
.avatar-btn:hover { background: var(--surface-2); }

.avatar {
  width: 28px;
  height: 28px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--surface-inv);
  color: var(--on-inv);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0;
}
.avatar.lg { width: 40px; height: 40px; font-size: 14px; }
.avatar.outline { background: transparent; color: var(--fg-1); border: 1px solid var(--line-strong); }

.avatar-meta { text-align: start; line-height: 1.2; }
.avatar-meta b { display: block; font-size: 12px; font-weight: 650; white-space: nowrap; }
.avatar-meta span { font-size: 10.5px; color: var(--fg-3); white-space: nowrap; }

/* ------------------------------------------------------------- Content */
.content {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--sp-5);
  scroll-behavior: smooth;
}

/* أثناء جلب تصفية: ما هو معروض يبهت قليلاً حتى يحلّ الجديد محلّه، فيُعلم أن
   شيئاً يجري. وهو ما كان يقوله وميضُ التحميل الكامل، وقد ذهب.

   While a filter is being fetched, what stands is dimmed a little until the new
   content lands, so that something is visibly under way. It says what the full
   load's flash used to say, now that the flash is gone. */
.content[aria-busy="true"] { opacity: .62; transition: opacity 120ms linear; }

.page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin-block-end: var(--sp-5);
}
.page-head h2 { font-size: 22px; letter-spacing: var(--tr-lg); }
.page-head p { margin-block-start: 4px; color: var(--fg-3); font-size: 13px; max-width: 68ch; }
.page-head-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }

/* ------------------------------------------------------------- Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 36px;
  padding-inline: 14px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: transparent;
  color: var(--fg-1);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
    border-color var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.btn:hover { background: var(--surface-2); }
.btn:active { transform: translateY(0.5px); }

.btn.primary {
  background: var(--surface-inv);
  color: var(--on-inv);
  border-color: var(--surface-inv);
}
.btn.primary:hover { opacity: 0.86; }

.btn.ghost { border-color: transparent; color: var(--fg-2); }
.btn.ghost:hover { color: var(--fg-1); background: var(--surface-2); }

.btn.sm { height: 30px; padding-inline: 10px; font-size: 12px; }
.btn.xs { height: 26px; padding-inline: 8px; font-size: 11.5px; gap: 5px; }
.btn.block { width: 100%; }
.btn[disabled] { opacity: 0.4; cursor: not-allowed; }

.btn-group {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
}
.btn-group button,
.btn-group a {
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding-inline: 12px;
  border: 0;
  background: transparent;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--fg-3);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn-group > * + * { border-inline-start: 1px solid var(--line); }
.btn-group button:hover,
.btn-group a:hover { color: var(--fg-1); background: var(--surface-2); }
.btn-group [aria-pressed="true"] { background: var(--surface-inv); color: var(--on-inv); }

/* --------------------------------------------------------------- Cards */
.grid { display: grid; gap: var(--sp-4); }
.grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid.cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.grid.auto { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.grid.auto-lg { grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); }
.span-2 { grid-column: span 2; }
.span-3 { grid-column: span 3; }
.stack { display: grid; gap: var(--sp-4); }

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
  min-width: 0;
  overflow: hidden;
}
.card.flat { box-shadow: none; }
.card.pad { padding: var(--sp-5); }

.card-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-block-end: 1px solid var(--line);
}
.card-head .t { flex: 1; min-width: 0; }
.card-head h3 { font-size: 14px; font-weight: 650; }
.card-head p { font-size: 11.5px; color: var(--fg-3); margin-block-start: 2px; }
.card-head-icon {
  width: 30px; height: 30px; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--fg-2);
}
.card-body { padding: var(--sp-5); }
.card-body.tight { padding: var(--sp-3) var(--sp-4); }
.card-foot {
  padding: var(--sp-3) var(--sp-5);
  border-block-start: 1px solid var(--line);
  background: var(--surface-1);
  font-size: 12px;
  color: var(--fg-3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

/* ----------------------------------------------------------- Stat tile */
.stat {
  position: relative;
  padding: var(--sp-4) var(--sp-5);
  display: grid;
  gap: 10px;
}
/* Grid children default to min-width:auto, which would refuse to shrink below
   their min-content width and burst the tile on narrow tracks. */
.stat > * { min-width: 0; }
.stat-top { display: flex; align-items: center; gap: var(--sp-2); color: var(--fg-3); }
.stat-top .ico { color: var(--fg-2); }
.stat-label {
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em;
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* الرقم الكبير على سطرٍ 1.05: هو الوحيد في الورقتين الذي يقلّ عن حبره عمداً،
   لأن الأرقام لا نزول لها فيبدو السطر الفسيح تحتها فراغاً لا هواء. غير أن
   بعض هذه البطاقات لا تحمل رقماً بل كلمة — "دبي" في أعلى المدن — وللكلمة
   العربية ذيلٌ ينزل 0.40em، فكان يُقصّ عند الحافّة.

   ولا يُعالَج هذا برفع السطر: رفعُه يفكّ الرقم عن عنوانه في ثلاثين بطاقة من
   أجل بطاقتين تحملان كلمة. وإنما يُوسَّع صندوقُ القصّ وحده — حشوةٌ أسفله
   وهامشٌ سالب يلغيها من التخطيط — فيبقى مقاس البطاقة كما هو إلى البكسل،
   ويتّسع القصّ للذيل. والسطر الواحد لا شيء تحته يتسرّب، فالتوسيع آمن هنا
   وحده دون صناديق ‎line-clamp‎ التي يليها سطرٌ مخفيّ.

   The big figure on a 1.05 line: the one place in these two sheets that is
   deliberately shorter than its own ink, because figures have no descender and
   a roomy line under them reads as a gap rather than as air. But some of these
   cards carry a word instead of a number — "دبي" at the top of the cities —
   and an Arabic word has a tail dropping 0.40em, which was cut at the edge.

   Raising the line is not the answer: it would loosen the figure from its label
   on thirty cards for the sake of the two that hold a word. The clipping box
   alone is widened instead — padding below and a negative margin that removes
   it from the layout again — so the card measures the same to the pixel while
   the clip has room for the tail. A single line has nothing beneath it to leak,
   which is why this is safe here and not in the line-clamp boxes, where a
   hidden line follows. */
.stat-value {
  font-size: clamp(21px, 1.55vw + 8px, 27px);
  font-weight: 700;
  letter-spacing: var(--tr-xl);
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-block-end: 0.12em;
  margin-block-end: -0.12em;
}
.stat-value small { font-size: 13px; font-weight: 600; color: var(--fg-3); margin-inline-start: 3px; }
.stat-bottom { display: flex; align-items: center; gap: var(--sp-3); justify-content: space-between; }
.stat-bottom > .delta { flex: none; }
.stat-note {
  font-size: 11.5px; color: var(--fg-3);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.delta {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 7px 1px 5px;
  border-radius: var(--r-pill);
  font-size: 11.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  border: 1px solid var(--line-strong);
}
.delta.up { background: var(--surface-inv); color: var(--on-inv); border-color: var(--surface-inv); }
.delta.down { background: transparent; color: var(--fg-1); border-style: dashed; }
.delta.flat { color: var(--fg-3); border-color: var(--line); }

/* --------------------------------------------------------------- Pills */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
  line-height: 1.7;
}
.pill .ico { margin-inline-start: -2px; }
.pill.solid { background: var(--surface-inv); color: var(--on-inv); border-color: var(--surface-inv); }
.pill.dashed { border-style: dashed; color: var(--fg-2); }
.pill.soft { border-color: var(--line); background: var(--surface-2); color: var(--fg-2); }
.pill.hatch {
  color: var(--fg-1);
  border-color: var(--fg-1);
  background-image: repeating-linear-gradient(
    45deg,
    rgb(var(--ink) / 0.16) 0 3px,
    transparent 3px 7px
  );
}
.pill.xs { font-size: 10.5px; padding: 1px 7px; }

.tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 7px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-size: 11px;
  color: var(--fg-2);
  background: var(--surface-1);
  white-space: nowrap;
}

/* -------------------------------------------------------------- Tables */
.table-wrap {
  width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

table.data { font-size: 13px; min-width: 560px; }
table.data thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface);
  padding: 10px var(--sp-4);
  text-align: start;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-3);
  border-block-end: 1px solid var(--line);
  white-space: nowrap;
}
table.data tbody td {
  padding: 11px var(--sp-4);
  border-block-end: 1px solid var(--line-soft);
  vertical-align: middle;
}
table.data tbody tr { transition: background 0.14s var(--ease); }
table.data tbody tr:hover { background: var(--surface-1); }
table.data tbody tr:last-child td { border-block-end: 0; }
table.data td.num, table.data th.num { text-align: end; font-variant-numeric: tabular-nums; }
table.data td.actions { text-align: end; white-space: nowrap; }
table.data .primary-cell { font-weight: 600; }
table.data .sub { display: block; font-size: 11px; color: var(--fg-3); font-weight: 400; }

.cell-id { font-family: var(--ff-mono); font-size: 11.5px; color: var(--fg-3); white-space: nowrap; }

.cell-user { display: flex; align-items: center; gap: 9px; min-width: 0; }
.cell-user .avatar { width: 26px; height: 26px; font-size: 10px; }

/* ------------------------------------------------------ Review decisions */
/* قرار المراجع داخل صفّ الجدول.
   -------------------------------------------------------------------------
   وكانت طاولة مراجعة الوثائق تطلب من هذا الملف صنفين لا وجود لهما: .decision
   و.decision-form. فكان القالب يرسم نموذجَين بحقلَي نصّ في خليّة واحدة بلا
   قاعدةٍ تحكمهما — والنموذج عنصرٌ كتليّ، وحقلُ .input عرضه ‎100%‎ بالقاعدة
   أعلاه، والخليّة .actions عليها ‎white-space: nowrap‎. فتمدّد الحقلان على
   عرضٍ لا سقف له، ودُفع الجدولُ خارج حاويته، وصارت الصفحة شريطاً أفقياً
   يُسحَب.

   A reviewer's decision inside a table row.

   The document review desk was asking this file for two classes that did not
   exist in it: .decision and .decision-form. So the template drew two forms
   with two text fields in a single cell with no rule governing them — and a
   form is a block element, an .input is 100% wide by the rule above, and the
   .actions cell carries white-space: nowrap. The two fields stretched to a
   width with no ceiling, the table was pushed out of its container, and the
   page became a horizontal strip to be dragged.

   والقاعدة هنا تعطيهما ما ينقصهما: عرضاً أدنى يتّسع للحقل والزرّ، وسطراً لكل
   قرار، وحقلاً يأخذ ما بقي من العرض بعد زرّه لا كلَّ العرض.

   The rule here gives them what they lacked: a floor wide enough for a field
   and its button, a line per decision, and a field taking what is left of the
   width after its button rather than all of it. */
.decision {
  display: grid;
  gap: 6px;
  min-width: 250px;
  white-space: normal;
  text-align: start;
}

/* والخليّة تحاذي إلى الطرف، فيُدفع الصندوق إليه ولا يسبح في وسطها.
   The cell aligns to the far edge, so the box is pushed to it rather than
   floating in the middle. */
table.data td.actions .decision { margin-inline-start: auto; }

/* سطر القرار: عمودان ثابتان — الحقل ثم الزرّ — سواء وُجد الحقل أم لم يوجد.
   -------------------------------------------------------------------------
   وكان صفّاً مرناً، فيتبع موضعُ الزرّ ما إلى جانبه: زرٌّ وحده يقف عند أول
   السطر، وزرٌّ بعد حقلٍ يأخذ ما بقي يُدفع إلى آخره. فكان «اعتماد» في وثيقةٍ
   لا مرجعَ لها يقف في جهة، وفي صكٍّ يطلب مرجع داري يقف في الجهة المقابلة —
   والوثيقتان في بطاقتين متجاورتين. فيقرأ الناظر تضارباً وهو ينظر إلى الزرّ
   نفسه.

   The decision line: two fixed columns — the field, then the button — whether the
   field is there or not.

   It was a flexible row, so the button's place followed whatever stood beside
   it: a button on its own sat at the line's start, and a button after a field
   taking what remained was pushed to its end. So "approve" on a document with no
   reference stood on one side, and on a deed asking for a Dari reference stood on
   the opposite one — the two documents in neighbouring cards. What the eye reads
   is a contradiction, while it is looking at the very same button.

   والعمود الفارغ يبقى فارغاً: ‎grid-column‎ صريح على كلٍّ منهما، فلا يقفز
   الزرّ إلى موضع الحقل حين لا يكون ثمّة حقل.

   The empty column stays empty: an explicit grid-column on each, so the button
   does not jump into the field's place when there is no field. */
.decision-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 6px;
}

.decision-form .input { grid-column: 1; min-width: 0; }
.decision-form .btn { grid-column: 2; }

/* وأزرار القرار بمقاسٍ واحد: «اعتماد» و«رفض» — ومعهما «عرض» في بطاقة الطابور
   — تقف في عمودٍ واحد، وثلاثةُ عروضٍ مختلفة في عمودٍ واحد تُقرأ حافّةً
   متعرّجة.
   The decision buttons share one measure: "approve" and "refuse" — and "show"
   with them on a queue card — stand in one column, and three different widths in
   one column read as a ragged edge. */
.decision .btn,
.review-item-head .btn { min-width: 84px; }

/* والمقاس الصغير كان ناقصاً من نظام الحقول: .btn له xs و.input ليس له، فكان
   حقلٌ ارتفاعه ستةٌ وثلاثون يقف إلى جانب زرٍّ ارتفاعه ستةٌ وعشرون.
   The extra-small size was missing from the field system: .btn had an xs and
   .input had none, so a field thirty-six high stood beside a button
   twenty-six high. */
.input.sm, select.input.sm { height: 30px; font-size: 12px; padding-inline: 9px; }
.input.xs, select.input.xs { height: 26px; font-size: 11.5px; padding-inline: 8px; }

/* طاولة القرار: ما يُقرَّر فيه إلى جانب ما يُنظر إليه.
   -------------------------------------------------------------------------
   والعمودان لا سطران: القرار في وثيقةٍ يُتَّخذ بالنظر إليها، ووضعُها تحت
   القائمة يعني أن يُمرَّر إليها ثم يُمرَّر عائداً إلى الزرّ — أو أن تُفتح في
   لسانٍ آخر فيُفقد الصفّ الذي كان يُقرأ.

   The decision desk: what is being decided beside what is being looked at.

   Two columns rather than two rows: a decision about a document is taken while
   looking at it, and putting it below the list means scrolling to it and then
   scrolling back to the button — or opening it in another tab and losing the row
   that was being read.

   وتحت ‎1100px‎ يعودان سطرين: عمودان عرض كلٍّ منهما ثلاثمئة بكسل لا يُقرأ فيهما
   صكّ.
   Below 1100px they become two rows: two columns three hundred pixels wide are
   not somewhere a title deed can be read. */
/* ملفّ المعلن: عمودان على مستوى الصفحة.
   -------------------------------------------------------------------------
   ما يُقرأ في الأول — الأرقام، وما ينتظر قراراً، والحساب، والوثائق، والإعلانات
   — والوثيقة المعروضة في الثاني، ملتصقةً بأعلى الشاشة ما دام يُمرَّر بجانبها.

   وكان العارض داخل قسمٍ واحد، فما اختير من جدول الوثائق في أسفل الصفحة يتغيّر
   في عارضٍ في أعلاها لا يراه من اختاره.

   The advertiser's file: two columns at the page's level.

   What is read in the first — the figures, what waits on a decision, the
   account, the documents, the listings — and the document on show in the second,
   stuck to the top of the screen for as long as the first is being scrolled.

   The viewer used to sit inside one section, so whatever was picked from the
   documents table at the foot of the page changed in a viewer at its head that
   whoever picked it could not see.

   و‎align-items: start‎ هو ما يجعل اللصوق يعمل: عنصر الشبكة يتمدّد إلى ارتفاع
   الصفّ افتراضاً، واللاصق لا يلصق إلا إذا كان أقصر من حاويته.

   align-items: start is what makes the sticking work: a grid item stretches to
   the row's height by default, and a sticky box only sticks while it is shorter
   than its container. */
.dossier {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(340px, 0.75fr);
  gap: var(--sp-4);
  align-items: start;
}

/* والعمود الأول حاويةُ استعلام: ما بداخله يقيس نفسه به لا بالشاشة.
   -------------------------------------------------------------------------
   ونقاط الانكسار في هذا الملفّ كلها على عرض النافذة — وهو الصواب حين تكون
   الصفحة عموداً واحداً. فلمّا صارت عمودين ضاق ما في الأول إلى نصفٍ ونيّف، ولم
   تتحرّك نقطةٌ واحدة: أربع بطاقاتٍ في عمودٍ عرضه سبعمئة تُرسم أربعاً، وجدولٌ
   بستّة أعمدة يُقطع ويُسحب أفقيّاً — والشاشة عريضة، فلا شيء في القواعد يعلم أن
   ثمّة ضيقاً.

   The first column is a query container: what is inside it measures itself
   against it rather than against the screen.

   Every breakpoint in this file is on the window's width — which is right while
   a page is one column. Once it became two, what sat in the first narrowed to a
   little over half and not one breakpoint moved: four cards drawn as four in a
   column seven hundred wide, and a table of six columns cut off and dragged
   sideways — the screen being wide, so nothing in the rules knew there was any
   narrowness. */
.dossier-main {
  min-width: 0;
  container-type: inline-size;
  container-name: dossier;
}

@container dossier (max-width: 880px) {
  .grid.cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid.cols-3, .grid.cols-2 { grid-template-columns: minmax(0, 1fr); }
}

@container dossier (max-width: 520px) {
  .grid.cols-4 { grid-template-columns: minmax(0, 1fr); }
}

/* وجداول الملفّ تتّسع لعمودها ولا تُسحب: ‎min-width‎ المكتوب على كل جدول في
   اللوحة يفترض عرض الصفحة كلها، وهذا العمود أضيق منها.

   The file's tables fit their column rather than being dragged: the min-width
   written on every table in the console assumes the whole page's width, and this
   column is narrower than that.

   والخليّة الأولى تلتفّ: عنوانٌ طويل يُقرأ في سطرين خيرٌ من جدولٍ يخرج من
   حاويته.

   The first cell wraps: a long title read over two lines is better than a table
   leaving its container. */
.dossier table.data { min-width: 0; }
.dossier table.data td { vertical-align: top; }
.dossier table.data .primary-cell { white-space: normal; overflow-wrap: anywhere; }
.dossier table.data th { white-space: normal; }

.dossier-side {
  position: sticky;
  top: var(--sp-4);
  min-width: 0;
}

/* وتحت هذا العرض يصير عموداً واحداً، والوثيقة أوّله: من فتح ملفّاً على وثيقةٍ
   بعينها يريد أن يراها قبل أن يقرأ عنها.
   Below this width it becomes one column with the document first: whoever opened
   a file on a particular document wants to see it before reading about it. */
@media (max-width: 1200px) {
  .dossier { grid-template-columns: minmax(0, 1fr); }
  .dossier-side { position: static; order: -1; }
}

.review-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  align-items: start;
}

/* والعمود يُصرَّح بمقاسه: عنصر الشبكة مقاسه الأدنى مقاسُ محتواه، فبطاقةٌ فيها
   سطرٌ طويل تتّسع بمحتواها وتخرج من عمودها — والقصّ داخلها لا يمنع ذلك، لأن
   الاتّساع وقع قبل القصّ.
   The column is told its size: a grid item's minimum size is its content's, so a
   card holding a long line widens with it and leaves its column — and clipping
   inside it does not prevent that, because the widening happened first. */
.review-queue {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-3);
  align-content: start;
}

.review-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-width: 0;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-1);
}

/* والمعروضة منها تُعلَّم بحدٍّ لا بلون: النظام بلا ألوان، والحدّ الغامق على
   طرفها يقول «هذه التي في العارض» بلا أن يُدخل هوىً ثانياً.
   The one on show is marked by a rule rather than a colour: the system has no
   colours, and a heavy edge on its side says "this is the one in the viewer"
   without introducing a second hue. */
.review-item.is-open {
  border-inline-start: 3px solid var(--fg-1);
  background: var(--surface-2);
}

.review-item-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
  min-width: 0;
}

/* والزرّ لا ينكمش: هو المقاس الثابت في السطر، والعنوان هو ما يُقصّ.
   The button does not shrink: it is the fixed measure on the line, and the
   title is what gets clipped. */
.review-item-head > .btn { flex: none; }

.review-item-head .primary-cell { font-size: 13px; min-width: 0; }
.review-item .decision { min-width: 0; }

/* العارض: إطارٌ ثابت الارتفاع يلتصق بأعلى الشاشة حين يُمرَّر ما إلى جانبه.
   والالتصاق هو الغرض كله: قائمة الانتظار قد تطول، والوثيقة يجب أن تبقى
   منظورة ما دام يُقرَّر فيها.
   The viewer: a fixed-height frame that sticks to the top of the screen while
   what is beside it scrolls. The sticking is the whole point: the waiting list
   may run long, and the document must stay in sight for as long as it is being
   decided. */
/* والعارض لم يعد لاصقاً بنفسه: عمود الصفحة هو اللاصق، وعنصران لاصقان أحدهما
   داخل الآخر يتنازعان الموضع.
   The viewer is no longer sticky itself: the page's column is what sticks, and
   two sticky boxes one inside the other fight over the position. */
.doc-viewer {
  display: grid;
  grid-template-rows: auto 1fr;
  height: calc(100vh - var(--topbar-h) - var(--sp-6));
  min-height: 420px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-1);
  overflow: hidden;
}

.doc-viewer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-block-end: 1px solid var(--line);
  background: var(--surface);
  min-width: 0;
}

.doc-viewer-head .primary-cell { font-size: 12.5px; min-width: 0; }

/* والإطار بخلفية الورقة لا بخلفية اللوح: المستند أبيضٌ في كل الأحوال، وإطارٌ
   رماديّ حوله في السمت الليلي يجعل حافّته تُقرأ.
   The frame keeps the paper's ground rather than the console's: a document is
   white whatever the theme, and a grey surround in the dark theme is what makes
   its edge readable. */
.doc-viewer-frame {
  width: 100%;
  height: 100%;
  min-height: 0;
  border: 0;
  display: block;
  background: var(--surface-2);
}

.doc-viewer-empty {
  display: grid;
  place-items: center;
  align-content: center;
  gap: var(--sp-2);
  padding: var(--sp-6) var(--sp-4);
  text-align: center;
  color: var(--fg-3);
}

.doc-viewer-empty b { color: var(--fg-1); font-size: 13.5px; }
.doc-viewer-empty p { font-size: 12px; max-width: 40ch; }

/* ------------------------------------------------------- Property types */
/* نموذج إضافة نوع: شبكةٌ تتكسّر بنفسها.
   -------------------------------------------------------------------------
   وسبعة حقول في سطرٍ واحد على شاشةٍ عريضة، وفي سطرين على شاشةٍ أضيق، بلا نقطة
   انكسارٍ مكتوبة: auto-fit مع minmax يقرّر عدد الأعمدة من العرض المتاح، فلا
   يُضبط الرقم لشاشةٍ بعينها ثم يُنسى حين يُضاف حقلٌ ثامن.

   The form that adds a type: a grid that breaks by itself.

   Seven fields on one line on a wide screen and on two on a narrower one, with
   no breakpoint written: auto-fit with minmax decides the number of columns from
   the width available, so no number is tuned to one screen and then forgotten
   when an eighth field arrives. */
.type-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--sp-3);
  align-items: end;
}

.type-form-submit {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}

/* وعلامة التفعيل مربّعٌ وكلمة، والكلمة تُضغط كما يُضغط المربّع.
   The active mark is a box and a word, and the word is pressed as the box is. */
.type-active {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  color: var(--fg-2);
  cursor: pointer;
  user-select: none;
}

.type-active input { width: 15px; height: 15px; accent-color: var(--fg-1); cursor: pointer; }

/* وزرّا الصفّ متجاوران: النموذجان عنصران كتليّان، ولولا هذا لوقف أحدهما تحت
   الآخر وارتفع الصفّ إلى ضعفه.
   The row's two buttons sit side by side: the two forms are block elements, and
   without this one would stand beneath the other and double the row's height. */
.type-row-form { display: inline-block; margin-inline-start: 4px; }

/* حقل الأيقونة: زرٌّ يعرض ما اختير ويفتح المنتقي.
   -------------------------------------------------------------------------
   وكان حقل نصٍّ يُكتب فيه اسم الأيقونة. والزرّ يعرض الرسم والاسم معاً: الرسم
   ليعرف ما اختار، والاسم لأنه ما يُحفظ ويُقرأ في مكانٍ آخر.

   The icon field: a button showing what was picked and opening the picker.

   It was a text input the icon's name got typed into. The button shows the
   drawing and the name together: the drawing so they know what they picked, the
   name because it is what gets saved and read elsewhere. */
.icon-field { display: flex; align-items: center; min-width: 0; }

.icon-pick {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-width: 0;
  height: 26px;
  padding-inline: 7px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--fg-1);
  font-size: 11.5px;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}

.icon-pick:hover { border-color: var(--line-strong); background: var(--surface-1); }
.icon-pick:focus-visible { outline: 2px solid var(--fg-1); outline-offset: 1px; }
.icon-pick.sm { height: 30px; font-size: 12px; padding-inline: 9px; }

.icon-pick-glyph { display: inline-grid; place-items: center; flex: none; }

/* والاسم يُقصّ ولا يمدّ الزرّ: هو الجزء المتغيّر في سطرٍ عرضه محدود.
   The name is clipped rather than stretching the button: it is the varying part
   on a line of fixed width. */
.icon-pick-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: start;
  font-family: var(--ff-mono);
  direction: ltr;
  color: var(--fg-2);
}

.icon-pick .ico:last-child { flex: none; color: var(--fg-3); }

/* نافذة المنتقي: بحثٌ فوق شبكةٍ تُمرَّر.
   The picker's modal: a search above a grid that scrolls. */
.icon-modal .modal-body { display: grid; gap: var(--sp-3); align-content: start; }
.icon-modal .search-inline { flex: none; width: 100%; }

.icon-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
  gap: var(--sp-2);
  max-height: 48dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-inline-end: 2px;
}

.icon-tile {
  display: grid;
  place-items: center;
  gap: 5px;
  padding: var(--sp-3) 6px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--fg-1);
  cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.icon-tile:hover { background: var(--surface-2); border-color: var(--line-strong); }

/* والمختارة معلَّمة بالحبر لا بلون: النظام بلا ألوان.
   The chosen one is marked in ink rather than in colour: the system has none. */
.icon-tile.is-chosen {
  background: var(--surface-inv);
  border-color: var(--surface-inv);
  color: var(--on-inv);
}

.icon-tile.is-chosen span { color: var(--on-inv); }

.icon-tile span {
  font-family: var(--ff-mono);
  font-size: 9.5px;
  color: var(--fg-3);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  direction: ltr;
}
/* وأيقونة النوع إلى جانب قائمتها، فيُرى ما اختير لا اسمه فقط.
   The type's icon beside its list, so what was picked is seen rather than only
   named. */
.type-icon {
  display: inline-grid;
  place-items: center;
  width: 20px;
  color: var(--fg-2);
  vertical-align: middle;
}

/* وحقول الجدول لا تحمل هوامش الحقول العادية: الخليّة هي هامشها.
   The table's fields carry none of an ordinary field's spacing: the cell is
   their spacing. */
table.data td .input.xs { width: 100%; }

/* وثيقةٌ مرفوعة كما تُقرأ في الصفّ: اسمها وحجمها، ورابطها يفتحها.
   An uploaded document as it reads in the row: its name, its size, and a link
   that opens it. */
/* وعمودُ الخليّة يُعلَن مقصوصاً، ولا يُترك ضمنيّاً.
   -------------------------------------------------------------------------
   الشبكة بلا ‎grid-template-columns‎ تصنع عموداً ضمنيّاً مقاسه ‎auto‎، وأرضيّة
   ‎auto‎ هي ‎min-content‎ — وهي لنصٍّ لا يلتفّ عرضُ النصّ كلِّه. فاسم ملفٍّ من
   مئة حرف يجعل أرضيّة العمود ستّمئة بكسل، فيفيض على عموده في صفّ الورقة ويقع
   فوق ما بجانبه: الشارة تعلو الاسم، والاسم يعلو من قرّر.
   و‎min-width: 0‎ على الخليّة لا يكفي: هو يسمح لصندوقها أن ينكمش، والعمود
   الذي بداخله يبقى بمقاس أطول اسم فيه.
   والقصّ بالنقاط لا يُصلح ذلك وحده: ‎text-overflow‎ يرسم النقاط ولا يغيّر
   ‎min-content‎ الذي يُقاس به العمود.

   The cell's column is declared cut, rather than left implicit.

   A grid with no grid-template-columns makes one implicit column sized auto,
   and auto's floor is min-content — which for text that does not wrap is the
   width of the whole text. So a hundred-character file name gives the column a
   floor of six hundred pixels, and it spills out of its column in the paper's
   row and lands on top of what sits beside it: the badge over the name, the
   name over whoever decided.
   min-width: 0 on the cell does not do it: that lets the cell's box shrink,
   while the column inside it stays as wide as the longest name in it.
   Nor does the ellipsis alone: text-overflow draws the dots and does not change
   the min-content the column is measured by. */
.doc-cell {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 3px;
  min-width: 0;
}

.doc-cell .doc-file {
  display: flex; align-items: center; gap: 5px;
  font-size: 11px; color: var(--fg-3);
  max-width: 220px;
  min-width: 0;
}
.doc-cell .doc-file:hover { color: var(--fg-1); }
.doc-cell .doc-file .name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0;
}

/* والشارة تُقصّ هي الأخرى: نوع الوثيقة قد يطول، و‎nowrap‎ عليها يفيض بها كما
   يفيض بالاسم — والصفّ الذي أُصلح من جهةٍ ينكسر من الأخرى.
   The badge is clipped too: a document's type may run long, and the nowrap on
   it spills exactly as the name does — a row mended on one side breaking on the
   other. */
.doc-cell > .tag {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  display: inline-flex;
  justify-self: start;
}

/* والحجم والأيقونة لا ينكمشان: الاسم وحده هو ما يُقصّ.
   -------------------------------------------------------------------------
   وكان الثلاثة عناصر مرنة في سطرٍ ضيّق، والاسم يحمل ‎overflow: hidden‎ فيقبل
   الانكماش إلى الصفر — فيتوقّف الانكماش عنده ويقع على «‎· 156 KB‎»، وهو نصّ
   يلتفّ. فيُقرأ الحجم في سطرين: «156 ·» فوق «KB».

   The size and the icon do not shrink: the name alone is what gets clipped.

   The three were flexible items on a narrow line, and the name carries
   overflow: hidden so it accepts shrinking to zero — where the shrinking stops
   and lands on "· 156 KB", which is text, and text wraps. So the size read over
   two lines: "156 ·" above "KB". */
.doc-cell .doc-file > .ico,
.doc-cell .doc-file .size { flex: none; white-space: nowrap; }

/* --------------------------------------------------------------- Lists */
.list { display: grid; }
.list > li {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5);
  border-block-end: 1px solid var(--line-soft);
}
.list > li:last-child { border-block-end: 0; }
.list > li:hover { background: var(--surface-1); }
.list-icon {
  width: 32px; height: 32px; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--fg-2);
}
.list-icon.inv { background: var(--surface-inv); color: var(--on-inv); border-color: var(--surface-inv); }
.list-main { flex: 1; min-width: 0; }
.list-title { font-size: 13px; font-weight: 600; }
.list-sub { font-size: 11.5px; color: var(--fg-3); margin-block-start: 1px; }
.list-end { flex: none; text-align: end; font-size: 11.5px; color: var(--fg-3); white-space: nowrap; }

.timeline { position: relative; padding-inline-start: var(--sp-5); }
.timeline::before {
  content: "";
  position: absolute;
  inset-block: 6px 6px;
  inset-inline-start: 5px;
  width: 1px;
  background: var(--line);
}
.timeline li { position: relative; padding-block-end: var(--sp-4); }
.timeline li:last-child { padding-block-end: 0; }
.timeline li::before {
  content: "";
  position: absolute;
  inset-inline-start: calc(-1 * var(--sp-5) + 2px);
  inset-block-start: 6px;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--surface);
  border: 1.5px solid var(--fg-1);
}
.timeline li.done::before { background: var(--fg-1); }
.timeline-time { font-size: 10.5px; color: var(--fg-3); font-family: var(--ff-mono); }
.timeline-title { font-size: 13px; font-weight: 600; }
.timeline-desc { font-size: 12px; color: var(--fg-3); }

/* ------------------------------------------------------------ Progress */
.bar {
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  overflow: hidden;
}
.bar > i {
  display: block;
  height: 100%;
  background: var(--fg-1);
  border-radius: var(--r-pill);
  transition: width 0.6s var(--ease);
}
.bar.hatched > i {
  background-image: repeating-linear-gradient(
    45deg,
    rgb(var(--paper) / 0.55) 0 2px,
    transparent 2px 5px
  );
}
.bar.thin { height: 4px; }
.bar.tall { height: 10px; }

.meter-row { display: grid; gap: 6px; }
.meter-row + .meter-row { margin-block-start: var(--sp-4); }
.meter-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
  font-size: 12.5px;
}
.meter-head b { font-weight: 600; }
.meter-head span { color: var(--fg-3); font-variant-numeric: tabular-nums; font-size: 12px; }

/* -------------------------------------------------------------- Charts */
.chart { width: 100%; display: block; overflow: visible; direction: ltr; }
.chart text { font-family: var(--ff-sans); fill: var(--fg-3); font-size: 10px; }

/* Pattern primitives live in a single hidden <svg>; styling them from CSS is
   what makes them follow the light/dark inversion. */
.pat-stroke { stroke: var(--fg-1); fill: none; }
.pat-fill { fill: var(--fg-1); }
.chart .axis line, .chart .axis path { stroke: var(--line); }
.chart .grid-line { stroke: var(--line-soft); stroke-dasharray: 3 4; }
.chart .series-fill { fill: var(--fg-1); }
.chart .series-line { stroke: var(--fg-1); fill: none; stroke-width: 1.75; }
.chart .series-line.dashed { stroke-dasharray: 5 4; }
.chart .bar-solid { fill: var(--fg-1); }
.chart .bar-outline { fill: none; stroke: var(--fg-1); stroke-width: 1.25; }
.chart .dot { fill: var(--surface); stroke: var(--fg-1); stroke-width: 1.75; }

.legend { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-4); margin-block-start: var(--sp-3); }
.legend-item { display: flex; align-items: center; gap: 7px; font-size: 11.5px; color: var(--fg-2); }
.legend-swatch { width: 13px; height: 13px; border-radius: 3px; border: 1px solid var(--fg-1); flex: none; }
.legend-swatch.solid { background: var(--fg-1); }
.legend-swatch.hatch {
  background-image: repeating-linear-gradient(45deg, var(--fg-1) 0 2px, transparent 2px 5px);
}
.legend-swatch.dots {
  background-image: radial-gradient(var(--fg-1) 1.1px, transparent 1.2px);
  background-size: 5px 5px;
}
.legend-swatch.empty { background: transparent; }

.sparkline { width: 100%; height: 34px; display: block; }

.heat-grid { display: grid; gap: 3px; }
.heat-cell {
  aspect-ratio: 1;
  border-radius: 3px;
  border: 1px solid var(--line-soft);
  background: var(--fg-1);
}

/* ---------------------------------------------------------- Status dot */
.sdot {
  width: 9px; height: 9px; flex: none;
  border-radius: 50%;
  border: 1.5px solid var(--fg-1);
  display: inline-block;
}
.sdot.full { background: var(--fg-1); }
.sdot.half {
  background: linear-gradient(90deg, var(--fg-1) 0 50%, transparent 50% 100%);
}
.sdot.empty { background: transparent; border-style: dashed; }
.sdot.pulse { animation: pulse 2s var(--ease) infinite; }

@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgb(var(--ink) / 0.35); }
  60% { box-shadow: 0 0 0 6px rgb(var(--ink) / 0); }
}

/* --------------------------------------------------------------- Forms */
.field { display: grid; gap: 6px; }
.field label { font-size: 12px; font-weight: 600; color: var(--fg-2); }
.field .hint { font-size: 11px; color: var(--fg-3); }

.input, select.input, textarea.input {
  width: 100%;
  height: 36px;
  padding-inline: 11px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  transition: border-color var(--dur) var(--ease);
}
textarea.input { height: auto; padding-block: 9px; resize: vertical; min-height: 82px; }
.input:focus { outline: none; border-color: var(--fg-1); }
select.input {
  appearance: none;
  padding-inline-end: 30px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 13px center, right 8px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}
[dir="rtl"] select.input {
  background-position: left 8px center, left 13px center;
  background-image: linear-gradient(-45deg, transparent 50%, currentColor 50%),
    linear-gradient(-135deg, currentColor 50%, transparent 50%);
}

/* والخيارات تُرسم في نافذة النظام: يُصرَّح لها لونٌ صلب وإلا ورثت أبيضه.
   The options are painted in the platform's window: an opaque colour is
   stated for them, otherwise they inherit its white. */
select.input option,
select.input optgroup { background-color: var(--surface); color: var(--fg-1); }

/* كشف كلمة المرور: زرٌّ داخل الحقل لا بجانبه، فلا يزحم الشبكة ولا يزيد ارتفاع
   الصف. والحقل يترك فراغاً في نهايته حتى لا تمرّ الكلمة تحت الزرّ.
   Revealing a password: a button inside the field rather than beside it, so it
   neither crowds the grid nor adds to the row's height. The field leaves an
   empty space at its end so the word does not run underneath it. */
.field-reveal { position: relative; display: grid; }
.field-reveal .input { padding-inline-end: 40px; }

/* ولا يظهر إلا حيث تعمل السكربتات، والصنف js يضعه الجذر قبل أول رسم — فالزرّ
   موجودٌ من اللحظة الأولى أو ليس موجوداً، ولا يقفز إلى الحقل بعد أن يستقرّ.
   وهذا ما تعنيه تلك العلامة في رأس المستند، لا أن يُخفى العنصر ثم يُكشف.

   It shows only where scripts run, and the js class is put on the root before
   first paint — so the button is either there from the first moment or not
   there at all, and never jumps into a field that had already settled. That is
   what the mark in the document's head is for, rather than hiding an element
   and then revealing it. */
.reveal { display: none; }

.js .reveal {
  position: absolute;
  inset-inline-end: 5px;
  inset-block-start: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  appearance: none;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--fg-3);
  cursor: pointer;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.js .reveal:hover { color: var(--fg-1); background: var(--surface-2); }

/* والحدّ يُزاح إلى الداخل: الزرّ ملتصقٌ بحافّة الحقل، وإزاحةٌ إلى الخارج تقع
   على الحدّ نفسه فتبدو خطّاً مضاعفاً.
   The outline is pulled inward: the button sits against the field's edge, and
   an outward offset would land on the border itself and read as a doubled
   line. */
.reveal:focus-visible { outline-offset: -1px; }

/* الأيقونتان في موضع واحد، وتُبدَّل الظاهرة منهما بحالة الزرّ نفسها لا بسطرٍ في
   السكربت: حالةٌ واحدة تحكم الشكل والمعنى معاً، فلا ينحرف أحدهما عن الآخر.
   The two icons share one place, and which of them shows is switched by the
   button's own state rather than by a line in the script: one state governs
   both the look and the meaning, so neither drifts from the other. */
.reveal .reveal-hide { display: none; }
.reveal[aria-pressed="true"] .reveal-show { display: none; }
.reveal[aria-pressed="true"] .reveal-hide { display: block; }

.switch {
  position: relative;
  width: 40px; height: 22px;
  flex: none;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: transparent;
  cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.switch::after {
  content: "";
  position: absolute;
  inset-block-start: 2px;
  inset-inline-start: 2px;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--fg-1);
  transition: inset-inline-start var(--dur) var(--ease), background var(--dur) var(--ease);
}
.switch[aria-checked="true"] { background: var(--surface-inv); border-color: var(--surface-inv); }
.switch[aria-checked="true"]::after { inset-inline-start: 20px; background: var(--on-inv); }

.toolbar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4);
  border-block-end: 1px solid var(--line);
}
.toolbar .spacer { flex: 1; min-width: 0; }

.search-inline { position: relative; flex: 0 1 240px; min-width: 150px; }
.search-inline .ico {
  position: absolute; inset-inline-start: 9px; top: 50%;
  transform: translateY(-50%); color: var(--fg-3); pointer-events: none;
}
.search-inline input {
  width: 100%; height: 32px;
  padding-inline: 31px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-1);
}
.search-inline input:focus { outline: none; border-color: var(--fg-1); background: var(--surface); }

.chip-row { display: flex; gap: 6px; flex-wrap: wrap; }
.chip {
  height: 30px;
  padding-inline: 11px;
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: transparent;
  font-size: 12px; font-weight: 600; color: var(--fg-3);
  cursor: pointer;
  white-space: nowrap;
  transition: all var(--dur) var(--ease);
}
.chip:hover { color: var(--fg-1); border-color: var(--line-strong); }
.chip[aria-pressed="true"] { background: var(--surface-inv); color: var(--on-inv); border-color: var(--surface-inv); }
.chip .n { font-variant-numeric: tabular-nums; opacity: 0.7; }

/* ------------------------------------------------------------ Tab strip */
.tabs {
  display: flex;
  gap: var(--sp-1);
  border-block-end: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 9px 13px;
  border: 0;
  background: transparent;
  color: var(--fg-3);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--dur) var(--ease);
}
.tab:hover { color: var(--fg-1); }
.tab[aria-selected="true"] { color: var(--fg-1); }
.tab[aria-selected="true"]::after {
  content: "";
  position: absolute;
  inset-inline: 6px;
  inset-block-end: -1px;
  height: 2px;
  background: var(--fg-1);
  border-radius: 2px 2px 0 0;
}

/* -------------------------------------------------------------- States */
/* Named `empty-state`, not `empty`: a bare `.empty` would also match the
   `.legend-swatch.empty` pattern modifier and blow the swatch up. */
.empty-state {
  display: grid;
  place-items: center;
  gap: var(--sp-3);
  padding: var(--sp-7) var(--sp-5);
  text-align: center;
  color: var(--fg-3);
}
.empty-icon {
  width: 48px; height: 48px;
  display: grid; place-items: center;
  border: 1px dashed var(--line-strong);
  border-radius: 50%;
}
.empty-state b { color: var(--fg-1); font-size: 14px; }
.empty-state p { font-size: 12.5px; max-width: 42ch; }

/* ------------------------------------------------- Announcing dialogue */
/* نافذة النتيجة، مُلبَسةً هوية اللوحة.
   -------------------------------------------------------------------------
   والمكتبة تأتي بلونها: أخضرُ للقبول وأحمرُ للرفض وأزرقُ للزرّ. وأوّل سطرٍ في
   هذا الملفّ يقول إن النظام أحادي اللون — أسودُ وأبيضُ ومزيجُهما، ولا لون
   ثالث. فنافذةٌ خضراء في وسط لوحةٍ بلا لون ليست تنبيهاً، هي عنصرٌ غريب.

   The outcome dialogue, dressed in the console's identity.

   The library arrives with its own colour: green for acceptance, red for
   refusal, blue for the button. The first line of this file says the system is
   monochrome — black, white and their blend, and no third colour. A green
   dialogue in the middle of a colourless console is not an alert, it is a
   stranger.

   والفرق بين «تمّ» و«لم يتمّ» يبقى مقروءاً بلا لون: الأولى برسمٍ محبَّر
   والثانية برسمٍ مفرَّغ — كما تفرّق الرقاقة بين الحالات في هذا الملفّ كلّه.

   The difference between "done" and "not done" stays readable with no colour:
   the first with a filled mark and the second with a hollow one — exactly as the
   pills tell states apart throughout this file. */
.swal2-container { z-index: 300; }

.swal2-popup.console-swal {
  font-family: var(--ff-sans);
  background: var(--surface);
  color: var(--fg-1);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-3);
  padding: var(--sp-5);
  width: min(420px, 92vw);
}

.console-swal .swal2-title {
  font-size: 16px;
  font-weight: 700;
  color: var(--fg-1);
  padding: 0;
  margin-block-start: var(--sp-3);
}

.console-swal .swal2-html-container {
  font-size: 13px;
  color: var(--fg-2);
  margin: var(--sp-3) 0 0;
  padding: 0;
  line-height: 1.65;
}

.console-swal .swal2-actions { margin-block-start: var(--sp-5); gap: var(--sp-2); }

/* والأزرار أزرار اللوحة نفسها: ‎buttonsStyling: false‎ يمنع المكتبة من
   تلوينها، وهذه القاعدة تعطيها شكلها.
   The buttons are the console's own: buttonsStyling: false stops the library
   colouring them, and this rule gives them their shape. */
.console-swal .swal2-confirm,
.console-swal .swal2-cancel {
  display: inline-flex; align-items: center; justify-content: center;
  height: 36px;
  padding-inline: 16px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: transparent;
  color: var(--fg-1);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.console-swal .swal2-confirm {
  background: var(--surface-inv);
  color: var(--on-inv);
  border-color: var(--surface-inv);
}

.console-swal .swal2-confirm:hover { opacity: 0.86; }
.console-swal .swal2-cancel:hover { background: var(--surface-2); }

/* والأيقونة بالحبر: محبَّرة للنجاح، مفرَّغة لما لم يتمّ.
   The icon in ink: filled for success, hollow for what did not happen. */
/* والأيقونة تُصغَّر بمقبضها لا بفرض مقاسٍ عليها.
   -------------------------------------------------------------------------
   المكتبة تبني الأيقونة كلَّها بـ‎em‎: القرص ‎5em‎، وحدُّه ‎.25em‎، وخطّا علامة
   الصحّ موضعُهما وطولهما بالنسبة نفسها. فكتابةُ ‎54px‎ للعرض والارتفاع تقصّ
   الصندوق وحده وتترك الخطّين بمقاس ‎em‎ الموروث — فيخرجان عن موضعهما ويتقاطعان،
   فتُقرأ علامة الصحّ ضربةَ شطبٍ لا صحّاً. وكانت كذلك في كل رسالة نجاح.
   و‎--swal2-icon-zoom‎ هو المقبض الذي تعلنه المكتبة لهذا بعينه: يصغّر الرسم
   كلَّه بنسبةٍ واحدة، فتبقى الخطوط على مواضعها منه.

   The icon is reduced by its own handle rather than by a size forced on it.

   The library builds the whole icon in em: the disc is 5em, its border .25em,
   and the two strokes of the check are placed and measured in the same
   proportion. Writing 54px for the width and height cuts the box alone and
   leaves the two strokes at the inherited em size — so they land off their
   places and cross, and the check reads as a strike-through rather than a tick.
   It did, on every success message.
   --swal2-icon-zoom is the handle the library declares for exactly this: it
   reduces the whole drawing by one ratio, so the strokes keep their places in
   it. */
.console-swal .swal2-icon {
  --swal2-icon-zoom: 0.68;
  border-color: var(--line-strong);
  color: var(--fg-1);
  margin: 0 auto;
}

.console-swal .swal2-icon.swal2-success {
  border-color: var(--surface-inv);
  background: var(--surface-inv);
}

.console-swal .swal2-icon.swal2-success [class^='swal2-success-line'] { background-color: var(--on-inv); }
.console-swal .swal2-icon.swal2-success .swal2-success-ring { border-color: var(--surface-inv); }

/* والقطع الثلاث التي تخفي أطراف الرسم أثناء الحركة تُشطب، ولا تُلوَّن.
   -------------------------------------------------------------------------
   وكانت القاعدة مكتوبةً ولا تعمل: المكتبة تكتب ‎background-color: #fff‎ في
   سمة ‎style‎ على العناصر الثلاثة نفسها، والسطريّ يتغلّب على ورقة الأنماط مهما
   دقّ محدّدها. فتظهر ثلاث شرائح بيضاء فوق القرص العنبريّ في كل رسالة نجاح —
   في السمتين معاً، لا في الليليّ وحده كما كان يُظنّ.
   و‎!important‎ هنا ليست تهاوناً: هي الأداة الوحيدة في ورقة المؤلّف التي تتغلّب
   على سطريٍّ غير مهمّ، والبديل تعطيل حركة المكتبة كلّها.

   The three pieces that hide the drawing's ends during the animation are struck
   out rather than coloured.

   The rule was written and did not work: the library writes
   background-color: #fff into the style attribute on those very elements, and
   an inline declaration beats a stylesheet however sharp its selector. So three
   white slivers showed over the umber disc on every success message — in both
   themes, not in the dark one alone as was assumed.
   !important here is not laxity: it is the only tool in an author sheet that
   overcomes a non-important inline declaration, the alternative being to switch
   the library's animation off altogether. */
.console-swal .swal2-icon.swal2-success [class^='swal2-success-circular-line'],
.console-swal .swal2-icon.swal2-success .swal2-success-fix {
  background: transparent !important;
  background-color: transparent !important;
}

.console-swal .swal2-icon.swal2-warning,
.console-swal .swal2-icon.swal2-error,
.console-swal .swal2-icon.swal2-info,
.console-swal .swal2-icon.swal2-question {
  border-color: var(--line-strong);
  color: var(--fg-1);
  background: var(--surface-2);
}

.console-swal .swal2-icon.swal2-error [class^='swal2-x-mark-line'] { background-color: var(--fg-1); }

.swal2-container.swal2-backdrop-show { background: rgb(var(--ink) / 0.55); }

/* --------------------------------------------------------------- Modal */
.modal-scrim {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: var(--sp-4);
  background: rgb(var(--ink) / 0.55);
  backdrop-filter: blur(3px);
  animation: fade 0.18s var(--ease);
}
.modal {
  width: min(680px, 100%);
  max-height: 86dvh;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-3);
  overflow: hidden;
  animation: pop 0.2s var(--ease);
}
.modal-head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-block-end: 1px solid var(--line);
}
.modal-head h3 { flex: 1; font-size: 15px; }
.modal-body { padding: var(--sp-5); overflow-y: auto; }
.modal-foot {
  display: flex; justify-content: flex-end; gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-5);
  border-block-start: 1px solid var(--line);
  background: var(--surface-1);
}

@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes pop { from { opacity: 0; transform: translateY(8px) scale(0.985); } to { opacity: 1; transform: none; } }

/* -------------------------------------------------------------- Toasts */
.toasts {
  position: fixed;
  inset-block-end: var(--sp-5);
  inset-inline-end: var(--sp-5);
  z-index: 300;
  display: grid;
  gap: var(--sp-2);
  width: min(360px, calc(100vw - 32px));
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface-inv);
  color: var(--on-inv);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
  font-size: 13px;
  animation: toastIn 0.24s var(--ease);
}
.toast b { display: block; font-size: 13px; }
.toast span { font-size: 12px; opacity: 0.75; }
.toast.out { animation: toastOut 0.2s var(--ease) forwards; }

@keyframes toastIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes toastOut { to { opacity: 0; transform: translateY(6px); } }

/* -------------------------------------------------------------- Popover */
.popover {
  position: fixed;
  z-index: 210;
  width: min(320px, calc(100vw - 24px));
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
  overflow: hidden;
  animation: pop 0.16s var(--ease);
}
.popover-head {
  padding: var(--sp-3) var(--sp-4);
  border-block-end: 1px solid var(--line);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fg-3);
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
}
.popover-list { max-height: 320px; overflow-y: auto; }
.menu-item {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%;
  padding: 9px var(--sp-4);
  border: 0; background: transparent;
  font-size: 13px; text-align: start; cursor: pointer;
  color: var(--fg-2);
}
.menu-item:hover { background: var(--surface-2); color: var(--fg-1); }
.menu-sep { height: 1px; background: var(--line); margin: var(--sp-1) 0; }

/* --------------------------------------------------------- Misc blocks */
/* قائمة «مفتاح: قيمة».
   -------------------------------------------------------------------------
   والقيمة قد تطول: رقم هوية من خمسة عشر رقماً، أو بريدٌ لا فراغ فيه. وهي في
   عمودٍ مقاسه ‎1fr‎ — و‎1fr‎ لا يعني «ما بقي» بل «ما بقي، وعلى الأقل مقاسُ
   المحتوى» ما لم يُقَل غير ذلك. فكلمةٌ واحدة لا تنكسر توسّع البطاقة وتدفعها
   خارج عمودها.

   The "key: value" list.

   A value may run long: a fifteen-digit identity number, or an email with no
   space in it. It sits in a 1fr column — and 1fr does not mean "what is left" but
   "what is left, and at least the content's size" unless it is told otherwise. So
   one unbreakable word widens the card and pushes it out of its column. */
.kv { display: grid; grid-template-columns: minmax(90px, auto) minmax(0, 1fr); gap: 9px var(--sp-4); font-size: 13px; }
.kv dt { color: var(--fg-3); font-size: 12px; min-width: 0; overflow-wrap: anywhere; }
.kv dd {
  margin: 0;
  min-width: 0;
  font-weight: 550;

  /* والقيمة تلتفّ حيث لا موضع للكسر: رقمٌ طويل يُقرأ في سطرين ولا يمدّ
     البطاقة.
     The value breaks where there is nowhere to break: a long number reads over
     two lines rather than stretching the card. */
  overflow-wrap: anywhere;

  /* وما فيها من وسومٍ صغيرة — شارةُ توثيقٍ إلى جانب رقم — ينزل سطراً حين يضيق
     الموضع بدل أن يخرج منه.
     What small marks it carries — a verification badge beside a number — drop to
     a line of their own when the room narrows rather than leaving it. */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
}

.note {
  display: flex;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line);
  border-inline-start: 3px solid var(--fg-1);
  border-radius: var(--r-sm);
  background: var(--surface-1);
  font-size: 12.5px;
  color: var(--fg-2);
}
.note .ico { flex: none; color: var(--fg-1); margin-block-start: 1px; }
.note b { color: var(--fg-1); }

.code {
  font-family: var(--ff-mono);
  font-size: 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 1px 6px;
  white-space: nowrap;
}

/* Identifiers, cron expressions, IPs and endpoint paths are inherently
   left-to-right; isolating them stops bidi reordering inside Arabic text. */
.code, .cell-id { direction: ltr; unicode-bidi: isolate; }

.divider { height: 1px; background: var(--line); margin-block: var(--sp-4); }

.split-stat {
  display: flex;
  flex-wrap: wrap;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
}
.split-stat > div {
  flex: 1 1 130px;
  padding: var(--sp-3) var(--sp-4);
  border-inline-end: 1px solid var(--line);
}
.split-stat > div:last-child { border-inline-end: 0; }
.split-stat b { display: block; font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: var(--tr-lg); }
.split-stat span { font-size: 11.5px; color: var(--fg-3); }

.pattern-band {
  height: 8px;
  border-radius: var(--r-pill);
  background-image: repeating-linear-gradient(45deg, var(--fg-1) 0 2px, transparent 2px 6px);
  border: 1px solid var(--line);
}

/* ==================================================== OpenStreetMap layer */
/* Leaflet ships a colourful default. Everything below re-skins it to the same
   two-colour system as the rest of the console, and the grayscale filter on the
   tile pane keeps the raster imagery free of any hue of its own — so the only
   colour on the page stays the console's ink. */

.gm-wrap {
  position: relative;
  height: var(--gm-h, 460px);
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--surface-1);
}

.gm { position: absolute; inset: 0; background: var(--surface-1); }

/* grayscale() output has R=G=B on every pixel — pure black/white shades.

   والترتيب بعد ذلك مقصود، لا عادةً في الكتابة. البلاطة كلُّها فوق المنتصف —
   ضوءُ بكسلاتها بين ‎221‎ و‎255‎ — و‎contrast‎ يدور حول ‎0.5‎: فرفعُه وحده يدفع تلك
   الدرجات إلى البياض فيمحوها بدل أن يُظهرها، وهو ما كان يفعله ‎contrast(1.06)‎
   ومعه ‎brightness(1.02)‎ يزيده. فاللمعان الآن يُنزل المدى إلى حيث للتباين أثر،
   ثمّ يُوسَّع هناك.

   وليلاً ينقلب الأمر حرفاً بحرف: بعد ‎invert‎ يصير المدى كلُّه تحت المنتصف، فيقصّ
   ‎contrast‎ إلى السواد لا إلى البياض، واللمعانُ هو الذي يرفع.

   القياس على البلاطات نفسها — طاقةُ الحواف، أي ما يفرّق بكسلاً عن جاره:
   نهاراً ‎1805‎ ← ‎3230‎، وليلاً ‎2313‎ ← ‎6169‎.

   The order after that is deliberate rather than habit. The tile sits entirely
   above the pivot — its pixels fall between 221 and 255 — and contrast() turns
   on 0.5, so raising it alone drives those levels into white and erases them
   instead of revealing them. That is what contrast(1.06) was doing, with
   brightness(1.02) making it worse. Brightness now brings the range down to
   where contrast bites, and only then is it expanded.

   By night the argument inverts letter for letter: after invert(1) the range
   lies entirely below the pivot, where contrast crushes to black rather than
   clipping to white, and brightness is what lifts.

   Measured on the same tiles — edge energy, how much one pixel differs from its
   neighbour: 1805 -> 3230 by day, 2313 -> 6169 by night. */
.mapboxgl-canvas { filter: grayscale(1) brightness(0.85) contrast(1.36); }
[data-theme="dark"] .mapboxgl-canvas { filter: grayscale(1) invert(1) brightness(2.4) contrast(0.92); }

.mapboxgl-map {
  background: var(--surface-1);
  font-family: var(--ff-sans);
  outline: none;
}
/* Mapbox's own stylesheet carries real hues the console does not own — a link
   colour, a tap-highlight, a focus ring around its buttons, and the box-zoom
   outline. They are neutralised here so the console's two-colour rule holds for
   third-party CSS too. */
.mapboxgl-map a { color: var(--fg-2); -webkit-tap-highlight-color: rgb(var(--ink) / 0.25); }
.mapboxgl-boxzoom { border: 2px dashed var(--fg-1); background: rgb(var(--paper) / 0.45); }
.mapboxgl-map:focus-visible { outline: 2px solid var(--fg-1); }
.mapboxgl-ctrl-attrib-button:focus,
.mapboxgl-ctrl button:focus { box-shadow: none; outline: 2px solid var(--fg-1); outline-offset: 1px; }

/* Mapbox's stock control chrome, neutralised */
/* Matched to .mapboxgl-map .mapboxgl-ctrl-attrib, the stronger
   selector Mapbox uses for its own white background. */
.mapboxgl-map .mapboxgl-ctrl-attrib {
  background: rgb(var(--paper) / 0.86);
  color: var(--fg-3);
  font-size: 10px;
  padding: 2px 7px;
  border-radius: var(--r-pill) 0 0 0;
  backdrop-filter: blur(3px);
  direction: ltr;
  unicode-bidi: isolate;
}
.mapboxgl-ctrl-attrib a { color: var(--fg-2); text-decoration: underline; }
.gm-attr-lead { color: var(--fg-3); }

/* وعلامةُ الطيّ على الشاشة الضيّقة: يرسمها Mapbox صورةً بأرضٍ بيضاء نصف شفافة،
   وهي على ورقةٍ داكنة قرصٌ فاتحٌ لا سبب له. فتُترك الصورة — هي حرف «i» — وتُنزع
   أرضُها، ويُقلب لونها ليلاً كما تُقلب البلاطة.

   The fold's mark on a narrow screen: Mapbox draws it as an image over a
   half-transparent white ground, which on a dark sheet is a pale disc for no
   reason. The image is left — it is an "i" — its ground taken away, and its
   colour inverted by night as the tiles are. */
.mapboxgl-ctrl-attrib-button { background-color: transparent; opacity: 0.55; }
.mapboxgl-ctrl-attrib-button:hover { opacity: 1; }
[data-theme="dark"] .mapboxgl-ctrl-attrib-button { filter: invert(1); }
.mapboxgl-ctrl-attrib.mapboxgl-compact-show .mapboxgl-ctrl-attrib-button { background-color: transparent; }

.mapboxgl-ctrl-scale {
  background: rgb(var(--paper) / 0.86);
  border: 1px solid var(--line-strong);
  border-block-start: 0;
  color: var(--fg-2);
  font-size: 10px;
  font-family: var(--ff-mono);
  padding: 1px 6px;
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  box-shadow: none;
}

/* شعار Mapbox: يبقى، ويهدأ.
   -------------------------------------------------------------------------
   وجودُه شرطٌ في ترخيص الخدمة لا خيارٌ في التصميم، فلا يُخفى. وأمّا حِدّتُه على
   ورقةٍ ثنائية اللون فتُخفَّف بشفافيةٍ ترفعها إلى ما هو تامّ عند المرور — فمن
   أراد قراءته قرأه، ومن لم يُرده لم يزاحمه.

   The Mapbox wordmark: it stays, and it quietens.

   Its presence is a condition of the service's licence rather than a choice in
   the design, so it is not hidden. Its loudness on a two-colour sheet is softened
   by an opacity that lifts to full on hover — whoever wants to read it reads it,
   and whoever does not is not crowded by it. */
.mapboxgl-ctrl-logo { opacity: 0.42; transition: opacity 0.16s var(--ease); }
.mapboxgl-ctrl-logo:hover, .mapboxgl-ctrl-logo:focus-visible { opacity: 1; }

/* In RTL the control corners are mirrored so chrome stays on the inline side. */
[dir="rtl"] .mapboxgl-map { direction: rtl; }
.mapboxgl-ctrl, .mapboxgl-popup-content { direction: inherit; }

/* ------------------------------------------------------- custom controls */
.gm-ctrl {
  background: rgb(var(--paper) / 0.94);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
  backdrop-filter: blur(6px);
  overflow: hidden;
  margin: 10px !important;
  color: var(--fg-1);
}

.gm-ctrl--stack { display: grid; }
.gm-btn {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border: 0; background: transparent; cursor: pointer;
  color: var(--fg-2);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.gm-btn + .gm-btn { border-block-start: 1px solid var(--line); }
.gm-btn:hover { background: var(--surface-2); color: var(--fg-1); }

.gm-ctrl--row { display: flex; padding: 3px; gap: 3px; }
.gm-chip {
  display: inline-flex; align-items: center; gap: 5px;
  height: 26px; padding-inline: 9px;
  border: 0; border-radius: var(--r-sm);
  background: transparent; cursor: pointer;
  font-size: 11.5px; font-weight: 600; color: var(--fg-3);
  white-space: nowrap;
}
.gm-chip:hover { background: var(--surface-2); color: var(--fg-1); }
.gm-chip[aria-pressed="true"] { background: var(--surface-inv); color: var(--on-inv); }

.gm-ctrl--legend { padding: 9px 11px; display: grid; gap: 6px; max-width: 190px; }
.gm-legend-title {
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--fg-3);
}
.gm-legend-item { display: flex; align-items: center; gap: 7px; font-size: 11px; color: var(--fg-2); }

/* --------------------------------------------------------- live badge */
.gm-ctrl--live { padding: 9px 11px; display: grid; gap: 5px; min-width: 168px; }
.gm-live-head { display: flex; align-items: center; gap: 7px; }
.gm-live-head b {
  flex: 1; font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-3);
}
.gm-live-dot {
  width: 8px; height: 8px; flex: none;
  border-radius: 50%;
  background: var(--fg-1);
  animation: gmPulse 1.8s var(--ease) infinite;
}
.gm-live-dot[data-on="false"] { animation: none; background: transparent; border: 1.5px dashed var(--fg-3); }
.gm-live-toggle {
  width: 20px; height: 20px; flex: none;
  display: grid; place-items: center;
  border: 0; background: transparent; cursor: pointer; color: var(--fg-3);
  border-radius: var(--r-sm);
}
.gm-live-toggle:hover { background: var(--surface-2); color: var(--fg-1); }

.gm-live-metric { display: flex; align-items: baseline; gap: 6px; }
.gm-live-value { font-size: 19px; font-weight: 700; letter-spacing: var(--tr-lg); font-variant-numeric: tabular-nums; }
.gm-live-label { font-size: 10.5px; color: var(--fg-3); }
.gm-live-event {
  display: flex; align-items: center; gap: 5px;
  font-size: 10.5px; color: var(--fg-3);
  padding-block-start: 5px;
  border-block-start: 1px solid var(--line-soft);
  min-height: 22px;
}
.gm-live-event span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gm-live-event.is-new { animation: gmFadeIn 0.5s var(--ease); }

@keyframes gmPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgb(var(--ink) / 0.5); }
  70% { box-shadow: 0 0 0 7px rgb(var(--ink) / 0); }
}
@keyframes gmFadeIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }

/* -------------------------------------------------------------- markers */
/* حاملُ الدبوس. كان محرّك الخرائط يُلبس أيقونته صنفاً يُعرف به، وصار يُكتب هنا
   ليبقى ما بُني على المرور بالفأرة قائماً — ‎.gm-marker:hover‎ أدناه هو ما كان
   ‎.leaflet-marker-icon:hover‎.

   The pin's carrier. The map engine used to dress its icon in a class of its own
   to be known by; it is written here now so what was built on hovering still
   stands — .gm-marker:hover below is what .leaflet-marker-icon:hover was. */
.gm-marker { cursor: pointer; }
.gm-marker--label { cursor: default; }

.gm-pin {
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1.75px solid var(--fg-1);
  background: var(--surface);
  color: var(--fg-1);
  box-shadow: 0 1px 4px rgb(var(--ink) / 0.28);
  transition: transform 0.16s var(--ease);
}
.gm-marker:hover .gm-pin { transform: scale(1.12); }
.gm-pin--solid { background: var(--fg-1); color: var(--surface); }
.gm-pin--dashed { border-style: dashed; }
.gm-pin--hatch {
  background-image: repeating-linear-gradient(45deg, var(--fg-1) 0 2px, transparent 2px 5px);
}
.gm-pin--soft { border-color: var(--fg-3); color: var(--fg-3); }
.gm-pin--ping { background: var(--fg-1); color: var(--surface); }
.gm-pin--mini {
  width: 13px; height: 13px; border-width: 1.25px; flex: none;
  box-shadow: none;
}

.gm-cluster-wrap { position: relative; display: block; }
.gm-cluster {
  display: grid; place-items: center;
  width: 100%; height: 100%;
  border-radius: 50%;
  background: var(--fg-1);
  color: var(--surface);
  border: 2px solid var(--surface);
  box-shadow: 0 2px 10px rgb(var(--ink) / 0.35);
  font-variant-numeric: tabular-nums;
  transition: transform 0.16s var(--ease);
}
.gm-cluster b { font-size: 12px; font-weight: 700; letter-spacing: var(--tr-lg); }
.gm-marker:hover .gm-cluster { transform: scale(1.08); }

.gm-cluster-name {
  position: absolute;
  inset-block-start: 100%;
  inset-inline-start: 50%;
  transform: translate(-50%, 4px);
  white-space: nowrap;
  font-size: 10.5px;
  font-weight: 650;
  color: var(--fg-1);
  text-shadow:
    0 0 3px rgb(var(--paper)), 0 0 3px rgb(var(--paper)),
    0 0 6px rgb(var(--paper)), 0 0 6px rgb(var(--paper));
  pointer-events: none;
}
[dir="rtl"] .gm-cluster-name { transform: translate(50%, 4px); }

.gm-label {
  position: absolute;
  transform: translate(-50%, -50%);
  white-space: nowrap;
  font-size: 10.5px;
  font-weight: 650;
  color: var(--fg-1);
  text-shadow:
    0 0 3px rgb(var(--paper)), 0 0 3px rgb(var(--paper)),
    0 0 6px rgb(var(--paper)), 0 0 6px rgb(var(--paper));
  pointer-events: none;
}
.gm-label--city { font-size: 12px; letter-spacing: 0.02em; }
.gm-label--district { font-size: 10px; color: var(--fg-2); font-weight: 600; }
.gm-label--value {
  font-family: var(--ff-mono);
  font-size: 10px;
  transform: translate(-50%, 12px);
}
.gm-label--place { display: grid; gap: 1px; text-align: center; line-height: 1.25; }
.gm-place-name { font-size: 11px; font-weight: 650; }
.gm-place-value {
  font-family: var(--ff-mono);
  font-size: 9.5px;
  color: var(--fg-2);
  direction: ltr;
  unicode-bidi: isolate;
}

.gm-ping {
  display: grid; place-items: center;
  width: 100%; height: 100%;
  border-radius: 50%;
  background: var(--fg-1);
  color: var(--surface);
  animation: gmPing 2.6s var(--ease) forwards;
}
@keyframes gmPing {
  0% { transform: scale(0.4); opacity: 0; box-shadow: 0 0 0 0 rgb(var(--ink) / 0.55); }
  18% { transform: scale(1); opacity: 1; }
  100% { transform: scale(1); opacity: 0; box-shadow: 0 0 0 26px rgb(var(--ink) / 0); }
}

/* --------------------------------------------------------------- discs */
/* الدوائر: صناديق مستديرة لا أشكال SVG.
   -------------------------------------------------------------------------
   كانت مساراتٍ في طبقة Leaflet المتّجهة، تُوصف بـ‎fill‎ و‎stroke‎. وصارت عناصر
   DOM يُقاس قطرها بالبكسل عند كل تقريب، فتُوصف بـ‎background‎ و‎border‎. والصفتان
   تقولان الشيء نفسه بلغتين، والمقادير هي هي.

   ولمَ نُقلت أصلاً؟ لأن ما يُرسم داخل لوحة Mapbox يقع تحت مرشِّح الرمادي الذي
   يُصبغ به الأساس، فتفقد الهالةُ ودائرةُ السعر ما تعطيه إياهما هذه الورقة. وما
   كان خارج اللوحة نجا — وهذا هو الفرق نفسه الذي كان يحفظه ‎.leaflet-tile-pane‎.

   The circles: rounded boxes rather than SVG shapes.

   They were paths in Leaflet's vector layer, described with fill and stroke. They
   are now DOM elements whose diameter is measured in pixels at every zoom, and so
   described with background and border. The two say the same thing in two
   languages, and the quantities are unchanged.

   And why were they moved at all? Because what is drawn inside Mapbox's canvas
   falls under the greyscale filter the basemap is tinted with, and the halo and
   the price disc would lose what this sheet gives them. What sits outside the
   canvas is spared — the same separation .leaflet-tile-pane used to keep. */
.gm-disc {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  box-sizing: border-box;
}

.gm-halo { background: rgb(var(--ink) / 0.07); border: 1px dashed rgb(var(--ink) / 0.35); }
.gm-price { background: rgb(var(--ink) / 0.2); border: 1.4px solid var(--fg-1); }
.gm-price--down { background: rgb(var(--ink) / 0.06); border-style: dashed; }
.gm-marker:hover .gm-price { background: rgb(var(--ink) / 0.34); }
.gm-demand { background: rgb(var(--ink) / 0.1); border: 1.3px dashed var(--fg-1); }
.gm-demand--off { background: rgb(var(--ink) / 0.03); border-color: var(--fg-3); }

/* --------------------------------------------------------------- popups */
/* البطاقة صندوقٌ واحد عند Mapbox لا صندوقان.
   -------------------------------------------------------------------------
   كان في Leaflet غلافٌ يحمل الخلفية والحدّ، وداخله محتوىً يحمل العرض. وMapbox
   يجمعهما في ‎.mapboxgl-popup-content‎ واحد، فتُجمع القاعدتان فيه. وسقط معهما
   ‎width: auto !important‎: لم يعد أحدٌ يكتب العرض في صفةٍ سطرية، فلم يبقَ ما
   يُتجاوز.

   The card is one box under Mapbox rather than two.

   Leaflet had a wrapper carrying the background and the border, and a content
   node inside it carrying the width. Mapbox folds both into one
   .mapboxgl-popup-content, so the two rules fold into it. The
   `width: auto !important` went with them: nobody writes the width into a style
   attribute any more, so there is nothing left to override. */
.mapboxgl-popup-content {
  background: var(--surface);
  color: var(--fg-1);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-3);
  padding: 0;
  text-align: start;
  min-width: 216px;
  font-size: inherit;
  line-height: inherit;
}

/* والذيل مثلّثٌ مرسومٌ بالحدود، لونُه حدُّ الجهة المقابلة لاتجاه فتحها. فلكل
   اتجاهٍ حدُّه، ولا تُختصر في قاعدةٍ واحدة: الحدود الثلاثة الأخرى شفافةٌ عمداً
   وصبغُها يعطي مربّعاً لا مثلّثاً.

   The tip is a triangle drawn out of borders, its colour on the side opposite
   the direction it opens. So each direction has its own side, and they do not
   collapse into one rule: the other three borders are transparent deliberately,
   and colouring them gives a square rather than a triangle. */
.mapboxgl-popup-anchor-top .mapboxgl-popup-tip,
.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip,
.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip { border-bottom-color: var(--surface); }
.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip,
.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip,
.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip { border-top-color: var(--surface); }
.mapboxgl-popup-anchor-left .mapboxgl-popup-tip { border-right-color: var(--surface); }
.mapboxgl-popup-anchor-right .mapboxgl-popup-tip { border-left-color: var(--surface); }

/* Mapbox pins this button with `top: 0; right: 0`. Ours has to unpin it, or the
   close button stays welded to the right edge and lands on top of the title
   once the popup is mirrored for Arabic. */
.mapboxgl-popup-close-button {
  color: var(--fg-3);
  inset-block-start: 2px;
  inset-inline-end: 4px; inset-inline-start: auto;
  right: auto;
  width: 22px; height: 22px;
  font: 17px/20px var(--ff-sans);
  background: transparent;
  border-radius: var(--r-sm);
}
.mapboxgl-popup-close-button:hover,
.mapboxgl-popup-close-button:focus { color: var(--fg-1); background: transparent; }

.gm-popup { padding: 11px 13px 12px; }
.gm-popup-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding-inline-end: 18px;
  padding-block-end: 8px;
  margin-block-end: 8px;
  border-block-end: 1px solid var(--line);
}
.gm-popup-head b { font-size: 12.5px; font-weight: 650; }
.gm-popup-kv { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; margin: 0; }
.gm-popup-kv dt { font-size: 10.5px; color: var(--fg-3); white-space: nowrap; }
.gm-popup-kv dd { margin: 0; font-size: 11.5px; text-align: end; }
.gm-mono { font-family: var(--ff-mono); font-size: 11px; direction: ltr; unicode-bidi: isolate; }

/* ------------------------------------------------------- degraded state */
.gm-fallback {
  position: absolute; inset: 0;
  display: grid; place-content: center; justify-items: center;
  gap: 8px; padding: 24px; text-align: center;
  color: var(--fg-3);
  background-image: repeating-linear-gradient(45deg, rgb(var(--ink) / 0.04) 0 6px, transparent 6px 14px);
}
.gm-fallback b { color: var(--fg-1); font-size: 13px; }
.gm-fallback p { font-size: 11.5px; max-width: 34ch; }

/* On a phone the map chrome would eat the canvas: drop the legend, tighten
   the live badge, and cap the height so the map never owns the whole screen. */
@media (max-width: 720px) {
  .gm-wrap { height: min(var(--gm-h, 460px), 58vh); }
  .gm-ctrl { margin: 7px !important; }
  .gm-ctrl--legend { display: none; }
  .gm-ctrl--live { min-width: 0; padding: 7px 9px; gap: 3px; }
  .gm-live-value { font-size: 16px; }
  .gm-live-label { font-size: 9.5px; }
  .gm-live-event { font-size: 9.5px; max-width: 150px; }
  .gm-chip { height: 24px; padding-inline: 7px; font-size: 11px; }
  .gm-btn { width: 30px; height: 30px; }
}

.gm[data-tiles="failed"]::after {
  content: attr(data-tiles-msg);
  position: absolute;
  inset-block-start: 50%; inset-inline: 12%;
  transform: translateY(-50%);
  z-index: 500;
  padding: 12px 16px;
  text-align: center;
  font-size: 12px;
  color: var(--fg-2);
  background: rgb(var(--paper) / 0.94);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
}

/* -------------------------------------------------------- Responsive  */
@media (max-width: 1400px) {
  .grid.cols-5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid.cols-4 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 1200px) {
  .grid.cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .span-3 { grid-column: span 2; }
}

/* The two sidebar controls are mutually exclusive: the rail toggle only means
   something while the sidebar is docked, the drawer button only while it is not. */
@media (min-width: 1025px) { #btn-drawer { display: none; } }
@media (max-width: 1024px) { #btn-rail { display: none; } }

@media (max-width: 1024px) {
  :root { --topbar-h: 58px; }
  .sidebar {
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0;
    width: min(300px, 84vw);
    transform: translateX(-102%);
    box-shadow: var(--shadow-3);
  }
  [dir="rtl"] .sidebar { transform: translateX(102%); }
  .app[data-drawer="1"] .sidebar { transform: none; }

  /* هنا صار الشريط درجاً، فصار له مخرجٌ منظور.
     Here the sidebar has become a drawer, so it gets a visible way out. */
  .brand .brand-close { display: grid; }
  .app[data-drawer="1"] .scrim { opacity: 1; visibility: visible; }
  /* The rail state is desktop-only; the drawer always shows full labels. */
  .app[data-rail="1"] .sidebar { width: min(300px, 84vw); }
  .app[data-rail="1"] .brand { justify-content: flex-start; padding-inline: var(--sp-4); }
  .app[data-rail="1"] .nav-label,
  .app[data-rail="1"] .nav-count,
  .app[data-rail="1"] .nav-group-title,
  .app[data-rail="1"] .env-card > div { display: block; }
  .app[data-rail="1"] .nav-item { justify-content: flex-start; padding-inline: var(--sp-3); }
  .app[data-rail="1"] .nav-item::after { display: none; }
  .app[data-rail="1"] .env-card { justify-content: flex-start; padding: var(--sp-3); }
  .content { padding: var(--sp-4); }
  .grid.cols-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 860px) {
  .topbar-search { display: none; }
  .avatar-meta { display: none; }
  .avatar-btn { padding-inline: 4px; }
  .crumbs { display: none; }
  .topbar-title h1 { font-size: 15px; }
  .page-head h2 { font-size: 19px; }
}

@media (max-width: 720px) {
  .grid.cols-2,
  .grid.cols-3,
  .grid.cols-4,
  .grid.cols-5 { grid-template-columns: minmax(0, 1fr); }
  .span-2, .span-3 { grid-column: span 1; }
  .card-body { padding: var(--sp-4); }
  .card-head { padding: var(--sp-3) var(--sp-4); }
  .stat { padding: var(--sp-3) var(--sp-4); }
  .stat-value { font-size: 23px; }
  .grid.auto, .grid.auto-lg { grid-template-columns: minmax(0, 1fr); }
  .kv { grid-template-columns: minmax(0, 1fr); gap: 2px var(--sp-3); }
  .kv dd { margin-block-end: 8px; }
  .toasts { inset-inline: var(--sp-3); width: auto; }
  .modal { max-height: 92dvh; }
}

@media (max-width: 480px) {
  :root { --sp-5: 16px; }
  .content { padding: var(--sp-3); }
  .topbar { padding-inline: var(--sp-3); gap: var(--sp-1); }
  .page-head { margin-block-end: var(--sp-4); }
  .btn { padding-inline: 11px; }
  /* Printing is reachable from the browser chrome on phones. */
  #btn-print { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------- Print */
@media print {
  body { overflow: visible; background: #fff; color: #000; }
  .sidebar, .topbar, .scrim, .toasts, .page-head-actions, .toolbar { display: none !important; }
  .app, .main-col, .content { display: block; height: auto; overflow: visible; padding: 0; }
  .card { break-inside: avoid; border: 1px solid #000; box-shadow: none; }
  .grid { display: block; }
  .card { margin-block-end: 12px; }
}

/* ============================================================== Sign-in */
/* سطح الدخول: الصفحة الوحيدة بلا شريط جانبي ولا شريط علوي، فلا تُبنى على
   قواعد القشرة. لوحة واحدة في وسط الشاشة، وبنفس الرموز — فلا يشعر الداخل أنه
   انتقل بين تطبيقين حين يصل إلى اللوحة.

   The sign-in surface: the one page with no sidebar and no top bar, so it is
   not built on the shell's rules. A single panel in the middle of the screen,
   on the same tokens — so nobody feels they crossed between two applications
   on their way into the console. */

.auth {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: var(--sp-5);
  position: relative;
  /* شبكة خافتة تكسر بياض الصفحة الفارغة دون أن تضيف لوناً.
     A faint grid breaking the emptiness without introducing a colour. */
  background-image:
    linear-gradient(to right, var(--line-soft) 1px, transparent 1px),
    linear-gradient(to bottom, var(--line-soft) 1px, transparent 1px);
  background-size: 46px 46px;
}

.auth-lang {
  position: absolute;
  inset-block-start: var(--sp-4);
  inset-inline-end: var(--sp-4);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding-inline: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  font-size: 12px;
  font-weight: 600;
  color: var(--fg-2);
}
.auth-lang:hover { color: var(--fg-1); background: var(--surface-2); }

.auth-panel {
  width: 100%;
  max-width: 392px;
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-6);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
}

.auth-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  padding-block-end: var(--sp-4);
  border-block-end: 1px solid var(--line);
}
/* على سطح الدخول لا شيء يزاحم العلامة، فتأخذ حجمها الحقيقي: هي أول ما يراه
   القادم وآخر ما يتأكّد به أنه على الباب الصحيح.
   On the sign-in surface nothing competes with the mark, so it takes its real
   size: it is the first thing an arrival sees and the last confirmation that
   they are at the right door. */
/* وعلى سطح الدخول يكبر الشعار فيكبر ظلّه معه: ظلٌّ ثابت المقاس تحت علامةٍ
   ضِعفَ حجمها يبدو خطّاً رفيعاً لا ارتفاعاً.
   On the sign-in surface the logo grows and its shadow grows with it: a
   fixed-size shadow under a mark twice the size reads as a thin line rather
   than as height. */
.brand-logo--auth {
  height: 76px;
  filter: drop-shadow(0 2px 2px rgba(60, 42, 8, 0.26))
          drop-shadow(0 7px 14px rgba(60, 42, 8, 0.22));
}
[data-theme="dark"] .brand-logo--auth {
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.55))
          drop-shadow(0 0 20px rgba(226, 178, 74, 0.3));
}

.auth-head h1 { font-size: 19px; font-weight: 700; letter-spacing: var(--tr-lg); }

.auth-error { border-inline-start-color: var(--fg-1); background: var(--surface-2); }

.auth-form { display: grid; gap: var(--sp-4); }
.auth-form .input { height: 40px; }

.auth-remember {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  color: var(--fg-2);
  cursor: pointer;
}
.auth-remember input { width: 15px; height: 15px; accent-color: var(--fg-1); cursor: pointer; }

.auth-form .btn { height: 40px; }

@media (max-width: 480px) {
  .auth { padding: 0; place-items: stretch; }
  .auth-panel { max-width: none; border: 0; border-radius: 0; box-shadow: none; padding: var(--sp-6) var(--sp-5); align-content: start; padding-block-start: 72px; }
}

/* ------------------------------------------------------- Role matrix */
/* مصفوفة الأدوار: صف لكل مجال، وقائمة اختيار للمستوى. تُعرض مقفلة لمن يقرأ
   ولا يعدّل، والقفل صفة العنصر نفسه لا طبقة فوقه.

   The role matrix: a row per domain and a select for the level. It is shown
   locked to whoever reads without editing, and the lock is a property of the
   control itself rather than a layer over it. */

.perm-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 132px;
  gap: var(--sp-2) var(--sp-3);
  align-items: center;
}

.perm-domain { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--fg-2); min-width: 0; }
.perm-domain .ico { flex: none; color: var(--fg-3); }
.perm-grid select.input { height: 30px; font-size: 12px; }
.perm-grid select.input:disabled { color: var(--fg-3); background: var(--surface-1); cursor: default; }

.role-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

/* --------------------------------------------------------- Review gateway */
/* الطابور جدول، وطاولة المراجعة صفحة.
   -------------------------------------------------------------------------
   وكان الطابور بطاقاتٍ: كل بطاقةٍ فيها صورةٌ وأعلامٌ وأوراقٌ وأربعةُ نماذج
   بحقولها. فيقف المراجع أمام خمسٍ وعشرين بطاقةً بمئةِ حقلٍ يقرّر بها في أشياء
   لا يرى منها إلا عنواناً وسعراً — وهو ما لا يُسمّى مراجعةً بل ختماً.

   والجدول يقول ما يقوله الصفّ: من ينتظر، ومنذ متى، وكم ينقصه. أما القرار فحيث
   يُرى ما يُقرَّر فيه.

   The queue is a table and the review desk is a page.

   The queue used to be cards: each with a photograph, flags, papers and four
   forms with their fields. A reviewer stood before twenty-five cards holding a
   hundred fields with which to decide on things they could see nothing of but a
   title and a price — which is not called reviewing but stamping.

   A table says what a row says: who is waiting, since when, and what they are
   short of. The decision belongs where what is being decided can be seen. */

/* خليّة الطلب: صورةٌ صغيرة إلى جانب عنوانه ومرجعه.
   The request's cell: a small photograph beside its title and reference. */
.rv-cell {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  max-width: 340px;
}

.rv-thumb {
  width: 40px;
  height: 40px;
  flex: none;
  object-fit: cover;
  border-radius: var(--r-sm);
}

.rv-thumb-empty {
  display: grid;
  place-items: center;
  border: 1px dashed var(--line);
  color: var(--fg-3);
  opacity: .6;
}

table.data .rv-cell .primary-cell { white-space: normal; overflow-wrap: anywhere; }

/* خليّة الوجهة: البوابة برسمها، وتحتها التصنيف الذي يُبلَغ منه.
   والرسم قبل الاسم لا بعده: عشرون صفّاً تُفرز بالنظر إلى عمودٍ من الرسوم أسرع
   ممّا تُفرز بقراءة عشرين سطراً.
   The destination cell: the portal with its drawing, and the classification it is
   reached by beneath. The drawing leads rather than trails: twenty rows sort
   faster down a column of marks than through twenty lines of reading. */
.rv-dest {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  max-width: 200px;
  font-size: 12px;
}

.rv-dest > .ico { flex: none; color: var(--fg-3); }
.rv-dest .sub i { font-style: normal; opacity: .75; }

/* -------------------------------------------------------- the review desk */

.rv-desk { min-width: 0; }
.rv-work { min-width: 0; }

/* والبطاقات تتتابع بفراغٍ بينها: بطاقتان متلاصقتان تُقرآن بطاقةً واحدة انكسر
   إطارها.
   The cards follow one another with space between: two cards touching read as
   one card with a broken frame. */
.card.rv-facts,
.card.rv-trail,
.card.rv-decide { margin-block-start: 16px; }

/* أجزاء بطاقة القرار: الأوراق، وسجلّ التصريح، ثم الفصل في الطلب.
   -------------------------------------------------------------------------
   وهي أجزاءٌ في بطاقةٍ واحدة لا بطاقات: القرار واحد وإن كان على ثلاث مراحل،
   وثلاثةُ إطارات تقول إنها ثلاثة أعمال. فيفصل بينها خطٌّ ورأسٌ صغير — يكفي
   لتُقرأ متمايزة، ولا يبلغ أن تُقرأ متفرّقة.

   The decision card's parts: the papers, the permit record, then the verdict on
   the request.

   They are parts of one card rather than cards: the decision is one thing even
   in three stages, and three frames say they are three jobs. A rule and a small
   heading divide them — enough to be read apart, not enough to be read as
   separate. */
.rv-part {
  padding-block-start: 14px;
  border-block-start: 1px solid var(--line-soft);
}

/* ولا خطَّ فوق الأوّل: هو يلي رأس البطاقة، وخطٌّ تحت خطٍّ يُقرأ إطاراً مكسوراً.
   No rule above the first: it follows the card's own head, and a rule beneath a
   rule reads as a broken frame. */
.rv-part:first-child { padding-block-start: 0; border-block-start: 0; }

.rv-part-head { margin-block-end: 10px; }
.rv-part-head h4 { font-size: 12.5px; font-weight: 600; margin: 0; }

.rv-part-head p {
  font-size: 11.5px;
  color: var(--fg-3);
  margin: 2px 0 0;
}

/* وزرّ الحفظ في طرف جزئه لا في قدم البطاقة: البطاقة قدمُها للقرار في الطلب،
   وزرُّ حفظِ رقمٍ يقف هناك يُقرأ اعتماداً.
   The save button sits at the end of its own part rather than in the card's
   foot: the card's foot belongs to the verdict, and a button that saves a number
   standing there reads as an approval. */
.rv-part-foot {
  display: flex;
  justify-content: flex-end;
  padding-block-start: 12px;
}

/* حقائق الطلب: جدولٌ من عمودين — اسمٌ وقيمة، ولا ثالث.
   -------------------------------------------------------------------------
   وكانت شبكةَ مربّعاتٍ تتراصّ بحسب عرض الصفحة: صفٌّ فيه أربعة ثم صفٌّ فيه واحد
   ثم صفٌّ فيه ثلاثة، والفراغُ يقع حيث انتهى العدد لا حيث انتهى المعنى. فلا
   عمودَ يُنزل النظرُ فيه، ولا اسمان يقعان تحت بعضهما ليُقارَنا — وكل تغييرٍ في
   عرض النافذة يعيد ترتيب البطاقة على غير ما رآها صاحبها قبل قليل.

   والجدول ثابت: ‎table-layout: fixed‎ وعمودُ الأسماء بنسبةٍ واحدة، فتنزل الأسماء
   على خطٍّ واحد وتنزل القيم على خطٍّ واحد في كل المجموعات — وهو الغرض كلّه.

   The request's facts: a table of two columns — a name and a value, no third.

   It was a grid of tiles packing themselves by the page's width: a row of four,
   then a row of one, then a row of three, with the gap falling where the count
   ran out rather than where the meaning did. No column for the eye to run down,
   no two names beneath one another to be compared — and every change in the
   window's width rearranged the card into something other than what its reader
   saw a moment before.

   The table holds still: table-layout: fixed and one proportion for the name
   column, so the names fall on one line and the values on another across every
   group — which is the whole point. */
/* والجدول جدولان متجاوران: نصفُ المعلومات في ناحية ونصفُها في الأخرى.
   -------------------------------------------------------------------------
   وعشرون حقيقةً في عمودٍ واحد بطاقةٌ بطول شاشتين، يُنزَل فيها ويُصعَد لتُقارَن
   أوّلُها بآخرها، وتُدفَع البطاقةُ التي بعدها إلى ما لا يُرى.

   و‎auto-fit‎ لا وسيطُ عرضٍ ولا استعلامُ شاشة: البطاقة تضيق حين يُفتح عارضُ
   الوثائق إلى جانبها — والشاشة كما هي — فيقيس ‎minmax‎ ما بقي للبطاقة نفسها
   فينزل النصفان تحت بعضهما. والطفلان اثنان فلا تصير ثلاثة أعمدة أبداً.

   The table is two tables side by side: half the information on one side and
   half on the other.

   Twenty facts in one column is a card two screens tall, scrolled down and up to
   compare its first line against its last, pushing the card after it out of
   sight.

   والبطاقة حاويةُ استعلامٍ لنفسها لا تقيس بالشاشة: هي تضيق حين يُفتح عارضُ
   الوثائق إلى جانبها والشاشةُ كما هي، فلو كان الانكسار على عرض النافذة لبقي
   النصفان متجاورَين في عمودٍ لا يسعهما.

   The card is a query container to itself rather than measuring by the screen: it
   narrows when the document viewer opens beside it with the screen unchanged, so
   a breakpoint on the window's width would leave the two halves side by side in a
   column with no room for them. */
.rv-facts .card-body { container-type: inline-size; }

.fact-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: 28px;
  min-width: 0;
}

/* والنصفان يمتدّان إلى ارتفاع أطولهما — ‎stretch‎ لا ‎start‎ — ليبلغ الخطُّ
   الفاصل قاع البطاقة. ولو وقف كلٌّ عند محتواه لانقطع الفاصل حيث انتهى النصف
   الأقصر، فيُقرأ خطّاً تحت شيءٍ لا فاصلاً بين نصفين.
   The two halves stretch to the height of the taller — stretch rather than start
   — so the dividing rule reaches the card's floor. Were each to stop at its own
   content, the divider would break off where the shorter half ended and read as a
   rule under something rather than a division between two halves. */
.fact-col { min-width: 0; }

/* وخطٌّ يفصل النصفين — وهو أفقيٌّ حين ينزل أحدهما تحت الآخر ورأسيٌّ حين
   يتجاوران. والخطّ واحد مرسومٌ على الثاني: لو رُسم على الحاوية في المنتصف لبقي
   قائماً حين لا منتصف، ولو رُسم على كليهما لصار خطّين متلاصقين.
   A rule dividing the two halves — horizontal while one sits beneath the other,
   vertical while they stand side by side. It is one rule drawn on the second: on
   the container down the middle it would stand where there is no middle, and on
   both of them it would be two rules touching. */
.fact-col + .fact-col {
  margin-block-start: 16px;
  padding-block-start: 16px;
  border-block-start: 1px solid var(--line);
}

@container (min-width: 560px) {
  .fact-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* والحدُّ منطقيّ لا يمينيّ: في العربية يقع على يمين النصف الثاني وفي
     الإنجليزية على يساره — وهو في الحالتين الوجه المقابل للنصف الأول.
     The edge is logical rather than right-handed: in Arabic it falls on the
     second half's right and in English on its left — in both the face turned
     towards the first half. */
  .fact-col + .fact-col {
    margin-block-start: 0;
    padding-block-start: 0;
    border-block-start: 0;
    border-inline-start: 1px solid var(--line);
    padding-inline-start: 28px;
  }
}

.fact-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: 13px;
  min-width: 0;
}

.fact-table th,
.fact-table td {
  text-align: start;
  vertical-align: baseline;
  padding: 9px 0;
  border-block-end: 1px solid var(--line-soft);
  min-width: 0;
  overflow-wrap: anywhere;
}

/* واسم الحقيقة خافتٌ وعمودُه أضيق: هو ما يُبحث به عن السطر، والقيمةُ هي ما
   يُقرأ منه.
   The fact's name is faint and its column narrower: it is what the row is found
   by, and the value is what is read off it. */
.fact-table th[scope="row"] {
  width: 38%;
  font-size: 12px;
  font-weight: 450;
  color: var(--fg-3);
}

.fact-table th[scope="row"] > span {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}

.fact-table th[scope="row"] .ico { flex: none; opacity: .7; }

.fact-table td { font-weight: 550; }

/* والسعر أظهر من جيرانه بلا صندوقٍ حوله: هو الرقم الذي يُقارَن بما حوله، ويكفيه
   حجمُه.
   The price stands out from its neighbours with no box around it: it is the
   figure compared against its surroundings, and its size is enough. */
.fact-table tr.lead td { font-size: 16px; font-weight: 650; }

/* ورأس المجموعة يمتدّ على العمودين، وفوقه فراغٌ يفصله عمّا قبله وخطٌّ لا يقع
   تحته: هو عنوانٌ لما يليه لا صفٌّ من الجدول.
   The group's heading spans both columns, with space above separating it from
   what came before and no rule beneath it: it is a title over what follows
   rather than a row of the table. */
.fact-table tr.fact-section th {
  padding-block: 18px 7px;
  border-block-end: 0;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--fg-2);
}

.fact-table tr:first-child th { padding-block-start: 0; }

/* ولا خطَّ تحت آخر صفّ: حدُّ البطاقة يقوله.
   No rule under the last row: the card's own border says it. */
.fact-table tr:last-child th,
.fact-table tr:last-child td { border-block-end: 0; }

/* والأوراق في جزءٍ من بطاقة لا في بطاقةٍ لها.
   وكانت تسكن الصندوق وحدها فتحمل حشوته بنفسها؛ وهي الآن في جسمٍ له حشوته، فلو
   بقيت حشوتها لانحسر صفُّ الورقة عن حافّتين ووقف أضيقَ من الجزء الذي تحته.
   The papers sit inside a part of a card rather than a card of their own. They
   used to hold the box alone and carried its padding themselves; they are in a
   body with its own padding now, and keeping theirs would inset the paper's row
   from two edges and stand it narrower than the part beneath it. */
.rv-part .rv-papers { padding: 0; }

/* الوجهة: بوابةٌ واحدة تُقرأ بنظرة، وتحتها ما يقع تحته.
   -------------------------------------------------------------------------
   والبطاقة أوّل الصفحة عمداً، وأجزاؤها تتباعد من القاعدة لا من سمةٍ في القالب:
   التحذيرات ثلاثة يظهر منها ما يظهر، وهامشٌ مكتوبٌ على واحدٍ منها يبقى معلّقاً
   حين يغيب ما بعده — وهي حجّة ‎.rv-decide‎ نفسها.

   The destination: one portal read at a glance, with what it falls under
   beneath it.

   The card heads the page deliberately, and its parts space from the rule rather
   than from an attribute in the template: there are three warnings of which
   whichever show show, and a margin written on one of them hangs there when what
   followed it is gone — the same argument as .rv-decide's. */
.rv-where .card-body > * + * { margin-block-start: 12px; }

.rv-where-portal {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-width: 0;
}

.rv-where-portal .t { min-width: 0; flex: 1 1 auto; }
.rv-where-portal .t b { display: block; font-size: 13.5px; }
.rv-where-portal .t .sub { font-size: 11.5px; overflow-wrap: anywhere; }

/* والرسم في مربّعٍ له أرضيّة: البوابة هي ما يُميَّز في هذه البطاقة، ورسمٌ عارٍ
   بحجم النصّ لا يُميَّز عنه.
   The drawing sits in a square with a ground of its own: the portal is what this
   card distinguishes, and a bare mark at the text's size is not distinguished
   from it. */
.rv-where-icon {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex: none;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--fg-1);
}

.rv-where-portal .btn { margin-inline-start: auto; }

.rv-where-marks {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}

.rv-where-marks .sub { font-size: 11.5px; }

/* المعرض: صورةٌ واحدة بحجمٍ يُراجَع به، وشريطٌ تحتها.
   -------------------------------------------------------------------------
   والصورة تُعرض بكاملها لا مقصوصة: ‎object-fit: contain‎. فالمقصوصة تخفي طرف
   الغرفة الذي قد يكون هو سبب الردّ — والمراجعة نظرٌ في ما رُفع لا في ما يليق
   بالمستطيل.

   The gallery: one photograph at a size it can be reviewed at, with a strip
   beneath it.

   The photograph is shown whole rather than cropped: object-fit: contain. A crop
   hides the corner of the room that may be the very reason for a refusal — and
   reviewing is looking at what was uploaded rather than at what suits a
   rectangle. */
.rv-stage {
  display: grid;
  place-items: center;
  position: relative;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  min-height: 260px;
}

.rv-stage img {
  display: block;
  width: 100%;
  max-height: 62vh;
  object-fit: contain;
}

.rv-stage figcaption {
  position: absolute;
  inset-block-end: 8px;
  inset-inline-end: 8px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  color: var(--fg-1);
  background: var(--surface);
  border: 1px solid var(--line);
}

/* والشريط يُمرَّر أفقيّاً ولا يلتفّ: صفٌّ واحد يُقرأ بنظرة، وعشرون صورةً في
   أربعة صفوف تدفع كل ما تحتها خارج الشاشة.
   The strip scrolls sideways rather than wrapping: one row is taken in at a
   glance, where twenty photographs over four rows push everything beneath them
   off the screen. */
.rv-strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-block: 10px 2px;
  scrollbar-width: thin;
}

.rv-shot {
  flex: none;
  width: 84px;
  height: 60px;
  border-radius: var(--r-sm);
  overflow: hidden;
  border: 1px solid var(--line);
  opacity: .55;
  transition: opacity .12s ease;
}

.rv-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.rv-shot:hover { opacity: .85; }

/* والمعروضة منها تُعلَّم بحدٍّ لا بلون: النظام بلا ألوان.
   The one on show is marked by a rule rather than a colour: the system has no
   colours. */
.rv-shot.is-open { opacity: 1; border-color: var(--fg-1); box-shadow: 0 0 0 1px var(--fg-1); }

.rv-clip {
  display: block;
  width: 100%;
  max-height: 62vh;
  margin-block-start: 12px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: #000;
}

/* الأوراق: عمودان — الورقة وما قيل فيها، وما يُفعل بها.
   -------------------------------------------------------------------------
   وكانت ثلاثة: الاسم، ثم الحال، ثم الأفعال. والحالُ في أكثر الأوراق شارةٌ واحدة،
   فيقف عمودٌ كامل لأجلها بين الشارة وزرّها — تُقرأ «مقبولة» في ناحيةٍ ويُضغط
   «عرض» في ناحية، والاسمُ إلى جانبهما مقصوصٌ لضيق عموده. فصارت الشارة في صفّ
   الأفعال، والمرجعُ والسببُ سطرين تحت الاسم.

   The papers: two columns — the paper with what was said of it, and what is done
   with it.

   There were three: the name, then the standing, then the actions. On most
   papers the standing is a single badge, so a whole column stood for it between
   the badge and its own button — "approved" read on one side and "show" pressed
   on the other, with the name beside them clipped for want of room. The badge
   now sits in the row of actions, and the reference and the reason are two lines
   under the name.

   والقرار عمودٌ ثابت لا مرن: ‎.decision‎ لها عرضٌ أدنى مئتان وخمسون، وعمودٌ مرن
   حولها يجعل موضع الزرّ يتبع طول اسم الوثيقة — فيقف «اعتماد» في صكّ الملكية
   موضعاً وفي تصريح الإيجار موضعاً آخر، والورقتان تحت طلبٍ واحد.

   The papers: three columns — the paper, how it stands, and what is done with
   it.

   The decision is a fixed column rather than a flexible one: it is measured in
   pixels here, so it is the same width on every paper under one request — where
   an "auto" column is measured by what happens to be inside it, and a settled
   paper (no forms, two words) collapses it while the paper beside it holds it
   open. The button's place then follows the length of a document's name: it
   stands one place on a title deed and another on a rental permit, the two rows
   being under the same heading. */
.rv-papers {
  display: grid;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-5);
  min-width: 0;
}

.rv-paper {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 250px;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-1);
  min-width: 0;
}

/* واسم الملفّ يأخذ عموده: السقف مئتان وعشرون لأنه كُتب لخليّة جدول، وهذا عمودٌ
   أوسع منها — فيُقصّ الاسم وثلث العمود فارغ.
   The file's name takes its column: the ceiling of two hundred and twenty was
   written for a table cell, and this column is wider than one — so the name was
   clipped with a third of the column standing empty. */
.rv-paper .doc-cell .doc-file { max-width: 100%; }

/* والمعروضة في العارض تُعلَّم بحدٍّ على طرفها، كما في قائمة الانتظار.
   وهو حدٌّ داخليّ لا حدُّ الصندوق: زيادة الحدّ من واحدٍ إلى ثلاثة تزيح ما في
   الصفّ بكسلين، فتقف الأوراق غيرَ محاذيةٍ بعضها لبعض.
   The one on show in the viewer is marked by a rule on its edge, as in the
   waiting list. It is an inner rule rather than the box's own: taking the border
   from one to three shifts the row's contents by two pixels, leaving the papers
   out of line with one another. */
.rv-paper.is-open {
  border-inline-start: 3px solid var(--fg-1);
  padding-inline-start: calc(var(--sp-3) - 2px);
  background: var(--surface-2);
}

/* الوثيقة وما قيل فيها أسطرٌ في عمودٍ واحد: الاسم، ثم المرجع، ثم السبب.
   The document and what was said of it are lines in one column: the name, then
   the reference, then the reason. */
.rv-paper-doc {
  display: grid;
  gap: 5px;
  min-width: 0;
}

/* والمرجع والسبب سطران لا صفّ: رقمٌ لاتيني ثم سطرُ سببٍ قد يبلغ خمسمئة حرف،
   في صفٍّ واحد بفراغٍ واحد بينهما، يُقرآن سلسلةً لا يُعرف أين ينتهي أحدهما
   ويبدأ الآخر.
   The reference and the reason are two lines rather than a row: a Latin number
   then a reason that may run five hundred characters, on one line with one gap
   between them, read as a string with no telling where one ends and the next
   begins. */
.rv-paper-state {
  display: grid;
  gap: 5px;
  justify-items: start;
  align-content: center;
  min-width: 0;
}

.rv-paper-by {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 11.5px;
  color: var(--fg-3);
  min-width: 0;
}

/* ونقطةٌ بين الاسم والمرجع: بلا فاصلٍ يُقرأ الاثنان اسماً واحداً.
   A point between the name and the reference: with no separator the two read as
   one name. */
.rv-paper-by > * + *::before { content: "·"; margin-inline-end: 6px; }

.rv-paper-why {
  font-size: 11.5px;
  color: var(--fg-3);
  margin: 0;
  overflow-wrap: anywhere;
}

.rv-paper-act {
  display: grid;
  gap: 6px;
  justify-items: stretch;
  align-content: center;
  min-width: 0;
}

/* والشارة والزرّ صفٌّ واحد: حالُ الورقة وما يُفعل بها يُقرآن في موضعٍ واحد،
   والشارة أوّلاً لأنها هي التي تقول لماذا لا زرَّ قرارٍ بعدها.
   The badge and the button are one row: the paper's standing and what is done
   about it are read in one place, the badge first because it is what says why no
   decision button follows it. */
.rv-paper-do {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  min-width: 0;
}

/* وزرّ «عرض» بمقاسه لا بمقاس عمود القرار.
   -------------------------------------------------------------------------
   وكان ‎stretch‎ يمطّه على العمود كلّه: صندوقٌ عرضه مئتان وخمسون فيه أيقونةٌ
   وكلمة، وهو أعرض من زرّ «اعتماد ونشر» في أسفل الصفحة نفسها. والنموذجان وحدهما
   يحتاجان العرض كلّه — الحقلُ فيهما هو ما يتمدّد.

   The "show" button is of its own measure rather than the decision column's.

   stretch was blowing it up to the whole column: a box two hundred and fifty
   wide holding an icon and one word, wider than the "approve and publish" button
   at the foot of the same page. Only the two forms need the full width — the
   field inside them is what stretches. */
.rv-paper-do .btn { min-width: 84px; }

/* والقرار لا يفرض عرضاً هنا: العمود مقطوعٌ بمقاسه أعلاه، والأرضية المئتان
   والخمسون إنما كُتبت لخليّة جدولٍ لا عمود لها.
   The decision imposes no width here: the column is cut to measure above, and
   the floor of two hundred and fifty was written for a table cell that has no
   column of its own. */
.rv-paper-act .decision { min-width: 0; }

/* وسطرُ «مقبولة وسارية» يقول لماذا لا زرَّ هنا — فيُقرأ شرحاً بمقاس الشرح.
   The "settled and in date" line says why there is no button here — so it reads
   as an explanation, at an explanation's size. */
.rv-paper-act .decision .sub { font-size: 11.5px; }

/* ما ينقص قبل القرار: سطرٌ واحد يجيب عن «لماذا لا يُعتمد هذا».
   What is missing before the decision: one line answering "why can this not be
   approved". */
.rv-marks {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}

/* القرار القائم: ما هو عليه الآن، ولماذا.
   وهو أوّل ما في البطاقة حين يكون ثمّة قرار — يُقرأ قبل ما يُعرض من أفعال، لأنه
   هو الذي يفسّر لماذا صار المعروض فعلاً واحداً لا اثنين.
   The standing decision: what it is at now, and why. It heads the card wherever
   there is one — read before what is offered, because it is what explains why
   one action is offered rather than two. */
.rv-standing {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  min-width: 0;
}

.rv-standing .sub { font-size: 11.5px; overflow-wrap: anywhere; }

/* والفراغ بين أجزاء البطاقة من القاعدة لا من سمةٍ في القالب: الأجزاء ثلاثة
   يظهر منها اثنان أو واحد بحسب الحال، وهامشٌ مكتوبٌ على واحدٍ منها يبقى معلّقاً
   حين يغيب ما بعده.
   The space between the card's parts comes from the rule rather than an
   attribute in the template: there are three parts, of which two or one show
   depending on the case, and a margin written on one of them hangs there when
   what followed it is gone. */
.rv-decide .card-body > * + * { margin-block-start: 12px; }

/* وزرّا القرار في الطاولة بمقاسٍ عاديّ لا صغير: هما آخر ما يُضغط في الصفحة،
   وليسا إجراءً في صفّ.
   -------------------------------------------------------------------------
   والقاعدتان على الابن المباشر لا على النسل: القرارُ في الطلب ابنُ جسم البطاقة،
   والقرارُ في كل ورقةٍ يقع في ‎.rv-paper-act‎ داخل جزء الأوراق. فلمّا جُمعت
   الأوراق والقرار في بطاقةٍ واحدة صار نسلُ ‎.rv-decide‎ يشمل الاثنين، فأخذ زرُّ
   «اعتماد» في صفّ ورقةٍ أرضيّةَ مئةٍ واثنين وثلاثين والصندوقُ أرضيّةَ مئتين
   وثمانين — وعمود الصفّ مئتان وخمسون. فخرجت الأزرار من إطار الورقة سبعة عشر
   بكسلاً.

   وأرضيّةُ عرضٍ لا تُورَّث: هي مقاسٌ لموضعٍ بعينه، وكلُّ ما يشبهه في نسله يرثها
   ولو كان في صفٍّ عرضُه أقلّ منها.

   The desk's two decision buttons are of the ordinary size rather than the small
   one: they are the last thing pressed on the page rather than an action in a
   row.

   Both rules match a direct child rather than a descendant: the verdict on the
   request is a child of the card's body, while the decision on each paper sits in
   .rv-paper-act inside the papers part. Once the papers and the verdict were
   gathered into one card, .rv-decide's descendants covered both — so the
   "approve" button in a paper's row took a floor of a hundred and thirty-two and
   its box a floor of two hundred and eighty, against a row column of two hundred
   and fifty. The buttons broke seventeen pixels out of the paper's frame.

   A width floor is not to be inherited: it is a measure for one place, and
   everything resembling it further down inherits it even in a row narrower than
   the floor itself. */
.rv-decide > .card-body > .decision { min-width: 280px; max-width: 460px; }
.rv-decide > .card-body > .decision .btn { min-width: 132px; }

@media (max-width: 900px) {
  .rv-paper { grid-template-columns: minmax(0, 1fr); }
  .rv-papers { padding: var(--sp-3); }
}

@container dossier (max-width: 880px) {
  .rv-paper { grid-template-columns: minmax(0, 1fr); }
}
