/* ============================================================================
   CONSIA — the space layer.
   Owner brief, 14 Aug 2026: bento grids, scroll storytelling, kinetic type,
   light 3D, dark mode by default, variable fonts, speed, strategic minimalism —
   «أريد من المستخدم ان يتصفح المنصة كانها من الفضاء» — and, twice: without
   touching the project's work or its colours.

   HOW THIS OBEYS BOTH HALVES OF THAT.

   Everything here is TOKENS, not components. style.css already draws every
   button, card, table and form from --canvas / --surface / --ink / --border, so
   redefining those six values turns the whole platform — 1168 pages — dark at
   once, without editing a single component and without a single template
   knowing it happened. Nothing below overrides a component rule; if it did, the
   next component someone writes would be the one that stayed light.

   AND THE GREEN IS THE GREEN. #0B6E4F is the identity and it is untouched; it
   still fills buttons and marks. What it cannot do is carry TEXT on a near
   black canvas — that lands around 2:1 against WCAG's 4.5:1, i.e. unreadable,
   not merely dim. So a lit sibling of the same hue (--brand-lit, #2FD39A) does
   the reading, and the original does the filling. Same colour family, same
   brand, legible in the dark.

   SPEED IS PART OF THE DESIGN, NOT A TAX ON IT. No library, no canvas, no
   WebGL runtime: the starfield is three tiled radial-gradients on one fixed
   pseudo-element, the depth is CSS transforms, and the whole file gzips to a
   few KB. A 3D scene that costs a second of load on an Algerian 3G phone is a
   worse design than no 3D scene.

   AND IT ALL STOPS FOR prefers-reduced-motion. Vestibular disorders are not an
   edge case, and a moving starfield is exactly the trigger.
   ========================================================================= */

/* ---------------------------------------------------------------- palette */

/* THE TOKENS MOVED (17 Aug 2026, owner Q17 «مصدر واحد للألوان»).
   Every custom property that used to be declared here now lives in
   shared/design_tokens.py and is emitted into static/css/tokens.css, which
   base.html loads BEFORE this file. The desktop program imports the same
   module, so one edit changes both surfaces and neither can drift from the
   other by accident — which is exactly how the two canvases ended up
   #FAFBFC and #F6F4EE without anyone deciding they should differ.

   This file keeps what it was always for: the rules that USE the tokens. */

/* The cream original is not deleted — it is one attribute away, and pages that
   are printed or read in daylight still have somewhere to go. */
/* The day palette moved too — see tokens.css. */

/* --------------------------------------------------------- the sky itself */

/* Fixed, behind everything, painted once. Attached to body::before rather than
   to body's own background so scrolling never repaints a gradient — the single
   most common way a "starfield" becomes a scroll-jank machine. */
body::before {
    content: "";
    position: fixed;
    inset: -10vmax;
    z-index: -2;
    pointer-events: none;
    background:
        /* two nebulae in the brand hue, low and wide */
        radial-gradient(60vmax 45vmax at 78% -10%, rgba(11, 110, 79, 0.30), transparent 62%),
        radial-gradient(52vmax 40vmax at 8% 108%, rgba(47, 211, 154, 0.13), transparent 60%),
        /* three star layers at different densities = parallax without JS */
        radial-gradient(1.4px 1.4px at 12% 22%, rgba(255,255,255,.75), transparent 50%),
        radial-gradient(1.2px 1.2px at 68% 14%, rgba(255,255,255,.55), transparent 50%),
        radial-gradient(1.6px 1.6px at 41% 71%, rgba(255,255,255,.65), transparent 50%),
        radial-gradient(1px 1px at 88% 58%, rgba(255,255,255,.45), transparent 50%),
        radial-gradient(1.1px 1.1px at 26% 88%, rgba(255,255,255,.50), transparent 50%),
        var(--canvas);
    background-size: 100% 100%, 100% 100%, 320px 320px, 460px 460px, 280px 280px, 540px 540px, 380px 380px, auto;
    animation: drift 240s linear infinite;
}

:root[data-theme="light"] body::before { animation: none; opacity: 0; }

@keyframes drift {
    to { background-position: 0 0, 0 0, 320px 320px, -460px 460px, 280px -280px, -540px -540px, 380px 380px, 0 0; }
}

/* The horizon grid — a surveyor's lattice receding to a vanishing point. It is
   the one place the space metaphor and the trade meet: this is a construction
   platform, and the ground under the stars should read as a site grid. */
body::after {
    content: "";
    position: fixed;
    inset: auto 0 0 0;
    height: 42vh;
    z-index: -1;
    pointer-events: none;
    opacity: .5;
    background:
        linear-gradient(to top, var(--canvas) 4%, transparent 60%),
        repeating-linear-gradient(to right, var(--lattice) 0 1px, transparent 1px 6vmin),
        repeating-linear-gradient(to top, var(--lattice) 0 1px, transparent 1px 6vmin);
    transform: perspective(60vmin) rotateX(72deg);
    transform-origin: bottom center;
}

:root[data-theme="light"] body::after { opacity: .25; }

body { background: transparent; }

/* -------------------------------------------------------------- typography */

/* Variable axes: ONE file per family instead of four static weights, which is
   both the modern practice and, on a phone in Sétif, three fewer round trips. */

/* ---------------------------------------------------------------- kinetic */

/* Kinetic type that MEANS something: the hero name arrives the way a structure
   is raised — letter by letter, from below, settling into place. Motion as
   information, not decoration. */
.kinetic {
    display: inline-block;
    font-size: var(--step-hero);
    line-height: .95;
    letter-spacing: -.04em;
    font-weight: 700;
    /* THE BRAND SPELLED BACKWARDS. Splitting the wordmark into per-letter spans
     * made each letter an inline box — and on the Arabic pages, where the
     * document runs rtl, inline boxes flow right-to-left. The owner's own
     * screenshot read «AISNOC». A Latin wordmark has one direction regardless
     * of the page around it. */
    direction: ltr;
    unicode-bidi: isolate;
}

.kinetic > span { display: inline-block; }

/* Same rule as the scroll reveal, and for a sharper reason: this is the H1. A
 * headline that starts at opacity 0 and waits for a CSS animation is invisible
 * anywhere animations do not run — and a brand name nobody can see is not a
 * subtle entrance, it is a blank page. So the letters are visible until the
 * script says otherwise, and the same three-second failsafe puts them back. */
html.reveal-armed .kinetic > span {
    opacity: 0;
    transform: translateY(.5em) rotateX(40deg);
    animation: rise .8s cubic-bezier(.2, .7, .2, 1) forwards;
    animation-delay: calc(var(--i, 0) * 60ms);
}

@keyframes rise {
    to { opacity: 1; transform: none; }
}

/* A slow shimmer across the mark, like light moving over glass. */
.kinetic-sheen {
    background: linear-gradient(100deg, var(--ink) 30%, var(--brand-lit) 50%, var(--ink) 70%);
    background-size: 300% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: sheen 9s ease-in-out infinite;
}

@keyframes sheen {
    0%, 100% { background-position: 100% 0; }
    50% { background-position: 0 0; }
}

/* ------------------------------------------------------- scroll narrative */

/* The storytelling mechanic. Sections do not appear all at once; each earns its
   moment as the reader reaches it. One IntersectionObserver, no scroll handler,
   nothing that runs on every frame. */
/* NOTHING IS HIDDEN UNTIL THE SCRIPT PROVES IT CAN UNHIDE IT.
 *
 * The obvious way round — `.reveal { opacity: 0 }` in the stylesheet, revealed
 * by an observer — was written first and caught in the browser: the whole page
 * rendered blank tiles because IntersectionObserver does not fire in a
 * non-compositing tab. That tab was an artifact of my own tooling, but the
 * failure it exposed is not: an observer also stays silent behind some
 * extensions, in a restored background tab, and any time a JS error higher up
 * the file kills the script before this line.
 *
 * So the hiding is ARMED BY THE SCRIPT ITSELF (html.reveal-armed, set from
 * app.js immediately before the observer is created) and lifted by a failsafe
 * timer if anything goes quiet. CSS alone can never hide the product.
 */
html.reveal-armed .reveal {
    opacity: 0;
    transform: translateY(26px);
    transition: opacity .7s cubic-bezier(.2,.7,.2,1), transform .7s cubic-bezier(.2,.7,.2,1);
    transition-delay: var(--reveal-delay, 0ms);
}

html.reveal-armed .reveal.is-in { opacity: 1; transform: none; }

/* ------------------------------------------------------------ bento grid */

/* Asymmetric on purpose: equal tiles say every item matters equally, which is
   never true. The big tile is the thing we want read first. */
.bento {
    display: grid;
    gap: 14px;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}

.bento-2 { grid-column: span 2; }
.bento-tall { grid-row: span 2; }

@media (max-width: 720px) {
    .bento-2, .bento-tall { grid-column: auto; grid-row: auto; }
}

.bento-item {
    position: relative;
    background:
        linear-gradient(180deg, rgba(255,255,255,.035), transparent 55%),
        var(--surface);
    border: 1px solid var(--border);
    border-radius: 18px;
    padding: 22px;
    overflow: hidden;
    transition: transform .35s cubic-bezier(.2,.7,.2,1), border-color .35s, box-shadow .35s;
}

.bento-item::after {
    /* A light that follows the card, so a grid of panels reads as lit glass
       rather than as flat rectangles. */
    content: "";
    position: absolute;
    inset: -1px;
    border-radius: inherit;
    pointer-events: none;
    opacity: 0;
    transition: opacity .35s;
    background: radial-gradient(30vmax 20vmax at var(--mx, 50%) var(--my, 0%),
                rgba(47, 211, 154, .12), transparent 60%);
}

.bento-item:hover {
    transform: translateY(-4px);
    border-color: rgba(47, 211, 154, .35);
    box-shadow: var(--glow);
}

.bento-item:hover::after { opacity: 1; }

.bento-item h3 {
    margin: 0 0 6px;
    font-size: 1.05rem;
    color: var(--ink);
}

.bento-item p { margin: 0; color: var(--muted); line-height: 1.7; }

.bento-num {
    font-size: clamp(2rem, 5vw, 3.2rem);
    font-weight: 700;
    line-height: 1;
    color: var(--brand-lit);
    font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------------- depth/3D */

/* "Light 3D" done with transforms rather than a renderer: the tile tips toward
   the pointer. Costs nothing, ships nothing, and is disabled where there is no
   pointer at all — a phone cannot hover and should not pay for the code. */
@media (hover: hover) and (pointer: fine) {
    .tilt { transform-style: preserve-3d; will-change: transform; }
    .tilt > * { transform: translateZ(18px); }
}

/* The orbit: a slow ring behind a focal element, one element, one animation. */
.orbit {
    position: relative;
}

.orbit::before {
    content: "";
    position: absolute;
    inset: -12%;
    border-radius: 50%;
    border: 1px solid rgba(47, 211, 154, .18);
    animation: spin 44s linear infinite;
    pointer-events: none;
}

.orbit::after {
    content: "";
    position: absolute;
    inset: -12%;
    border-radius: 50%;
    border: 1px dashed rgba(47, 211, 154, .10);
    animation: spin 70s linear infinite reverse;
    pointer-events: none;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------- images */

.art-frame {
    position: relative;
    border-radius: 20px;
    overflow: hidden;
    border: 1px solid var(--border);
    background: var(--surface);
}

.art-frame img, .art-frame svg { display: block; width: 100%; height: auto; }

/* Photographs shot in daylight sit badly on a night canvas. A whisper of the
   canvas colour over the image ties it to the page without dimming the work. */
.art-frame::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(180deg, transparent 40%, rgba(4, 16, 11, .55));
    mix-blend-mode: multiply;
}

:root[data-theme="light"] .art-frame::after { opacity: .25; }

.art-caption {
    position: absolute;
    inset: auto 0 0 0;
    z-index: 1;
    padding: 14px 18px;
    color: #EAF6F0;
    font-size: .92rem;
}

/* ----------------------------------------------------------- theme switch */

/* «أيضا THEME اعطني خيار تغييره من ليلي الى نهاري» — owner, 14 Aug 2026.
 *
 * Both palettes already existed in this file; what was missing was the handle.
 * It sits in the top bar beside the language switcher because it is the same
 * kind of control — a preference about how the page is read, not a feature —
 * and someone looking for one will look where the other is.
 *
 * One button, not a three-way menu. "Follow the system" is the honest third
 * option and it is also the one nobody uses deliberately; the two states a
 * person actually wants are night and day, and the button says which one it
 * will give you next. */
.theme-toggle {
    display: inline-grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
    padding: 0;
    transition: border-color .25s, color .25s, transform .25s;
}

.theme-toggle:hover {
    border-color: var(--brand-lit);
    color: var(--brand-lit);
    transform: translateY(-1px);
}

.theme-toggle .icon { width: 18px; height: 18px; }

/* The sun and the moon swap by theme, so the glyph is always the state you are
   about to GET — a moon while you are in daylight. A control that shows the
   state you are already in is a control people press twice. */
.theme-toggle .theme-sun { display: none; }
:root[data-theme="light"] .theme-toggle .theme-sun { display: block; }
:root[data-theme="light"] .theme-toggle .theme-moon { display: none; }

/* Day mode still gets the site grid and the stars go quiet — the space
   metaphor is a night one, and forcing it into daylight makes both look
   wrong. */
:root[data-theme="light"] body { background: var(--canvas); }

/* ---------------------------------------------------------------- chrome */

/* THE ONE SURFACE THE TOKENS COULD NOT REACH. .navbar in style.css is
 * `background: rgba(250, 251, 252, 0.92)` — a literal, not a token — so it
 * stayed cream while the other 1168 pages went dark, and it is the FIRST thing
 * on every one of them. Invisible to every measurement I ran and obvious the
 * second a screenshot existed: contrast ratios were read against the page, and
 * the page was never the problem.
 *
 * Overridden here rather than edited there, so the light theme below still gets
 * its cream bar back from one place. */
.navbar {
    background: color-mix(in srgb, var(--surface) 82%, transparent);
    border-bottom-color: var(--border);
}

:root[data-theme="light"] .navbar { background: rgba(250, 251, 252, 0.92); }

/* -------------------------------------------------------- the scroll story */

/* A horizontal strip of real work that the reader drags — or flicks, on the
 * phone where most of these visitors are. scroll-snap makes it land on a
 * photograph instead of stopping halfway across two, which is the whole
 * difference between a gallery and a mess.
 *
 * CSS-only: no carousel library, no autoplay, no dots. A carousel that moves by
 * itself is a carousel that moves away from what someone was reading. */
.scrollytell {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: min(78vw, 460px);
    gap: 16px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    padding: 6px 2px 20px;
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
}

.scrollytell-shot {
    position: relative;
    margin: 0;
    scroll-snap-align: center;
    border-radius: 18px;
    overflow: hidden;
    border: 1px solid var(--border);
    background: var(--surface);
    aspect-ratio: 4 / 3;
}

.scrollytell-shot img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    /* Daylight photographs sit badly on a night canvas. A small pull toward the
       page's own darkness ties them to it without dulling the work itself. */
    filter: saturate(.92) brightness(.86) contrast(1.06);
    transform: scale(1.02);
    transition: transform .8s cubic-bezier(.2,.7,.2,1), filter .5s;
}

.scrollytell-shot:hover img { transform: scale(1.06); filter: none; }

.scrollytell-shot figcaption {
    position: absolute;
    inset: auto 0 0 0;
    padding: 34px 16px 14px;
    color: #EAF6F0;
    font-size: .95rem;
    background: linear-gradient(to top, rgba(4, 16, 11, .92), transparent);
}

/* The reader is told the strip moves, once, without a widget saying so. */
.scrollytell::after {
    content: "";
    flex: 0 0 2px;
}

/* ------------------------------------------------------------- icon hues */

/* «كل ايقونة بلونها كي لا يرى المستخدم نفس الالوان فتصبح مختلطة» — owner.
   A wall of thirty identical green glyphs is a wall, and the eye stops
   distinguishing them; colour is what makes a card findable at a glance on the
   second visit. The hue is assigned per tool in saas/icons.py and arrives here
   as --icon-hue, so the ring, the wash and the glyph always agree. */
.icon-chip {
    --icon-hue: 160;
    display: inline-grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: 12px;
    color: hsl(var(--icon-hue) 72% 62%);
    background: hsl(var(--icon-hue) 60% 50% / .12);
    border: 1px solid hsl(var(--icon-hue) 60% 55% / .28);
    transition: transform .3s cubic-bezier(.2,.7,.2,1), background .3s;
}

a:hover > .icon-chip, .card:hover .icon-chip, .bento-item:hover .icon-chip {
    transform: translateY(-2px) scale(1.04);
    background: hsl(var(--icon-hue) 60% 50% / .2);
}

/* THE DASHBOARD IS WHERE THIS MATTERS. The sidebar carries up to 39 entries and
 * the tool grid 36 cards; in one green they were a texture, not a list. Both
 * already have their own boxes in style.css, so only the colour is taken from
 * the hue — the geometry is left exactly where it was. */
.starter-card .starter-ic,
.app-nav-item .app-nav-ic {
    --icon-hue: 160;
    color: hsl(var(--icon-hue) 70% 60%);
    transition: color .25s, background .25s, border-color .25s;
}

.starter-card .starter-ic {
    background: hsl(var(--icon-hue) 60% 50% / .12);
    border: 1px solid hsl(var(--icon-hue) 60% 55% / .26);
}

.starter-card:hover .starter-ic {
    background: hsl(var(--icon-hue) 60% 50% / .2);
    border-color: hsl(var(--icon-hue) 65% 60% / .45);
}

/* A muted card is a tool that is not on the web yet, and its colour has to say
 * so before the click — the whole point of the badge beside it. */
.app-nav-item-muted .app-nav-ic { color: hsl(var(--icon-hue) 22% 52%); }

.app-nav-item.active .app-nav-ic { color: hsl(var(--icon-hue) 80% 66%); }

/* ----------------------------------------------------------------- plans */

/* THE PRICING GRID IS THE BENTO'S BEST ARGUMENT, so it gets it — but through
 * CSS alone, because half of these cards are built by plans.html's own script
 * at runtime. Restyling the markup would have meant editing that script, and a
 * design change has no business touching the code that decides what a customer
 * is charged.
 *
 * Equal columns say every plan is equally right for you, which is exactly the
 * thing a pricing page must not say. The featured plan takes more room, and the
 * asymmetry does the recommending that a "most popular" sticker usually has to.
 */
.plans-grid {
    display: grid;
    gap: 14px;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
    align-items: stretch;
}

.plan-card {
    position: relative;
    overflow: hidden;
    border-radius: 18px;
    border: 1px solid var(--border);
    background:
        linear-gradient(180deg, rgba(255,255,255,.035), transparent 55%),
        var(--surface);
    transition: transform .35s cubic-bezier(.2,.7,.2,1), border-color .35s, box-shadow .35s;
}

.plan-card::after {
    content: "";
    position: absolute;
    inset: -1px;
    border-radius: inherit;
    pointer-events: none;
    opacity: 0;
    transition: opacity .35s;
    background: radial-gradient(30vmax 20vmax at var(--mx, 50%) var(--my, 0%),
                rgba(47, 211, 154, .12), transparent 60%);
}

.plan-card:hover { transform: translateY(-4px); border-color: rgba(47,211,154,.35); box-shadow: var(--glow); }
.plan-card:hover::after { opacity: 1; }

/* THE RECOMMENDED PLAN IS MARKED WITH LIGHT, NOT WITH WIDTH — and the first
 * version of this rule was width, which was wrong for a measurable reason.
 * `featured` is not rare: the live page has three of five in the main grid and
 * two of three in the next. Widening most of the cards makes the emphasis mean
 * nothing and the rows ragged, and a pricing page that visually recommends
 * everything is recommending nothing.
 *
 * The ring follows whatever the plan config says, one card or five, and stays
 * honest at every count. Which plan is featured is a pricing decision and
 * belongs to the owner — a stylesheet must not quietly promote one by making
 * it twice as big. */
.plan-card.featured {
    border-color: rgba(47, 211, 154, .38);
    box-shadow: var(--glow);
}

/* A CENTRED PARAGRAPH IS NOT A READABLE PARAGRAPH. `.plan-card` is
 * `text-align: center` in style.css, which is right for the plan name and the
 * price and wrong for what sits under them: four to six full sentences about
 * quotas and limits, every line starting at a different x, with a bullet
 * hanging off to one side. In the French screenshot the ULTRA list reads as a
 * ragged column. The heading and the number stay centred; the reasons to buy
 * get a straight edge to read down.
 *
 * `start`, not `left`, so this is still correct in Arabic. */
.plan-card ul,
.plan-card li,
.plan-card .plan-features {
    text-align: start;
}

/* The number is the thing the eye came for. */
.plan-price {
    color: var(--brand-lit);
    font-variant-numeric: tabular-nums;
    letter-spacing: -.02em;
}

.plan-price span { color: var(--muted); font-variant-numeric: normal; }

.plan-badge {
    background: hsl(160 60% 50% / .16);
    color: var(--brand-lit);
    border: 1px solid hsl(160 60% 55% / .3);
}

/* ------------------------------------------------------------ living hero */

/* «اجعل هذه الصورة من الداخل تتحرك» — owner, 14 Aug 2026, pointing at the hero.
 * A slow drift-and-zoom inside a fixed frame (the film trade calls it Ken
 * Burns): the image travels, the frame does not. 36 seconds out and back —
 * slow enough that you FEEL it rather than watch it, which is the difference
 * between depth and a screensaver. GPU-composited transform only, so it costs
 * no layout and no paint. */
.hero-visual.has-image { overflow: hidden; }

.hero-visual.has-image img {
    animation: kenburns 36s ease-in-out infinite alternate;
    will-change: transform;
}

@keyframes kenburns {
    from { transform: scale(1.02) translate(0, 0); }
    to   { transform: scale(1.14) translate(-2.2%, -1.6%); }
}

/* ------------------------------------------------------- platform marquee */

/* «اضف اليه صور تسكرولي توحي بانها منصة قوية». The strongest picture of a
 * powerful platform is THE PLATFORM: real captures of its own screens, drifting
 * slowly across the page. Not stock, not renders — the owner rejected stock
 * photographs earlier today, and screenshots of the product cannot lie about
 * the product.
 *
 * The strip is duplicated once in the markup and translated -50%, so the loop
 * is seamless. It pauses under the pointer — drift is an invitation to look,
 * and the moment someone looks, the page must hold still for them. */
.marquee {
    overflow: hidden;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}

.marquee-track {
    display: flex;
    gap: 16px;
    width: max-content;
    animation: marquee 55s linear infinite;
}

.marquee:hover .marquee-track { animation-play-state: paused; }

@keyframes marquee {
    to { transform: translateX(-50%); }
}

/* RTL pages drift the other way — toward the reading direction, not against it. */
[dir="rtl"] .marquee-track { animation-name: marquee-rtl; }

@keyframes marquee-rtl {
    from { transform: translateX(-50%); }
    to   { transform: translateX(0); }
}

.marquee-shot {
    flex: 0 0 auto;
    width: min(72vw, 430px);
    aspect-ratio: 16 / 10;
    border-radius: 16px;
    overflow: hidden;
    border: 1px solid var(--border);
    background: var(--surface);
    box-shadow: var(--shadow);
}

.marquee-shot img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top;
    display: block;
}

/* ------------------------------------------------------------ bidi money */

/* «اخطاء الارقام في العربية» — owner, 14 Aug 2026, photographing «000 3 دج».
 * Algerian notation groups thousands with a SPACE, and rtl bidi treats the
 * digit runs on each side of that space as separate numbers, then lays them
 * out right-to-left. Every numeric table cell gets the isolate once, here;
 * prices inside prose use <bdi dir="ltr"> or LRI/PDI marks at the source. */
.data-table .num,
.plan-price bdi,
.dsc-tier-price bdi {
    direction: ltr;
    unicode-bidi: isolate;
}

/* ------------------------------------------------------------- the mark */

/* «بالنسبة للوغو CONSIA اجعلها انيميشن وتتحرك ايضا» — owner, 14 Aug 2026.
 * The mark breathes and bobs, always; a full turn is saved for the hover,
 * because chrome that spins uninvited is a fidget, and chrome that answers
 * the pointer is alive. Both shells share the treatment — one brand, one
 * heartbeat. */
.brand-mark,
.app-brand-mark {
    animation: mark-breathe 5s ease-in-out infinite;
    transition: transform .6s cubic-bezier(.3, .7, .2, 1.2);
    will-change: transform, filter;
}

.brand:hover .brand-mark,
.app-brand-toggle:hover .app-brand-mark {
    transform: rotate(360deg) scale(1.12);
}

@keyframes mark-breathe {
    0%, 100% {
        transform: translateY(0);
        filter: drop-shadow(0 0 0 rgba(47, 211, 154, 0));
    }
    50% {
        transform: translateY(-2.5px);
        filter: drop-shadow(0 2px 10px rgba(47, 211, 154, .55));
    }
}

@media (prefers-reduced-motion: reduce) {
    .brand-mark, .app-brand-mark { animation: none; transition: none; }
}

/* ------------------------------------------------------------ social row */

/* «لماذا لا تكون ايقونات حقيقية وتكون من فوق كي يراها المستخدم». Each badge
 * carries its brand's own colour with the real glyph in white — recognition at
 * a glance is the entire job of a social icon, and a grey outline does not do
 * it. In the top bar from 1100px up; the footer keeps the same badges for
 * phones, where the top bar has no room to spare. */
.social-badge {
    display: inline-grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: 9px;
    color: #fff !important;
    transition: transform .25s cubic-bezier(.2,.7,.2,1), box-shadow .25s;
}

.social-badge .icon { width: 16px; height: 16px; }

.social-badge:hover { transform: translateY(-2px) scale(1.08); box-shadow: 0 6px 18px rgba(0,0,0,.35); }

.social-badge--facebook { background: #1877F2; }
.social-badge--instagram {
    background: radial-gradient(circle at 30% 110%, #FDF497 0%, #FD5949 45%, #D6249F 60%, #285AEB 90%);
}
.social-badge--tiktok { background: #111; border: 1px solid rgba(255,255,255,.25); }
.social-badge--youtube { background: #FF0000; }
.social-badge--twitter { background: #14171A; border: 1px solid rgba(255,255,255,.22); }
.social-badge--telegram { background: #26A5E4; }
.social-badge--threads { background: #101010; border: 1px solid rgba(255,255,255,.25); }
.social-badge--whatsapp { background: #25D366; }

.nav-social { display: none; align-items: center; gap: 8px; }

@media (min-width: 1100px) {
    .nav-social { display: inline-flex; }
}

.footer-social .social-badge { width: 34px; height: 34px; }
.footer-social .social-badge .icon { width: 18px; height: 18px; }

/* ---------------------------------------------------------- material chips */

/* «انظر الى الايقونات اعجبوني» — Malewicz's 2026–27 direction: icons as
 * MATERIAL objects, extruded, catching light. Our CSS version: the chip
 * becomes a squircle tile with a per-hue gradient body, a specular sweep
 * along its top edge, an inner bevel and real depth below — and the glyph
 * rides on top with its own tiny lift. No renders, no images: the same hue
 * variable the flat chips carried now drives a lit object. */
.icon-chip {
    border-radius: 32%;
    background:
        linear-gradient(160deg,
            hsl(var(--icon-hue) 68% 62%) 0%,
            hsl(var(--icon-hue) 62% 46%) 48%,
            hsl(var(--icon-hue) 66% 34%) 100%);
    border: 1px solid hsl(var(--icon-hue) 60% 70% / .5);
    box-shadow:
        inset 0 1.5px 1px hsl(var(--icon-hue) 90% 88% / .75),
        inset 0 -3px 6px hsl(var(--icon-hue) 70% 20% / .55),
        0 5px 12px -3px hsl(var(--icon-hue) 65% 25% / .5),
        0 12px 26px -8px rgba(0, 0, 0, .5);
    color: #fff;
    position: relative;
    overflow: hidden;
}

.icon-chip::before {
    /* the specular sweep — the light that says "surface", not "flat fill" */
    content: "";
    position: absolute;
    inset: 6% 10% auto 10%;
    height: 34%;
    border-radius: 50%;
    background: linear-gradient(180deg, rgba(255, 255, 255, .5), transparent);
    pointer-events: none;
}

.icon-chip .icon {
    position: relative;
    filter: drop-shadow(0 1.5px 1.5px hsl(var(--icon-hue) 70% 18% / .8));
}

a:hover > .icon-chip, .card:hover .icon-chip, .bento-item:hover .icon-chip {
    transform: translateY(-3px) scale(1.06);
}

/* ----------------------------------------------------- soft material system */

/* «نيومورفيزمُ والايقوناتُ الموادّية البارزة طبقها على جميع المشروع» — owner,
 * 15 Aug 2026, promoting two approved samples to the project's physics.
 *
 * THE SYSTEM, NOT A STYLE PER PAGE: panels RISE out of the canvas, fields
 * SINK into it, primary buttons POP and then PRESS — depth as affordance.
 * All of it rides the --neu-dark/--neu-light token pair, so both themes get
 * correct physics from one ruleset. Marketing glass (bento on the landing,
 * plan cards, walid panels) deliberately keeps its own glass language: soft
 * pillows are the APP's material; glow-glass is the SHOWROOM's. */

/* GLOBAL, NOT APP-ONLY — the first cut scoped the physics to the app shell
   and the owner walked straight into a bare public form («طبقها على صفحات
   جميعا مثل Trouvez un artisan»). The soft system is the PROJECT's material;
   only the marketing glass families (bento landing, plan/dsc/fam cards,
   walid panels) keep their own deliberate language. */
.form-card, .auth-neu,
.card,
.form-panel,
body.app-body .starter-card,
body.app-body .bento-item,
.fab-panel {
    border-radius: 22px;
    border: none;
    background: var(--canvas);
    box-shadow:
        9px 9px 22px var(--neu-dark),
        -9px -9px 22px var(--neu-light);
}

/* TYPED FIELDS ARE WHITE AND CLEAR — the owner's amendment (walk notes
   3/7/10/18/24 + compass 37, executed 2 Sep 2026). The 14-Aug physics had
   typed fields SINK into the dark canvas; using his own platform he called
   that «مقعرة» twice and asked, verbatim: «أريدها بيضاء ليتضح الخط — وفي
   جميع الصفحات». So the sink is retired for typed fields ONLY: a white
   well, dark ink, a visible border and a real focus ring. Panels still
   rise, primary buttons still press — the material stays; only the paper
   you WRITE on became paper.
   TICKBOXES ARE STILL NOT TYPED FIELDS (rule 3): enumerated types only,
   never a bare `input` — a checkbox has no interior to write in. */
input[type="text"], input[type="number"], input[type="email"],
input[type="password"], input[type="date"], input[type="file"],
input[type="tel"], input[type="search"], input[type="url"],
select, textarea {
    border: 1px solid rgba(11, 110, 79, .35);
    border-radius: 10px;
    background: #FFFFFF;
    color: #10231C;
    box-shadow: none;
    padding: 10px 12px;
    font-size: 15px;
    min-height: 42px;
}

input[type="text"]:focus, input[type="number"]:focus, input[type="email"]:focus,
input[type="password"]:focus, input[type="date"]:focus, input[type="file"]:focus,
input[type="tel"]:focus, input[type="search"]:focus, input[type="url"]:focus,
select:focus, textarea:focus {
    outline: 2px solid var(--brand-lit, #2FD39A);
    outline-offset: 1px;
}

::placeholder { color: #5B6B64; opacity: 1; }

/* Big, unambiguous buttons — compass note 37: «أزرار كبيرة غير معقدة». */
.btn { padding: 10px 18px; font-size: 15px; }

/* Dense tables keep compact fields — same white paper, smaller cut: the
   cahier's line grid and the invoice rows would drown in 42px cells. */
table input[type="text"], table input[type="number"], table select {
    min-height: 34px;
    padding: 6px 8px;
    font-size: 14px;
}

/* primary buttons pop, then press — the metaphor's whole point */
.btn-primary {
    box-shadow:
        5px 5px 12px var(--neu-dark),
        -4px -4px 10px var(--neu-light);
    transition: box-shadow .15s, transform .15s, background .2s;
}

.btn-primary:active {
    transform: translateY(1px);
    box-shadow:
        inset 4px 4px 9px rgba(0, 0, 0, .35),
        inset -3px -3px 8px rgba(255, 255, 255, .08);
}

/* THE MATERIAL TILES GO EVERYWHERE AN ICON SITS IN CHROME: the dashboard
 * grid's chips and the sidebar's nav icons take the same extruded body the
 * landing chips carry — sized to their station. One hue variable was already
 * threaded through both by the colour wave; the material rides it for free. */
body.app-body .starter-ic {
    border-radius: 30%;
    border: 1px solid hsl(var(--icon-hue, 160) 60% 70% / .45);
    background:
        linear-gradient(160deg,
            hsl(var(--icon-hue, 160) 68% 60%) 0%,
            hsl(var(--icon-hue, 160) 62% 45%) 50%,
            hsl(var(--icon-hue, 160) 66% 33%) 100%);
    box-shadow:
        inset 0 1.5px 1px hsl(var(--icon-hue, 160) 90% 88% / .7),
        inset 0 -2px 5px hsl(var(--icon-hue, 160) 70% 20% / .5),
        0 4px 10px -3px hsl(var(--icon-hue, 160) 65% 25% / .45);
    color: #fff;
}

body.app-body .starter-ic .icon {
    filter: drop-shadow(0 1px 1.5px hsl(var(--icon-hue, 160) 70% 18% / .8));
}

body.app-body .app-nav-ic {
    display: inline-grid;
    place-items: center;
    width: 27px;
    height: 27px;
    border-radius: 30%;
    flex: 0 0 27px;
    background:
        linear-gradient(160deg,
            hsl(var(--icon-hue, 160) 62% 56%) 0%,
            hsl(var(--icon-hue, 160) 60% 42%) 55%,
            hsl(var(--icon-hue, 160) 64% 32%) 100%);
    box-shadow:
        inset 0 1px 1px hsl(var(--icon-hue, 160) 90% 88% / .65),
        0 2px 6px -2px hsl(var(--icon-hue, 160) 65% 22% / .5);
    color: #fff;
}

body.app-body .app-nav-ic .icon { width: 14px; height: 14px; }
/* Owner, 25 Aug 2026 (live use, on a phone): «ايقونة الحاسبات عندما تضغط
   على autocalc» — bigger. The collapsed/mobile rail (≤860px, or
   nav-collapsed) hides the label text entirely (.app-nav-item span), so
   .active's background tint two rules up was the ONLY "you are here"
   signal — invisible on a small badge with nothing beside it to contrast
   against. Same specificity family as the rule above (body.app-body
   .app-nav-ic .icon), one more class deep, so it wins on its own without
   depending on source order. */
body.app-body .app-nav-item.active .app-nav-ic .icon { width: 18px; height: 18px; }

/* muted (desktop/soon) tools: the tile goes matte — availability stays
 * readable before the click, the rule the 040 order was written for */
body.app-body .app-nav-item-muted .app-nav-ic {
    background: linear-gradient(160deg,
        hsl(var(--icon-hue, 160) 14% 38%), hsl(var(--icon-hue, 160) 12% 26%));
    box-shadow: none;
    color: hsl(var(--icon-hue, 160) 18% 70%);
}

/* -------------------------------------------------------------- mkt hero */

/* The artisan-request header: the breathing panel, the floating craft tile,
 * and one light forever running its baseline. The pulse keyframes are the
 * triptych's own (.v-pulse / v-run) — one signature, reused, not re-invented. */
.mkt-hero {
    display: flex;
    align-items: center;
    gap: 22px;
    padding: clamp(20px, 4vw, 34px);
    margin-bottom: 22px;
}

.mkt-hero-chip { flex: 0 0 auto; }

.mkt-hero-line {
    position: absolute;
    inset: auto 0 0 0;
    width: 100%;
    height: 8px;
    display: block;
}

.mkt-hero .v-pulse {
    stroke-dasharray: 90 910;
    animation: v-run 6s linear infinite;
}

@keyframes v-run { to { stroke-dashoffset: -1000; } }

@media (max-width: 640px) {
    .mkt-hero { flex-direction: column; text-align: center; }
}

@media (prefers-reduced-motion: reduce) {
    .mkt-hero .v-pulse { animation: none; stroke-dasharray: none; opacity: .4; }
}

/* ------------------------------------------------------------- role chips */

/* The register page's category picker, dressed to the same physics as the
 * form around it: each chip rises as a pillow carrying its category's
 * material tile, and CHOOSING one sinks it — the selected state is the press
 * itself, held. The owner's words for the before-state: «لماذا هي بسيطة
 * هكذا». */
.role-chip {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 14px 10px 12px;
    border: none;
    border-radius: 18px;
    background: var(--canvas);
    color: var(--ink);
    font-weight: 600;
    cursor: pointer;
    box-shadow:
        6px 6px 14px var(--neu-dark),
        -6px -6px 14px var(--neu-light);
    transition: box-shadow .18s, transform .18s;
}

.role-chip:hover { transform: translateY(-2px); }

.role-chip.selected,
.role-chip[aria-pressed="true"],
.role-chip.active {
    transform: none;
    box-shadow:
        inset 5px 5px 10px var(--neu-dark),
        inset -5px -5px 10px var(--neu-light);
    color: var(--brand-lit);
}

.role-chip-ic { width: 42px; height: 42px; }
.role-chip-ic .icon { width: 20px; height: 20px; }

/* the chosen chip dims its tile slightly — pressed things recede */
.role-chip.selected .role-chip-ic,
.role-chip.active .role-chip-ic {
    filter: saturate(.9) brightness(.92);
}

/* ------------------------------------------------------------- fam scene */

/* The card's face after the photographs failed the owner's bar twice: a
 * WALID-language mini-panel — diagonal depth, one breathing glow, the faint
 * site grid — carrying the category's material chip at hero size, in the
 * category's own hue. Designed, consistent, archive-proof. */
.fam-scene {
    height: 150px;
    flex: 0 0 150px;
    position: relative;
    overflow: hidden;
    display: grid;
    place-items: center;
    background:
        repeating-linear-gradient(to right, hsl(var(--icon-hue) 60% 55% / .07) 0 1px, transparent 1px 34px),
        repeating-linear-gradient(to top, hsl(var(--icon-hue) 60% 55% / .07) 0 1px, transparent 1px 34px),
        radial-gradient(80% 110% at 80% -10%, hsl(var(--icon-hue) 60% 40% / .5), transparent 60%),
        linear-gradient(135deg, #071B13, #0E2318);
}

.fam-scene::before {
    content: "";
    position: absolute;
    width: 60%;
    aspect-ratio: 1;
    border-radius: 50%;
    inset-inline-start: -18%;
    bottom: -35%;
    filter: blur(46px);
    background: hsl(var(--icon-hue) 70% 50% / .28);
    animation: walid-breathe 9s ease-in-out infinite;
}

.fam-chip {
    width: 84px;
    height: 84px;
    animation: fam-float 6.5s ease-in-out infinite;
}

.fam-chip .icon { width: 40px; height: 40px; }

@media (prefers-reduced-motion: reduce) {
    .fam-scene::before, .fam-chip { animation: none; }
}

/* ------------------------------------------------------------- fam cards */

/* The homepage marquee's category cards — the real photograph, the name and
 * the definition in the reader's language. The photos keep their own colours
 * («الصور اتركهم بلونهم الطبيعي»); only the frame is ours. */
.fam-card {
    flex: 0 0 auto;
    width: min(80vw, 360px);
    display: flex;
    flex-direction: column;
    border-radius: 18px;
    overflow: hidden;
    border: 1px solid var(--border);
    background: var(--surface);
    box-shadow: var(--shadow);
    color: inherit;
    transition: transform .35s cubic-bezier(.2,.7,.2,1), border-color .35s, box-shadow .35s;
}

.fam-card:hover {
    transform: translateY(-4px);
    border-color: rgba(47, 211, 154, .4);
    box-shadow: var(--glow);
    color: inherit;
}

.fam-card-photo {
    /* HARD CAGE. The first cut let some portrait photographs push past the
       170px band and the artisan's title ended up printed on his own photo.
       Fixed height + overflow + explicit img height: no image, whatever its
       shape, decides this layout. */
    height: 170px;
    flex: 0 0 170px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #0A3D2E, #0E2318);
}

.fam-card-photo img {
    width: 100%;
    height: 170px;
    object-fit: cover;
    display: block;
}

.fam-card-body { position: relative; background: var(--surface); }

.fam-card-glyph { font-size: 56px; }

.fam-card-body { padding: 14px 16px 16px; }
.fam-card-body h3 { margin: 0 0 6px; font-size: 1.02rem; color: var(--ink); }
.fam-card-body p {
    margin: 0 0 10px;
    color: var(--muted);
    font-size: .9rem;
    line-height: 1.65;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* «الايقونات اريدها حقيقية» — on the discovery cards the category's own
 * photograph is its face; the drawn icon survives only as the fallback for a
 * family that has no photo yet. */
.dsc-card-face {
    width: 64px;
    height: 64px;
    border-radius: 16px;
    overflow: hidden;
    border: 1px solid hsl(160 60% 55% / .3);
}

.dsc-card-face img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* the family page's media panel: photo as the ground layer, the hue scene
 * above it, the admin's video above everything when it exists */
.dsc-media-photo {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: .8;
}

/* THE DARK PANEL SPEAKS LIGHT, IN BOTH THEMES. .walid-panel is always a night
 * surface, but the headings inside it inherit --ink — which flips DARK in the
 * light theme, and the owner's screenshot shows the result: a title barely
 * distinguishable from its own background. A dark panel's text colour is a
 * property of the PANEL, not of the theme around it. */
.walid-panel h1, .walid-panel h2, .walid-panel .space-title { color: #EAF6F0; }
.walid-panel p, .walid-panel .space-lede { color: #B9D8C9; }

/* -------------------------------------------------------- discovery media */

/* «في الفئات يكون فيديو توضيحي لكل فئة مع صورة في الخلفية ايضا ملهمة حسب
 * الفئة» — the family page's header had a dead flank (the owner circled it in
 * red). It now holds the family's own media panel: an explainer VIDEO when the
 * admin has dropped one into static/video/discovery/<slug>.mp4 — same
 * folder-driven rule as the site photographs, no code and no deploy — and
 * until then an inspiring per-family scene: the family's glyph over the site
 * grid, lit in that family's own hue. */
.dsc-hero {
    display: grid;
    grid-template-columns: 1.1fr .9fr;
    gap: 28px;
    align-items: center;
}

@media (max-width: 900px) {
    .dsc-hero { grid-template-columns: 1fr; }
}

.dsc-media {
    position: relative;
    border-radius: 18px;
    overflow: hidden;
    border: 1px solid var(--border);
    aspect-ratio: 16 / 9;
    box-shadow: var(--shadow);
    background:
        radial-gradient(80% 90% at 70% 20%, hsl(var(--fam-hue, 160) 55% 30% / .55), transparent 70%),
        linear-gradient(180deg, #071b13, #04120c);
}

.dsc-media video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* the drawn scene when no video exists yet */
.dsc-media-scene {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    background-image:
        repeating-linear-gradient(to right, hsl(var(--fam-hue, 160) 60% 55% / .10) 0 1px, transparent 1px 44px),
        repeating-linear-gradient(to top, hsl(var(--fam-hue, 160) 60% 55% / .10) 0 1px, transparent 1px 44px);
}

.dsc-media-glyph {
    font-size: clamp(64px, 10vw, 118px);
    line-height: 1;
    filter: drop-shadow(0 10px 30px hsl(var(--fam-hue, 160) 70% 40% / .55));
    animation: fam-float 6s ease-in-out infinite;
}

.dsc-media-halo {
    position: absolute;
    width: 46%;
    aspect-ratio: 1;
    border-radius: 50%;
    border: 1px solid hsl(var(--fam-hue, 160) 70% 60% / .35);
    animation: spin 40s linear infinite;
}

.dsc-media-halo::after {
    content: "";
    position: absolute;
    inset: -14%;
    border-radius: 50%;
    border: 1px dashed hsl(var(--fam-hue, 160) 70% 60% / .2);
}

@keyframes fam-float {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-12px); }
}

@media (prefers-reduced-motion: reduce) {
    .dsc-media-glyph { animation: none; }
    .dsc-media-halo { animation: none; }
}

/* ------------------------------------------------------------ the vision */

/* The panorama with the sentence over it. The image carries its own dawn sky,
 * so it needs no theme variants; the scrim under the words is part of the
 * artwork's own darkness, readable on both themes. */
.vision-frame {
    position: relative;
    margin: 0;
    border-radius: 20px;
    overflow: hidden;
    border: 1px solid var(--border);
    box-shadow: var(--shadow);
}

.vision-frame img { display: block; width: 100%; height: auto; }

.vision-words {
    /* TOP, not bottom — the bottom is where the triptych's first act lives,
       and the first cut of v3 sat the sentence squarely on the site plan. The
       upper-left sky holds nothing but stars, which is exactly what a caption
       wants behind it. */
    position: absolute;
    inset: 0 0 auto 0;
    padding: clamp(18px, 4vw, 40px);
    /* .78 was written against dark drawings; the real map photograph is
       WHITE, and the sub-line drowned in it. Deep enough now to carry text
       over paper. */
    background: linear-gradient(to bottom, rgba(4, 16, 11, .94) 26%, rgba(4, 16, 11, .55) 60%, transparent 90%);
}

.vision-quote {
    /* The words must survive ANY ground — the map photograph under them is
       white paper, and a scrim alone kept losing the sub-line to it. */
    text-shadow: 0 1px 16px rgba(4, 16, 11, .95), 0 0 3px rgba(4, 16, 11, .9);
    margin: 0 0 6px;
    font-size: clamp(1.25rem, 3vw, 2.1rem);
    font-weight: 700;
    line-height: 1.3;
    color: #EAF6F0;
    max-width: 26ch;
}

.vision-sub {
    text-shadow: 0 1px 14px rgba(4, 16, 11, .95), 0 0 3px rgba(4, 16, 11, .9);
    margin: 0;
    color: #9FD4BE;
    font-size: clamp(.92rem, 1.5vw, 1.05rem);
    max-width: 52ch;
}

/* -------------------------------------------------------------- side rails */

/* «على اليمين وعلى الشمال هناك فراغ لماذا لا نضع شيء فيهما» — owner, 14 Aug
 * 2026, pointing at the register page's empty flanks. Two quiet columns of the
 * platform itself: screen captures and the trade's own words, drifting slowly
 * upward, faded at both ends so they read as atmosphere rather than content.
 *
 * DECORATION MUST KNOW ITS PLACE: display:none below 1200px (on a phone there
 * is no empty flank — the rails would sit ON the form), aria-hidden in the
 * markup, pointer-events none so nothing under a rail is ever unclickable, and
 * position:fixed so the drift rides with the visitor down the page. */
.side-rail {
    position: fixed;
    top: 0;
    bottom: 0;
    width: 200px;
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
    display: none;
    -webkit-mask-image: linear-gradient(180deg, transparent, #000 18%, #000 82%, transparent);
    mask-image: linear-gradient(180deg, transparent, #000 18%, #000 82%, transparent);
}

.side-rail-start { inset-inline-start: 24px; }
.side-rail-end { inset-inline-end: 24px; }

@media (min-width: 1200px) {
    .side-rail { display: block; }
}

.side-rail-track {
    display: flex;
    flex-direction: column;
    gap: 18px;
    width: 100%;
    animation: rail-drift 70s linear infinite;
}

/* The two rails drift in opposite directions — the page breathes instead of
 * conveyor-belting. */
.side-rail-end .side-rail-track { animation-direction: reverse; }

@keyframes rail-drift {
    to { transform: translateY(-50%); }
}

.side-rail-shot {
    border-radius: 12px;
    overflow: hidden;
    border: 1px solid var(--border);
    opacity: .8;
}

.side-rail-shot img { display: block; width: 100%; height: auto; }

.side-rail-word {
    text-align: center;
    font-weight: 700;
    letter-spacing: .08em;
    color: var(--brand-lit);
    opacity: .6;
    font-size: .95rem;
}

@media (prefers-reduced-motion: reduce) {
    .side-rail-track { animation: none; }
}

/* ---------------------------------------------------- floating assistant */

/* «ايقونة فيها شات بووت تبقى تتحرك معك … ايقونة على شكل مهندس». Fixed, so it
 * rides the scroll; breathing, so it reads as alive rather than parked. It is
 * the SMART ENGINEER's door — the button answers because the platform already
 * has someone who answers, and a bubble that fakes a chat would be worse than
 * no bubble. */
.fab-assistant {
    position: fixed;
    inset-inline-end: 22px;
    bottom: 22px;
    z-index: 60;
    display: grid;
    place-items: center;
    width: 58px;
    height: 58px;
    border-radius: 50%;
    border: 1px solid hsl(160 60% 55% / .4);
    background: linear-gradient(160deg, var(--brand) 0%, var(--brand-deep) 100%);
    color: #EAF6F0;
    cursor: pointer;
    box-shadow: 0 10px 30px rgba(0, 0, 0, .45), var(--glow);
    animation: fab-breathe 4.5s ease-in-out infinite;
    transition: transform .25s cubic-bezier(.2,.7,.2,1);
}

.fab-assistant:hover { transform: translateY(-3px) scale(1.05); animation-play-state: paused; }
.fab-assistant .icon { width: 26px; height: 26px; }

@keyframes fab-breathe {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-5px); }
}

.fab-panel {
    position: fixed;
    inset-inline-end: 22px;
    bottom: 92px;
    z-index: 60;
    width: min(92vw, 340px);
    border-radius: 18px;
    border: 1px solid var(--border);
    background: var(--surface);
    box-shadow: 0 24px 70px rgba(0, 0, 0, .55), var(--glow);
    padding: 16px;
    transform-origin: bottom right;
}

[dir="rtl"] .fab-panel { transform-origin: bottom left; }

.fab-panel[hidden] { display: none; }

.fab-panel h3 {
    margin: 0 0 4px;
    font-size: 1rem;
    display: flex;
    align-items: center;
    gap: 8px;
}

.fab-panel .fab-sub { margin: 0 0 10px; color: var(--muted); font-size: .88rem; line-height: 1.6; }

.fab-panel textarea {
    width: 100%;
    min-height: 66px;
    resize: vertical;
    border-radius: 10px;
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--ink);
    padding: 8px 10px;
    font: inherit;
}

.fab-answer {
    margin-top: 10px;
    max-height: 200px;
    overflow-y: auto;
    font-size: .9rem;
    line-height: 1.7;
    white-space: pre-wrap;
    color: var(--ink);
}

.fab-answer:empty { display: none; }

/* The old command-door styles that lived here were removed with the
   duplicate widget generation (30 Aug 2026, test-walk notes 2 & 4) —
   the Ctrl+K palette pill in base_app.html carries its own styles. */

@media (prefers-reduced-motion: reduce) {
    .hero-visual.has-image img { animation: none; }
    .marquee-track { animation: none; }
    .marquee { overflow-x: auto; }
    .fab-assistant { animation: none; }
}

/* -------------------------------------------------------- the walid panel */

/* «مثل ما فعلنا في موقع مشروع WALID … ليس بالضرورة تكون مثلها لكن تكون شبيهة»
 * — owner, 14 Aug 2026. What WALID actually is, measured from its built CSS:
 * a deep diagonal gradient panel with two soft radial glows breathing in
 * opposite corners, and cards that lift under the pointer. The SHAPE travels;
 * the colours do not — WALID glows cyan-blue, CONSIA glows in its own green,
 * because the palette is the one thing the owner has twice ordered untouched. */
.walid-panel {
    position: relative;
    border-radius: 22px;
    overflow: hidden;
    background:
        radial-gradient(circle at top left, hsl(160 70% 45% / .20), transparent 30%),
        radial-gradient(circle at bottom right, hsl(150 65% 40% / .26), transparent 34%),
        linear-gradient(135deg, #04100B, #0E2318);
    border: 1px solid var(--border);
}

/* The two glows breathe very slowly — alive, not blinking. */
.walid-panel::before,
.walid-panel::after {
    content: "";
    position: absolute;
    width: 46%;
    aspect-ratio: 1;
    border-radius: 50%;
    pointer-events: none;
    filter: blur(60px);
    animation: walid-breathe 11s ease-in-out infinite;
}

.walid-panel::before {
    top: -18%;
    inset-inline-start: -12%;
    background: hsl(160 75% 50% / .16);
}

.walid-panel::after {
    bottom: -18%;
    inset-inline-end: -12%;
    background: hsl(145 70% 45% / .2);
    animation-delay: -5.5s;
}

@keyframes walid-breathe {
    0%, 100% { opacity: .55; transform: scale(1); }
    50% { opacity: 1; transform: scale(1.18); }
}

/* -------------------------------------------------------- icons that draw */

/* «غير الايقونات بايقونات سكرولي وحقيقية». Real means OUR line icons instead
 * of emoji — an emoji is a font glyph nobody designed for this brand, and it
 * cannot be recoloured or animated. Scroll means the stroke DRAWS itself as
 * the card arrives: every path in the 24px set is shorter than ~70 units, so
 * one dasharray serves the whole family without measuring each path.
 *
 * Scoped to .icon-scroll, never global: making every icon in the app animate
 * would bill each dashboard paint for a landing-page flourish. */
.icon-scroll .icon path,
.icon-scroll .icon circle,
.icon-scroll .icon rect {
    stroke-dasharray: 72;
    stroke-dashoffset: 72;
    fill: none;
}

html.reveal-armed .is-in .icon-scroll .icon path,
html.reveal-armed .is-in .icon-scroll .icon circle,
html.reveal-armed .is-in .icon-scroll .icon rect,
html:not(.reveal-armed) .icon-scroll .icon path,
html:not(.reveal-armed) .icon-scroll .icon circle,
html:not(.reveal-armed) .icon-scroll .icon rect {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset 1.1s cubic-bezier(.4, 0, .2, 1) .25s;
}

/* And once drawn, the icon floats — the same quiet life as the family glyph. */
.icon-scroll {
    display: inline-grid;
    place-items: center;
    animation: fam-float 7s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .walid-panel::before, .walid-panel::after { animation: none; }
    .icon-scroll { animation: none; }
    .icon-scroll .icon path, .icon-scroll .icon circle, .icon-scroll .icon rect {
        stroke-dashoffset: 0; transition: none;
    }
}

/* ------------------------------------------------------------- discovery */

/* «استكشف المنصة» is the first door a visitor opens, and until now it opened
 * onto a flat grid of equal boxes. Same bento treatment, same reason — but the
 * emphasis here is the OPPOSITE of the pricing page's: no family is
 * recommended, because the visitor is the only one who knows which trade is
 * his. The variety comes from the light, not from the sizing. */
.dsc-card {
    position: relative;
    overflow: hidden;
    border-radius: 18px;
    background:
        linear-gradient(180deg, rgba(255,255,255,.035), transparent 55%),
        var(--surface);
    transition: transform .35s cubic-bezier(.2,.7,.2,1), border-color .35s, box-shadow .35s;
}

.dsc-card:hover {
    transform: translateY(-4px);
    border-color: rgba(47, 211, 154, .35);
    box-shadow: var(--glow);
}

/* The family glyphs are emoji, set by the template — so they are left alone and
   only their plate is drawn. Recolouring an emoji is not possible and faking it
   with a filter turns a hard hat grey. */
.dsc-card-icon {
    display: inline-grid;
    place-items: center;
    width: 46px;
    height: 46px;
    border-radius: 13px;
    background: hsl(160 60% 50% / .10);
    border: 1px solid hsl(160 60% 55% / .24);
}

.dsc-card-open { color: var(--brand-lit); }

/* --------------------------------------------------------------- section */

.space-section { padding: clamp(48px, 9vh, 110px) 0; }

.space-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: .82rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--brand-lit);
}

.space-eyebrow::before {
    content: "";
    width: 26px;
    height: 1px;
    background: currentColor;
}

.space-title {
    font-size: var(--step-1);
    line-height: 1.12;
    letter-spacing: -.02em;
    margin: 12px 0 10px;
}

.space-lede {
    max-width: 62ch;
    color: var(--muted);
    font-size: clamp(1rem, 1.4vw, 1.15rem);
    line-height: 1.8;
}

/* ------------------------------------------------------------ reduced motion */

@media (prefers-reduced-motion: reduce) {
    body::before { animation: none; }
    html.reveal-armed .kinetic > span { animation: none; opacity: 1; transform: none; }
    .kinetic-sheen { animation: none; }
    .orbit::before, .orbit::after { animation: none; }
    .reveal { transition: none; opacity: 1; transform: none; }
    .bento-item { transition: none; }
    .bento-item:hover { transform: none; }
}

/* ---------------------------------------------------------------------------
   Back — the owner's short touch of 15 Aug, built 16 Aug.

   Same size, radius and border as .theme-toggle beside it, because they are
   the same KIND of control: a small round chrome button that belongs to the
   shell rather than to any page. Matching them by hand rather than sharing a
   class keeps a future change to one from silently moving the other.

   The arrow is «→» and it is correct: this shell is RTL, and in RTL the arrow
   that means «back» points right. It is flipped for the LTR locales, where the
   same glyph would otherwise mean «forward» to an English or French reader.
--------------------------------------------------------------------------- */
.app-back {
    display: inline-grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
    padding: 0;
    font-size: 1.05rem;
    line-height: 1;
    margin-inline-end: 10px;
    flex: 0 0 auto;
    transition: border-color .25s, color .25s, transform .25s;
}

.app-back:hover {
    border-color: var(--brand-lit);
    color: var(--brand-lit);
    transform: translateY(-1px);
}

.app-back:focus-visible {
    outline: 2px solid var(--brand-lit);
    outline-offset: 2px;
}

html[dir="ltr"] .app-back span { display: inline-block; transform: scaleX(-1); }

@media (prefers-reduced-motion: reduce) {
    .app-back { transition: none; }
    .app-back:hover { transform: none; }
}
