/* Components CSS - Extracted from header.php inline styles */

/* Mobile Menu Styles */
.mobile-menu-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 1000;
    opacity: 0;
    visibility: hidden;
    transition: all 0.3s ease;
}
.mobile-menu-overlay.active {
    opacity: 1;
    visibility: visible;
}
.mobile-menu {
    position: fixed;
    top: 0;
    left: 0;
    width: 280px;
    height: 100%;
    background: var(--bg-card);
    border-right: 1px solid var(--card-border);
    z-index: 1001;
    transform: translateX(-100%);
    transition: transform 0.3s ease;
    overflow-y: auto;
}
.mobile-menu.active {
    transform: translateX(0);
}
.mobile-menu-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    /* 40px close button + 0.75rem vertical padding ≈ 64px header band. */
    min-height: 64px;
    padding: 0.75rem 1.25rem;
    border-bottom: 1px solid var(--card-border);
}
.mobile-menu-close {
    /* In-flow part of .mobile-menu-header — sits on the side nearest the
       hamburger (left in RTL, right in LTR) so it still reads as the toggle. */
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--card-border);
    border-radius: 8px;
    background: var(--bg-card);
    cursor: pointer;
    transition: background 0.2s ease, color 0.2s ease;
}
.mobile-menu-close:hover {
    background: var(--primary-accent);
    border-color: var(--border-color);
    color: var(--icon-color);
}
.mobile-menu-links {
    padding: 1rem 0;
}
.mobile-menu-links a {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.875rem 1.25rem;
    color: var(--text-primary);
    font-weight: 600;
    transition: all 0.2s;
    border-right: 3px solid transparent;
}
.mobile-menu-links a:hover {
    background: var(--primary-accent);
    color: #202935;
    border-right-color: var(--border-color);
}
.mobile-menu-links a svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}
.hamburger-btn {
    display: flex;
    width: 40px;
    height: 40px;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--card-border);
    border-radius: 8px;
    background: var(--bg-card);
    cursor: pointer;
    transition: all 0.2s;
}
.hamburger-btn:hover {
    background: var(--primary-accent);
    border-color: var(--border-color);
    color: var(--icon-color);
}
.menu-divider {
    height: 1px;
    background: var(--card-border);
    margin: 0.5rem 1.25rem;
}

/* PWA Install Instructions Modal */
.pwa-modal-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    z-index: 10000;
    align-items: center;
    justify-content: center;
    padding: 16px;
}
.pwa-modal-overlay.show {
    display: flex;
}
.pwa-modal {
    background: var(--bg-card);
    border: 1px solid var(--card-border);
    border-radius: 16px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
    max-width: 400px;
    width: 100%;
    animation: modalIn 0.3s ease;
}
@keyframes modalIn {
    from { opacity: 0; transform: scale(0.9); }
    to { opacity: 1; transform: scale(1); }
}
.pwa-modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 16px 20px;
    border-bottom: 1px solid var(--card-border);
}
.pwa-modal-header h3 {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 700;
}
.pwa-modal-close {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    background: var(--bg-card);
    border: 1px solid var(--card-border);
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.2s;
}
.pwa-modal-close:hover {
    background: #fee2e2;
}
.pwa-modal-close svg {
    width: 18px;
    height: 18px;
}
.pwa-modal-body {
    padding: 20px;
}
.pwa-instructions-title {
    font-weight: 700;
    margin: 0 0 16px 0;
    color: var(--text-primary);
}
.pwa-steps {
    list-style: none;
    padding: 0;
    margin: 0;
    counter-reset: step;
}
.pwa-steps li {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 12px 0;
    border-bottom: 1px solid var(--border-color);
}
.pwa-steps li:last-child {
    border-bottom: none;
}
.step-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    background: var(--primary-accent);
    color: #202935;
    border: 1px solid var(--card-border);
    border-radius: 50%;
    font-size: 1rem;
    flex-shrink: 0;
}
.pwa-steps li span:last-child {
    padding-top: 6px;
    line-height: 1.5;
}

/* Menu Install Button */
.install-btn {
    display: none;
    align-items: center;
    gap: 0.75rem;
    margin: 0.75rem 1.25rem;
    /* <button> shrinks to fit; .bz-row (an <a>) fills the row. Pin both to
       the same width so neither install path looks like the bigger offer. */
    width: calc(100% - 2.5rem);
    padding: 0.875rem 1.25rem;
    /* Outlined, not filled: it sits between the theme toggle and the Bazaar
       row, and a solid accent block made it read as the only real action. */
    background: var(--bg-card);
    color: var(--text-primary);
    font-weight: 700;
    border: 1px solid var(--card-border);
    border-radius: 0.75rem;
    cursor: pointer;
    transition: all 0.2s;
}
.install-btn svg {
    width: 20px;
    height: 20px;
}
.install-btn:hover,
.bz-row:hover {
    background: var(--primary-accent);
    border-color: var(--border-color);
    color: #202935;
}
.install-btn.show {
    display: flex;
}

/* Menu: get the Android app from Cafe Bazaar. Deliberately identical
   metrics to .install-btn — only the colour differs. */
.bz-row {
    display: none;
    align-items: center;
    gap: 0.75rem;
    margin: 0.75rem 1.25rem;
    width: calc(100% - 2.5rem);
    padding: 0.875rem 1.25rem;
    background: var(--bg-card);
    color: var(--text-primary);
    font-weight: 700;
    text-decoration: none;
    border: 1px solid var(--card-border);
    border-radius: 0.75rem;
    transition: all 0.2s;
}
.bz-row img {
    width: 20px;
    height: 20px;
    object-fit: contain;
}
.bz-row.show {
    display: flex;
}

/* Toast Notification System */
.toast-container {
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 10000;
    display: flex;
    flex-direction: column;
    gap: 8px;
    pointer-events: none;
}
.toast {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 20px;
    background: var(--bg-card, #fff);
    border: 2px solid var(--border-color, #202935);
    border-radius: 12px;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15);
    font-weight: 600;
    pointer-events: auto;
    animation: toastIn 0.3s ease;
    width: max-content;
    max-width: min(90vw, 420px);
    line-height: 1.55;
    text-align: start;
}
.toast > span {
    flex: 1 1 auto;
    min-width: 0;
}
.toast .toast-icon {
    margin-top: 2px;
}
.toast.toast-out {
    animation: toastOut 0.3s ease forwards;
}
.toast-success {
    border-color: #22c55e;
}
.toast-success .toast-icon {
    color: #22c55e;
}
.toast-error {
    border-color: #ef4444;
}
.toast-error .toast-icon {
    color: #ef4444;
}
.toast-warning {
    border-color: #f59e0b;
}
.toast-warning .toast-icon {
    color: #f59e0b;
}
.toast-info {
    border-color: #3b82f6;
}
.toast-info .toast-icon {
    color: #3b82f6;
}
.toast-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}
@keyframes toastIn {
    from { opacity: 0; transform: translateY(20px); }
    to { opacity: 1; transform: translateY(0); }
}
@keyframes toastOut {
    from { opacity: 1; transform: translateY(0); }
    to { opacity: 0; transform: translateY(20px); }
}

/* Feedback reply-push opt-in chip (footer rating widget) */
.fb-push-cta {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-inline-start: 8px;
    background: transparent;
    border: 1.5px solid var(--primary-accent);
    color: var(--text-primary);
    border-radius: 999px;
    padding: 2px 11px;
    font: inherit;
    font-size: 0.8rem;
    font-weight: 700;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}
.fb-push-cta:hover:not(:disabled) {
    background: var(--primary-accent);
    color: #000;
}
.fb-push-cta:disabled { opacity: 0.6; cursor: default; }

/* Global Loading Overlay */
.neo-loading-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 9999;
    opacity: 0;
    visibility: hidden;
    transition: all 0.2s ease;
}
.neo-loading-overlay.active {
    opacity: 1;
    visibility: visible;
}
.neo-loading-box {
    background: var(--bg-card, #fff);
    border: 3px solid var(--border-color, #202935);
    border-radius: 16px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
    padding: 32px 48px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
}
.neo-spinner {
    width: 48px;
    height: 48px;
    border: 4px solid var(--border-color, #202935);
    border-top-color: var(--primary-accent, #fec007);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}
.neo-loading-text {
    font-weight: 700;
    font-size: 1rem;
    color: var(--text-primary);
}
@keyframes spin {
    to { transform: rotate(360deg); }
}

/* Form Validation Styles */
.input-error {
    border-color: #ef4444 !important;
    box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2) !important;
}
.input-success {
    border-color: #22c55e !important;
    box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.2) !important;
}
.field-error {
    color: #ef4444;
    font-size: 0.875rem;
    margin-top: 4px;
    display: flex;
    align-items: center;
    gap: 4px;
}
.field-error svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

/* Fake-hole colour for multi-rect raw icons (QR, tank): tracks whatever the
   icon tile is filled with, so the cut-outs read as holes in both modes. */
:root { --icon-hole: var(--primary-accent); }
:root.dark { --icon-hole: var(--bg-input); }

/* ── Dark-mode tool cards ──────────────────────────────────────────────
   The solid-yellow icon tile vibrates against the dark cards. In dark mode
   swap it for a dark tile + yellow icon (far less yellow area) and drop the
   hard offset shadow. Light mode keeps the punchy yellow fill + shadow. */
:root.dark .tool-card .bg-accent {
    background-color: var(--bg-input);
    border: 1px solid var(--border-color);
}
:root.dark .tool-card .bg-accent .tool-icon,
:root.dark .tool-card .bg-accent .tool-icon-text {
    color: var(--primary-accent) !important;
}
/* The light-yellow halo only suited a black icon on a yellow tile. */
:root.dark .tool-card .bg-accent svg { filter: none; }
:root.dark .tool-card .bg-accent .tool-icon-text { text-shadow: none; }
/* Live widgets (calendar, IP, typing, game stats) get the same treatment —
   they use .tool-widget / .tool-widget-header-icon, not .tool-card. */
:root.dark .tool-widget-header-icon {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    color: var(--primary-accent);
}
/* Game widgets inject the tool's own icon, which carries .tool-icon
   (forced dark via !important) — re-assert yellow for it inside the tile. */
:root.dark .tool-widget-header-icon .tool-icon,
:root.dark .tool-widget-header-icon .tool-icon-text {
    color: var(--primary-accent) !important;
}
:root.dark .tool-widget-header-icon svg { filter: none; }
/* Card/widget shadow lives in the --card-shadow custom property (styles.css,
   :root + :root.dark) so light/dark resolve automatically — no override here. */

/* Promo Banner — two corner cards (qr-style). Box 1 = advertiser, box 2 =
   house-ad for one of our own lower-traffic tools. White card in light mode,
   theme-aware in dark mode; no "ads" label. One set of markup for both
   breakpoints — only size/position change. */
.promo-card {
    box-sizing: border-box;
    display: block;
    position: fixed;
    z-index: 999;
    /* Always a light card (even in dark mode) so the light-background artwork
       blends seamlessly and the promo reads as an ad, not part of the page. */
    background: #ffffff;
    border: 2px solid #e5e7eb;
    border-radius: 0.75rem;
    text-decoration: none;
    text-align: center;
    color: #1f2937;
    overflow: hidden;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.14);
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.promo-card.pos-left { left: 0.6rem; }
.promo-card.pos-right { right: 0.6rem; }
.promo-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.2);
    text-decoration: none;
    color: #1f2937;
}
.promo-card-img { width: 100%; display: block; object-fit: cover; }
.promo-card-body {
    padding: 0.55rem 0.5rem 0.6rem;
    font-family: 'Vazirmatn', sans-serif;
}
.promo-card-text {
    font-weight: 700;
    font-size: 0.9rem;
    line-height: 1.45;
    color: #52525b;
}
.promo-card-btn {
    margin-top: 0.5rem;
    display: block;
    width: 100%;
    text-align: center;
    padding: 0.4rem 1rem;
    background: var(--promo-btn, var(--primary-accent));
    color: var(--promo-btn-fg, #000);
    /* Two-way soft halo (qr's --pc-shadow pattern): black copy / white shadow,
       white copy / black shadow — set alongside --promo-btn-fg in PHP. The
       fallback covers the no-btnColor case (brand yellow, black copy). */
    text-shadow: 0 1px 2px var(--promo-btn-shadow, rgba(255, 255, 255, .5));
    font-weight: 800;
    font-size: 0.8rem;
    font-family: 'Vazirmatn', sans-serif;
    border-radius: 0.5rem;
    transition: filter 0.2s;
}
.promo-card:hover .promo-card-btn { filter: brightness(0.92); }
/* Both promo cards are clean white (theme-aware): the colourful image carries
   the colour, the CTA takes the banner's brand colour (set in admin, default
   brand yellow). House-ad copy clamps to two lines on the white body. */
.promo-house .promo-card-text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
}
/* No-image fallback: a category-tinted band + white OG glyph so a house-ad
   isn't blank on the white card until a real image is uploaded. */
/* NB: no filter here — filter applies to the WHOLE element (incl. background),
   so inverting turned the tint band white too => white-on-white. Show the OG
   glyph in its natural colour on the category-tinted band instead. */
.promo-house-icon { object-fit: contain; background: linear-gradient(150deg, #64748b, #475569); }
.promo-house.cat-utility       .promo-house-icon { background: linear-gradient(150deg, #0ea5e9, #2563eb); }
.promo-house.cat-finance       .promo-house-icon { background: linear-gradient(150deg, #059669, #0d9488); }
.promo-house.cat-content       .promo-house-icon { background: linear-gradient(150deg, #4f46e5, #7c3aed); }
.promo-house.cat-code          .promo-house-icon { background: linear-gradient(150deg, #475569, #0f172a); }
.promo-house.cat-image         .promo-house-icon { background: linear-gradient(150deg, #db2777, #f43f5e); }
.promo-house.cat-entertainment .promo-house-icon { background: linear-gradient(150deg, #7c3aed, #c026d3); }
.promo-wrap-close { display: none; }
/* Per-card dismiss X — only used on desktop (mobile has the tray X). Hidden by
   default; the desktop rules below reveal it on hover. */
.promo-card-close { display: none; }
/* Dismiss must hide the tray/cards on BOTH breakpoints. The mobile block also
   sets this, but desktop's .promo-wrap{display:contents} would otherwise keep
   the corner cards visible after dismissPromo() — this global (higher
   specificity than the single-class desktop rule) wins there too. */
.promo-wrap.promo-wrap-hidden { display: none; }

/* Desktop: two independent fixed corner cards; the wrapper dissolves so the
   cards sit in opposite corners. No dismiss X (they live in empty corners). */
@media (min-width: 1025px) {
    .promo-wrap, .promo-cards-row { display: contents; }
    /* Fixed equal height so the two corner cards line up (top AND bottom) even
       though their content differs; body flexes to fill and centers. */
    .promo-card { width: 150px; height: 200px; bottom: 0.6rem; display: flex; flex-direction: column; }
    .promo-card-img { height: 100px; flex: none; }
    /* Body is a column: copy sits centered up top, CTA pinned full-width at the
       bottom edge (margin-top:auto) so every card's button lines up. */
    .promo-card-body { flex: 1; display: flex; flex-direction: column; align-items: stretch; gap: 0.4rem; padding: 0.6rem; }
    .promo-card-btn { margin-top: auto; }
    /* Glyph fills the SAME 100px top slot as the ad image (contained, padded) so
       both cards' bodies start at the same y => buttons line up. */
    .promo-house-icon { height: 100px; padding: 1.3rem; box-sizing: border-box; }
    /* Hover-only dismiss X in the card's top corner. Idle => invisible and
       click-through; on hover it fades in and takes clicks. dismissPromo()
       hides both corner cards. */
    .promo-card-close {
        display: block;   /* override the global display:none on desktop */
        position: absolute;
        top: 5px;
        left: 5px;
        width: 20px;
        height: 20px;
        border-radius: 50%;
        background: rgba(0, 0, 0, 0.45);
        font-size: 0;   /* hide the &times; glyph — the cross is drawn below */
        z-index: 5;
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.15s ease;
        cursor: pointer;
    }
    /* Cross drawn from two centred bars — perfectly centred regardless of font
       metrics (the &times; glyph sat high-left of centre). */
    .promo-card-close::before,
    .promo-card-close::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 50%;
        width: 10px;
        height: 2px;
        background: #fff;
        border-radius: 1px;
    }
    .promo-card-close::before { transform: translate(-50%, -50%) rotate(45deg); }
    .promo-card-close::after  { transform: translate(-50%, -50%) rotate(-45deg); }
    :root[dir="ltr"] .promo-card-close { left: auto; right: 5px; }
    .promo-card:hover .promo-card-close { opacity: 1; pointer-events: auto; }
    .promo-card-close:hover { background: rgba(0, 0, 0, 0.72); }
}
/* Tier 2 — the second pair of cards (slot 2 in admin, or the overflow when
   more than two banners match a page). Laptop / narrow desktop (1025-1539):
   the gutter beside the content column only fits ONE card per side, so tier 2
   stacks above tier 1 — and to keep four cards from eating the viewport, the
   CTA button goes (the whole card is the link anyway) and the card shrinks to
   170px; the brand colour the button carried moves to a 4px bottom strip. */
@media (min-width: 1025px) and (max-width: 1539px) {
    .promo-card {
        height: 170px;
        border-bottom: 4px solid var(--promo-btn, var(--primary-accent));
    }
    .promo-card-body { align-items: center; justify-content: center; padding: 0.4rem 0.6rem; }
    .promo-card-btn { display: none; }
    .promo-card.tier-2 { bottom: calc(0.6rem + 170px + 0.6rem); }
}
/* Wide desktop (≥1540): two cards per side fit beside the content column
   (896px max), so tier 2 sits BESIDE tier 1 and the full card (with CTA)
   stays. */
@media (min-width: 1540px) {
    .promo-card.tier-2.pos-right { right: calc(0.6rem + 150px + 0.6rem); }
    .promo-card.tier-2.pos-left  { left:  calc(0.6rem + 150px + 0.6rem); }
}
/* Phone/tablet: one bottom tray holding both white cards side by side, a
   single close X (floating circle over the top edge), no image, no ads label. */
@media (max-width: 1024px) {
    .promo-wrap {
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        z-index: 999;
        background: var(--bg-card);
        border-top: 1.5px solid var(--card-border);
        box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.16);
        padding: 0.4rem 0.6rem;
        /* The tray starts off-screen and only slides up once the visitor
           scrolls (footer.php adds .promo-wrap-shown) — the first viewport
           belongs to the tool, not the ads. The extra 3rem clears the close
           tab (-1.5rem above the top edge) plus the shadow bloom. */
        transform: translateY(calc(100% + 3rem));
        transition: transform 0.45s cubic-bezier(0.22, 0.9, 0.35, 1);
    }
    .promo-wrap.promo-wrap-shown { transform: translateY(0); }
    .promo-wrap.promo-wrap-hidden { display: none; }
    /* Close reads as a "tab" growing out of the tray: it sits above the top
       edge, dips slightly into the tray, and drops its bottom border there so
       the tray and the tab merge seamlessly (same 1.5px border as the tray). */
    .promo-wrap-close {
        position: absolute;
        top: -1.5rem;
        left: 0.5rem;
        width: 2rem;
        height: 1.5rem;
        display: flex;
        align-items: center;
        justify-content: center;
        padding-bottom: 0;
        background: var(--bg-card);
        border: 1px solid var(--card-border);
        border-bottom: none;
        color: var(--text-secondary);
        border-radius: 7px 7px 0 0;
        font-size: 1.25rem;
        line-height: 0;
        cursor: pointer;
        box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.08);
    }
    :root[dir="ltr"] .promo-wrap-close { left: auto; right: 0.5rem; }
    /* Dark mode: bg-card is too close to the page tone and the tray got lost,
       while the old off-white tray flashed a light panel at night. Middle
       ground: one step lighter than the page surfaces (slate-700), a brighter
       top edge and a deeper shadow, so it reads as an elevated bar. */
    :root.dark .promo-wrap { background: #334155; border-top-color: #64748b; box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.45); }
    :root.dark .promo-wrap-close { background: #334155; border-color: #64748b; }
    /* Two chips per row; a tier-2 pair (when sold) wraps onto a second row. */
    .promo-cards-row { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: stretch; }
    /* pos-left/right carry a left/right offset for the DESKTOP corner cards.
       They leak in globally; on mobile the chips are position:static so it was
       harmless — until a photo chip became position:relative (to hold its
       absolute image) and the offset kicked in, shoving each chip outward
       (edge overhang + a fat centre gap). Neutralise it here. */
    .promo-card.pos-left, .promo-card.pos-right { left: auto; right: auto; }
    .promo-card {
        position: static;
        flex: 1 1 calc(50% - 0.5rem);
        min-width: 0;
        border-width: 1.5px;
        border-radius: 0.6rem;
        box-shadow: none;
        display: flex;
        flex-direction: column;
    }
    .promo-card:hover { transform: none; box-shadow: none; }
    /* Text-only chips on mobile — no image, no button (the whole card is a
       link). Each chip is filled with its CTA/brand colour (the same colour its
       desktop button uses) so the bar is lively, not two plain white boxes. */
    .promo-card {
        background: var(--promo-btn, var(--primary-accent));
        border-color: transparent;
    }
    .promo-card-img { display: none; }
    .promo-card-btn { display: none; }
    .promo-card-body {
        flex: 1;
        padding: 0.85rem 0.7rem;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }
    .promo-card-text {
        font-size: 0.86rem;
        line-height: 1.45;
        color: var(--promo-btn-fg, #000);
        /* Same two-way halo as .promo-card-btn — on mobile the chip body IS
           the coloured surface, so the copy takes the shadow here. */
        text-shadow: 0 1px 2px var(--promo-btn-shadow, rgba(255, 255, 255, .5));
    }
    .promo-house .promo-card-text { -webkit-line-clamp: 2; }
    /* Chips that have a REAL photo (not the glyph fallback, not image-less
       paid ads) show it as a SIDE thumb: copy on the brand-colour fill like
       every other chip, artwork complete in its own column at the inline start
       (right in RTL). Overlaying the copy on a full-bleed photo was tried in
       several forms (flat scrim, gradient scrim, blur pill, per-line
       highlight) and every one traded readability against the artwork on some
       card shape — side-by-side is the layout that never has to. */
    .promo-card:has(.promo-card-img:not(.promo-house-icon)) {
        flex-direction: row;
        align-items: stretch;
        overflow: hidden;   /* thumb bleeds to the card edge; clip to its radius */
    }
    .promo-card:has(.promo-card-img:not(.promo-house-icon)) .promo-card-img {
        display: block;
        width: 4rem;   /* 64px: ~80% of the 300×200 art visible (was 3.5rem / ~69%); ≤411px steps down below */
        flex: none;
        height: auto;
        min-height: 100%;
        object-fit: cover;
    }
    /* The thumb narrows the text column, so the body sheds padding to keep
       the copy on TWO lines (never truncate ad copy — the words are what the
       advertiser paid for; the columns give way instead). */
    .promo-card:has(.promo-card-img:not(.promo-house-icon)) .promo-card-body {
        padding: 0.45rem 0.4rem;
    }
    .promo-card:has(.promo-card-img:not(.promo-house-icon)) .promo-card-text {
        font-weight: 800;
    }
    /* Fallback offset before JS measures the exact tray height (avoids a flash);
       footer.php then sets body padding to the tray's real height => no gap. */
    body.has-promo { padding-bottom: 5rem; }
}
/* Narrow phones: the side thumb squeezes the text column until the copy runs
   past two lines (three at 360px, four at 320px) — and ad copy must NEVER
   truncate. Step the thumb and type down so two lines always fit; on the very
   narrowest screens the thumb goes entirely and the chip falls back to its
   solid brand-colour form. */
@media (max-width: 411px) {
    .promo-card:has(.promo-card-img:not(.promo-house-icon)) .promo-card-img { width: 2.75rem; }
    .promo-card:has(.promo-card-img:not(.promo-house-icon)) .promo-card-body { padding: 0.4rem 0.35rem; }
    .promo-card .promo-card-text { font-size: 0.82rem; }
}
@media (max-width: 355px) {
    .promo-card:has(.promo-card-img:not(.promo-house-icon)) .promo-card-img { display: none; }
    .promo-card .promo-card-text { font-size: 0.8rem; }
}
@media (max-width: 1024px) and (prefers-reduced-motion: reduce) {
    .promo-wrap { transition: none; }
}

/* Inline house-ad unit (promo-inline.php) — two cards in the flow, right under
   the content box. These are always OUR OWN tools, so unlike the tray they are
   styled as part of the page (theme-aware surfaces), not as an ad panel. Width
   and rhythm match .seo-content so the block reads as one column. */
.inline-promo {
    max-width: 800px;
    margin: 1.5rem auto 0;
    padding: 0 0.25rem;
}
.inline-promo-title {
    font-size: 0.9rem;
    font-weight: 800;
    color: var(--text-secondary);
    margin-bottom: 0.7rem;
    padding-inline-start: 0.15rem;
}
.inline-promo-row {
    display: grid;
    /* Mobile-first: one compact row-card per line. The 3-up desktop layout is
       opted into below — at 800px wide, three side-by-side cards can't also
       carry a side thumb, so they flip to a stacked card there. */
    grid-template-columns: minmax(0, 1fr);
    gap: 0.6rem;
}
/* Phones — one compact row: an uncropped 3:2 banner at the start (the exact
   ratio the uploaded house images are produced at), the copy in the middle, the
   CTA at the far end. The banner is width-capped so it stays a thumbnail, not a
   hero — a full-width one made each card ~260px tall. Desktop restacks it into
   a 3-up column further down. */
.inline-promo-card {
    display: grid;
    grid-template-columns: clamp(84px, 27%, 116px) minmax(0, 1fr) auto;
    grid-template-areas: "thumb body cta";
    align-items: center;
    gap: 0.7rem;
    min-width: 0;
    padding: 0.7rem;
    background: var(--bg-card);
    border: 1px solid var(--card-border);
    border-radius: 0.85rem;
    text-decoration: none;
    color: var(--text-primary);
    transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}
.inline-promo-card:hover {
    transform: translateY(-2px);
    border-color: var(--promo-btn, var(--primary-accent));
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);
    text-decoration: none;
    color: var(--text-primary);
}
:root.dark .inline-promo-card:hover { box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45); }
.inline-promo-thumb {
    grid-area: thumb;
    width: 100%;
    aspect-ratio: 3 / 2;
    border-radius: 0.6rem;
    overflow: hidden;
    display: block;
    background: var(--bg-input);
}
.inline-promo-thumb img {
    width: 100%;
    height: 100%;
    display: block;
    /* cover is safe here: the source IS 3:2, so nothing is cut. It only guards
       against an odd-sized legacy upload. */
    object-fit: cover;
}
/* Glyph fallback (cache/icons OG art, not 3:2): contain it on a category-tinted
   band so a card without hand-made artwork still carries colour and the whole
   glyph stays visible. Same palette as the tray's .promo-house-icon. */
.inline-promo-glyph img { object-fit: contain; padding: 0.45rem; box-sizing: border-box; }
.inline-promo-glyph { background: linear-gradient(150deg, #64748b, #475569); }
.inline-promo-card.cat-utility       .inline-promo-glyph { background: linear-gradient(150deg, #0ea5e9, #2563eb); }
.inline-promo-card.cat-finance       .inline-promo-glyph { background: linear-gradient(150deg, #059669, #0d9488); }
.inline-promo-card.cat-content       .inline-promo-glyph { background: linear-gradient(150deg, #4f46e5, #7c3aed); }
.inline-promo-card.cat-code          .inline-promo-glyph { background: linear-gradient(150deg, #475569, #0f172a); }
.inline-promo-card.cat-image         .inline-promo-glyph { background: linear-gradient(150deg, #db2777, #f43f5e); }
.inline-promo-card.cat-entertainment .inline-promo-glyph { background: linear-gradient(150deg, #7c3aed, #c026d3); }
.inline-promo-body {
    grid-area: body;
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 0.15rem;
}
/* Wrap to a 2nd line rather than truncate: the CTA takes the end of the copy
   row, so a hard nowrap would clip real tool names. */
.inline-promo-name {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    font-weight: 800;
    font-size: 0.95rem;
    line-height: 1.5;
    color: var(--text-primary);
}
.inline-promo-text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    font-size: 0.8rem;
    line-height: 1.6;
    color: var(--text-secondary);
}
/* Phone card: the whole card is the link, so a labelled button would only eat
   ~80px of copy width to repeat what a tap already does. It shrinks to a
   chevron in the tool's brand colour — the affordance and the colour stay, the
   width goes back to the name and description. Drawn from borders rather than
   a glyph so no font has to carry it. */
.inline-promo-btn {
    grid-area: cta;
    align-self: center;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    color: var(--promo-btn, var(--primary-accent));
    transition: filter 0.18s, transform 0.18s;
}
.inline-promo-btn::after {
    content: '';
    width: 0.5rem;
    height: 0.5rem;
    border-left: 2.5px solid currentColor;
    border-bottom: 2.5px solid currentColor;
    border-radius: 1px;
    transform: rotate(45deg);
}
.inline-promo-card:hover .inline-promo-btn { filter: brightness(0.92); }
.inline-promo-card:active .inline-promo-btn { transform: translateX(-2px); }
/* Tablet and up: three cards across, each a single stacked column. The columns
   are only ~245px, so the CTA becomes a full-width footer pinned to the bottom
   edge (the 1fr copy row absorbs the slack) and all three line up. */
@media (min-width: 700px) {
    .inline-promo-row {
        /* 2×2 in the middle band: four across only becomes readable once the
           container is wide enough to give each card ~190px. */
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.75rem;
        align-items: stretch;
    }
    .inline-promo-card {
        position: relative;
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "thumb"
            "body";
        align-items: stretch;
        gap: 0.55rem;
        padding: 0.6rem;
    }
    /* The thumb is 3:2 on mobile (a small side square), but three of them
       side-by-side on desktop turn the unit into a wall of pictures taller
       than the copy it sits under. 16:9 keeps the image readable at ~245px
       columns while cutting roughly a quarter of the card's height; the
       source stays 3:2, so cover only trims the top/bottom margins. */
    .inline-promo-thumb { aspect-ratio: 16 / 9; }
    .inline-promo-glyph img { padding: 0.7rem; }
    .inline-promo-name { font-size: 0.92rem; }
    /* Copy hugs the bottom of the (stretched) body row, so the last line of the
       description always lands at the same height as the pinned arrow — even
       when a neighbouring card's two-line name makes the row taller. */
    .inline-promo-body { justify-content: flex-end; }
    /* Reserve both lines whether or not the copy needs them. Without this a
       one-line description leaves the slack under itself (the body row is the
       1fr one), so the CTAs read as floating at different distances even
       though they are bottom-aligned. */
    .inline-promo-text {
        font-size: 0.85rem;
        min-height: calc(1.6em * 2);
        /* Gutter the arrow lives in, so a full-width second line can't run
           into it. Inline-END: the arrow points the way the text flows, which
           in RTL means the left edge. */
        padding-inline-end: 1.1rem;
    }
    /* No button here either: a filled CTA is what the PAID banners look like,
       and these are our own suggestions — they should read as a quieter thing.
       Its own row would push the arrow to the card's floor, a line below the
       copy; pinned to the end of the (always two-line) description it reads as
       part of the sentence instead. */
    .inline-promo-btn {
        position: absolute;
        /* Boxed into the last line of the copy: the card's inner edge on one
           side, the line's own height on the other — so the arrow never pokes
           out past the card or below the text. */
        inset-inline-end: 0.5rem;
        bottom: 0.5rem;
        height: 0.9rem;
        width: 0.9rem;
        align-self: auto;
    }
}
/* Wide enough for the full row: four cards at ~190px each inside the 800px
   content column. Below this the 2×2 above keeps the copy legible. */
@media (min-width: 900px) {
    .inline-promo-row { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.65rem; }
    .inline-promo-card { padding: 0.55rem; gap: 0.5rem; }
    .inline-promo-text { font-size: 0.85rem; line-height: 1.6; min-height: calc(1.6em * 2); }
    .inline-promo-glyph img { padding: 0.55rem; }
}
/* Very narrow phones: tighten the copy row so the CTA never squeezes the name
   into a sliver. The banner keeps its full width and 3:2 ratio. */
@media (max-width: 380px) {
    .inline-promo-card { padding: 0.55rem; gap: 0.55rem; }
    .inline-promo-name { font-size: 0.88rem; }
    .inline-promo-text { font-size: 0.76rem; -webkit-line-clamp: 2; }
    .inline-promo-btn { width: 1.1rem; }
}
@media (prefers-reduced-motion: reduce) {
    .inline-promo-card, .inline-promo-btn { transition: none; }
    .inline-promo-card:hover { transform: none; }
}

/* Favorite Button */
.favorite-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 1rem;
    font-size: 0.85rem;
    font-weight: 600;
    font-family: inherit;
    color: var(--text-secondary);
    background: var(--bg-card);
    border: 1px solid var(--card-border);
    border-radius: 2rem;
    cursor: pointer;
    transition: all 0.2s;
}
.favorite-btn:hover {
    background: var(--bg-input);
    border-color: #ef4444;
    color: #ef4444;
}
.favorite-btn.is-favorite {
    background: #fef2f2;
    border-color: #ef4444;
    color: #ef4444;
}
:root.dark .favorite-btn.is-favorite {
    background: #450a0a;
    border-color: #f87171;
    color: #f87171;
}
.favorite-btn svg {
    width: 1.25rem;
    height: 1.25rem;
}
.favorite-btn.is-favorite svg {
    fill: currentColor;
}

/* Header favorite button */
.favorite-btn-header {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 1px solid var(--card-border);
    background: var(--bg-card);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.2s;
    color: var(--text-secondary);
}
.favorite-btn-header:hover {
    background: #fef2f2;
    border-color: #ef4444;
    color: #ef4444;
}
.favorite-btn-header.is-favorite {
    background: #fef2f2;
    border-color: #ef4444;
    color: #ef4444;
}
.favorite-btn-header.is-favorite svg {
    fill: #ef4444;
}
:root.dark .favorite-btn-header:hover,
:root.dark .favorite-btn-header.is-favorite {
    background: #450a0a;
    border-color: #f87171;
    color: #f87171;
}
:root.dark .favorite-btn-header.is-favorite svg {
    fill: #f87171;
}
.favorite-btn-header svg {
    width: 20px;
    height: 20px;
}

/* Theme Toggle Button in Menu */
.theme-toggle-btn {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: calc(100% - 2.5rem);
    margin: 0.75rem 1.25rem;
    padding: 0.875rem 1.25rem;
    background: var(--bg-card);
    color: var(--text-primary);
    font-weight: 600;
    font-family: inherit;
    font-size: 1rem;
    border: 1px solid var(--card-border);
    border-radius: 0.75rem;
    cursor: pointer;
    transition: all 0.2s;
}
.theme-toggle-btn:hover {
    background: var(--primary-accent);
    border-color: var(--border-color);
    color: #202935;
}
.theme-toggle-btn svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

/* ===== Tool Card Layout ===== */
.tool-info {
    margin-inline-start: 1rem;
}

/* ===== LTR (English) Overrides ===== */
[dir="ltr"] .mobile-menu {
    left: auto;
    right: 0;
    border-right: none;
    border-left: 1px solid var(--card-border);
    transform: translateX(100%);
}
[dir="ltr"] .mobile-menu.active {
    transform: translateX(0);
}
[dir="ltr"] .mobile-menu-links a {
    border-right: none;
    border-left: 3px solid transparent;
}
[dir="ltr"] .mobile-menu-links a:hover {
    border-right-color: transparent;
    border-left-color: var(--border-color);
}
[dir="ltr"] .search-container input {
    text-align: left;
    direction: ltr;
}
[dir="ltr"] .back-link {
    direction: ltr;
}
[dir="ltr"] .seo-content {
    direction: ltr;
    text-align: left;
}

/* Category Nav Bar — full-bleed within container, fixed to header when stuck */
.category-chips-bar {
    position: relative;
    width: 100vw;
    margin: 0 calc(50% - 50vw) 1rem;
    padding: 0.5rem 1rem;
    background: transparent;
    transition: background-color 0.25s ease;
}
/* Placeholder keeps layout stable while chip bar is fixed */
.category-chips-placeholder {
    display: none;
    width: 100%;
}
.category-chips-placeholder.active {
    display: block;
}
.category-chips-bar.is-stuck {
    position: fixed;
    top: calc(var(--header-h, 56px) - 1px); /* -1px overlap to cover header's border & sub-pixel gaps */
    inset-inline: 0;
    width: 100vw;
    margin: 0;
    z-index: 998; /* above header (997) to seamlessly cover its border */
    background: var(--bg-card);
    border-bottom: 1px solid var(--card-border);
    box-shadow: var(--card-shadow);
}
:root.dark .category-chips-bar.is-stuck {
    box-shadow: var(--card-shadow);
}

/* Chip bar placed inside the header (desktop, on scroll) */
.category-chips-bar.in-header {
    position: static;
    width: auto;
    flex: 1 1 auto;
    min-width: 0;
    margin: 0 0.75rem;
    padding: 0;
    background: transparent !important;
    box-shadow: none !important;
    border: none !important;
}
.header-sticky .category-chips-bar.in-header .category-chips {
    max-width: 100%;
    margin: 0;
    gap: 6px;
    flex-wrap: nowrap;
    justify-content: center;
    /* No overflow clipping here — 8 short chips fit easily in desktop header. */
}
.header-sticky .category-chips-bar.in-header .category-chip {
    padding: 6px 12px;
    font-size: 0.8rem;
    flex-shrink: 0;
}
.header-sticky .category-chips-bar.in-header .category-chip svg {
    width: 16px;
    height: 16px;
}
.header-sticky .category-chips-bar.in-header .chip-full { display: none; }
.header-sticky .category-chips-bar.in-header .chip-short { display: inline; }
.header-sticky .category-chips-bar.in-header .chip-count { display: none; }

.category-chips {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    max-width: 900px; /* all 5 full-title chips fit on ONE line (~840px) */
    margin-inline: auto;
}
/* Forced 3+2 row break. Hidden on wide desktop (chips fit one line); active
   below 960px so a natural wrap never orphans one chip alone (4+1). */
.chip-row-break { display: none; }
@media (max-width: 960px) {
    .category-chips-bar:not(.is-stuck) .chip-row-break {
        display: block;
        flex-basis: 100%;
        height: 0;
    }
}

.category-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 14px;
    border: 1px solid var(--card-border);
    border-radius: 9999px;
    background: var(--bg-card);
    box-shadow: var(--card-shadow);
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 0.2s, color 0.2s, box-shadow 0.2s, transform 0.2s;
    white-space: nowrap;
    font-family: inherit;
    color: var(--text-primary);
}
.category-chip svg {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}
.chip-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    /* Top-only padding nudges the digit down: Persian digits sit high in the
       line box (empty descender space), so flex-centering alone reads as off. */
    padding: 0.14rem 0.34rem 0;
    box-sizing: border-box;
    border-radius: 9999px;
    background: var(--border-light);
    color: var(--text-secondary);
    font-weight: 700;
    font-size: 0.68rem;
    line-height: 1;
}
.category-chip.active {
    background: var(--primary-accent);
    color: #202935;
}
.category-chip.active .chip-count {
    background: rgba(0, 0, 0, 0.13);
    color: #202935;
}
@media (hover: hover) {
    .category-chip:hover {
        background: var(--primary-accent);
        color: #202935;
    }
    .category-chip:hover .chip-count {
        background: rgba(0, 0, 0, 0.13);
        color: #202935;
    }
}

/* Flat chips (no border/shadow) when chip bar is stuck or merged into header */
.category-chips-bar.is-stuck .category-chip,
.category-chips-bar.in-header .category-chip {
    border: none;
    background: var(--bg-input);
    box-shadow: none;
    padding: 7px 14px;
}
.category-chips-bar.is-stuck .category-chip.active,
.category-chips-bar.in-header .category-chip.active {
    background: var(--primary-accent);
    box-shadow: none;
}
@media (hover: hover) {
    .category-chips-bar.is-stuck .category-chip:hover,
    .category-chips-bar.in-header .category-chip:hover {
        background: var(--primary-accent);
        color: #202935;
        box-shadow: none;
    }
    .category-chips-bar.is-stuck .category-chip:hover .chip-count,
    .category-chips-bar.in-header .category-chip:hover .chip-count {
        background: rgba(0, 0, 0, 0.13);
        color: #202935;
    }
}

/* Full label on desktop, short label on mobile */
.chip-short { display: none; }

@media (max-width: 768px) {
    .category-chips-bar {
        padding: 0.4rem 0;
        margin-bottom: 0.75rem;
    }

    /* Mobile DEFAULT: wrap into multiple rows (like desktop), no horizontal
       scroll. Full chip labels wrap naturally + centered — no forced 4-4 split
       (the old break orphaned a wide chip alone on its own row). */
    .category-chips-bar:not(.is-stuck) .category-chips {
        flex-wrap: wrap;
        justify-content: center;
        max-width: 100%;
        column-gap: 4px;
        row-gap: 5px;
        padding: 0 0.5rem;
    }

    /* Mobile STUCK: horizontal scroll strip */
    .category-chips-bar.is-stuck .category-chips {
        flex-wrap: nowrap;
        overflow-x: auto;
        justify-content: flex-start;
        max-width: 100%;
        gap: 6px;
        padding: 0 1rem;
        scrollbar-width: none;
        -ms-overflow-style: none;
        scroll-snap-type: x proximity;
    }
    .category-chips-bar.is-stuck .category-chips::-webkit-scrollbar { display: none; }
    .category-chips-bar.is-stuck .category-chip {
        scroll-snap-align: start;
    }

    /* Edge fade only when horizontally scrollable (stuck state) */
    .category-chips-bar.is-stuck::before,
    .category-chips-bar.is-stuck::after {
        content: '';
        position: absolute;
        top: 0;
        bottom: 0;
        width: 20px;
        pointer-events: none;
        z-index: 2;
    }
    .category-chips-bar.is-stuck::before {
        inset-inline-start: 0;
        background: linear-gradient(to right, var(--bg-card), transparent);
    }
    .category-chips-bar.is-stuck::after {
        inset-inline-end: 0;
        background: linear-gradient(to left, var(--bg-card), transparent);
    }
    [dir="rtl"] .category-chips-bar.is-stuck::before {
        background: linear-gradient(to left, var(--bg-card), transparent);
    }
    [dir="rtl"] .category-chips-bar.is-stuck::after {
        background: linear-gradient(to right, var(--bg-card), transparent);
    }

    /* Common chip sizing on mobile — sized for the forced 3+2 layout
       (.chip-row-break, activated in the ≤960px query above). */
    .category-chip {
        padding: 6px 10px;
        font-size: 0.75rem;
        gap: 5px;
        flex-shrink: 0;
    }
    .category-chip svg { width: 13px; height: 13px; }
    /* Short labels on mobile — drops the "ابزار " prefix so chips stay narrow
       and wrap evenly instead of orphaning a wide chip on its own row. */
    .chip-full { display: none; }
    .chip-short { display: inline; }
    /* Hide count on mobile to save space */
    .chip-count { display: none; }
}

/* Page-load intro: the floating icons are pre-hidden in the markup (.intro-armed
   set server-side) so they never flash before the JS intro stashes them in the
   box. Only opacity is zeroed — the icons keep their natural layout positions so
   the intro's fly-vector measurement stays accurate. JS removes .intro-armed once
   it runs; a <noscript> rule in the markup restores opacity when JS is disabled. */
.hero-section.intro-armed .hero-shapes svg {
    opacity: 0;
}

/* Hero Logo. Size comes from --hero-logo-size so the <img> fallback below (used
   on Samsung Internet) stays in lockstep with the inline SVG at every breakpoint. */
.hero-section {
    --hero-logo-size: 80px;
}
@media (max-width: 640px) {
    /* Mobile: at 80px the (decorative, aria-hidden) logo was 3.5x the title's font
       size — vs 2.2x on desktop — so it read twice as heavy here as it does there.
       64px restores that ratio. Side effect, not the reason: the hero got ~24px
       shorter, which un-clips the first card row on short 360x800 Androids. */
    .hero-section {
        --hero-logo-size: 64px;
    }
}
.hero-logo {
    width: var(--hero-logo-size);
    height: var(--hero-logo-size);
    cursor: pointer;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    outline: none;
    overflow: visible;
    transition: transform 0.2s ease;
}
.hero-logo:hover {
    transform: scale(1.06);
}
.hero-logo:focus { outline: none; }
.hero-logo:focus-visible {
    outline: 2px solid var(--primary-accent, #fec007);
    outline-offset: 4px;
    border-radius: 8px;
}
/* Lid morphs via CSS `d` property: closed top face → open panel hinged on the
   back-left edge. The dark box base already sits behind it, so when the lid lifts
   the opening reads as the (black) inside of the box — no separate interior path.
   Fill transitions from yellow (outer surface) to near-black (inside of the lid).
   Har do path az scripts/build-favicon.php --emit miyayand (sakhtar-e node yeksan). */
.hero-logo .box-lid {
    d: path("M11.35,3.5 Q12,3.12 12.65,3.5 L19.27,7.36 Q19.66,7.59 19.27,7.81 L12.39,11.83 Q12,12.06 11.61,11.83 L4.73,7.81 Q4.34,7.59 4.73,7.36 L11.35,3.5 Z");
    /* Closing transition — smooth ease-out, no overshoot */
    transition: d 0.5s cubic-bezier(0.16, 1, 0.3, 1),
                fill 0.4s ease;
    will-change: d, fill;
}
.hero-section.lid-open .hero-logo .box-lid {
    /* Lid stands up from the back-left hinge (12,2.25)-(3,7.5). In the open state
       it is the FULL-size lid (top face grown by half the ring) — the small inset
       face alone read as a thin fin merging with the box's dark rim. */
    d: path("M10.7,3.01 Q12,2.25 12,0.75 L12,-6.12 Q12,-8.32 10.1,-7.21 L4.15,-3.74 Q2.85,-2.99 2.85,-1.49 L2.85,5.39 Q2.85,7.59 4.75,6.48 L10.7,3.01 Z");
    fill: #202935;
    /* Opening transition — bouncy overshoot for the "popping open" feel */
    transition: d 0.55s cubic-bezier(0.34, 1.56, 0.64, 1),
                fill 0.4s ease;
}
.hero-section.lid-bounce .hero-logo .box-lid {
    transform: translate(0.5px, 0.5px) rotate(2deg);
    transition-duration: 0.2s;
}
/* While the lid is open and icons are clustering inside / flying out, bring the
   hero-shapes layer ABOVE the logo so the cluster is visible (it would otherwise
   render behind the box body / interior since .hero-shapes has z-index 0). */
.hero-section.suck-in .hero-shapes,
.hero-section.release .hero-shapes {
    z-index: 2 !important;
}
/* During suck-in the icons stay saturated until they "enter" the box; during
   release they start saturated (visible inside the box) and gradually fade back
   to the default background tint as they reach their scattered positions. The
   color values animate inside the iconSuck / iconRelease keyframes themselves. */
.hero-section.suck-in .hero-shapes svg {
    color: rgba(254, 192, 7, 1) !important;
}
:root.dark .hero-section.suck-in .hero-shapes svg {
    color: rgba(254, 192, 7, 1) !important;
}
/* Suck-in: scattered icons first GATHER directly above the box opening, then drop
   straight DOWN into it through the lid — they enter from the top, not slide in
   from the sides. They stay fully opaque the whole way so you watch them go in. */
.hero-section.suck-in .hero-shapes svg {
    animation: iconSuck 0.85s cubic-bezier(0.5, 0, 0.5, 1) both !important;
    animation-delay: var(--fly-delay, 0ms) !important;
    will-change: transform, opacity;
}
@keyframes iconSuck {
    0% {
        transform: translate(0, 0) scale(1) rotate(0deg);
        opacity: 1;
    }
    55% {
        /* Gathered in a tight cluster directly ABOVE the box opening */
        transform: translate(var(--fly-x-top, var(--fly-x, 0px)), calc(var(--fly-y-top, 0px) - 72px)) scale(0.62) rotate(70deg);
        opacity: 1;
    }
    82% {
        /* Dropping straight down (x held constant) through the opening */
        transform: translate(var(--fly-x-top, var(--fly-x, 0px)), calc(var(--fly-y-top, 0px) - 4px)) scale(0.34) rotate(110deg);
        opacity: 1;
    }
    100% {
        /* Sunk deep into the box and faded out — the closing lid then hides it */
        transform: translate(var(--fly-x-top, var(--fly-x, 0px)), calc(var(--fly-y-top, 0px) + 6px)) scale(0.16) rotate(130deg);
        opacity: 0;
    }
}
/* Box full, lid closed (after suck-in, before release): the icons are FULLY
   hidden — they're a separate layer from the logo SVG, so they can't be tucked
   "behind the lid but in front of the interior". opacity:0 is the only clean way
   to keep them from poking past the closed lid. They re-appear on release by
   emerging UP out of the opening the instant the lid pops open. */
.hero-section.icons-stored .hero-shapes svg {
    animation: none !important;
    transform: translate(var(--fly-x-top, var(--fly-x, 0px)), calc(var(--fly-y-top, 0px) + 6px)) scale(0.16);
    opacity: 0;
}
/* Release: the icons are hidden inside the closed box. The lid pops open first
   (release() gives it a ~150ms head start), then the cluster emerges UP out of
   the opening — fading in fast (over the first 10%) WHILE rising, so it reads as
   one continuous "lid opens → tools stream out" motion with no empty-box beat.
   Phases:
     0-10%:   fast fade-in at the opening, just as the lid finishes popping
     10-34%:  cluster sits visibly inside the open box
     34-56%:  cluster rises straight up out of the opening (x held constant)
     56-100%: icons spread out + fade to the default background tint
*/
.hero-section.release .hero-shapes svg {
    animation: iconRelease 1.2s cubic-bezier(0.34, 1.4, 0.64, 1) both !important;
    animation-delay: var(--fly-delay, 0ms) !important;
}
@keyframes iconRelease {
    0% {
        /* Identical to the .icons-stored state — seamless hand-off, no jump */
        transform: translate(var(--fly-x-top, var(--fly-x, 0px)), calc(var(--fly-y-top, 0px) + 6px)) scale(0.16) rotate(0deg);
        opacity: 0;
        color: #fec007;
    }
    10% {
        /* Snapped into view at the opening — fast fade so there's no faint blur */
        transform: translate(var(--fly-x-top, var(--fly-x, 0px)), calc(var(--fly-y-top, 0px) - 2px)) scale(0.42) rotate(5deg);
        opacity: 1;
        color: #fec007;
    }
    34% {
        /* Cluster sits visibly inside the open box, grown enough to read */
        transform: translate(var(--fly-x-top, var(--fly-x, 0px)), calc(var(--fly-y-top, 0px) - 6px)) scale(0.5) rotate(10deg);
        opacity: 1;
        color: rgba(254, 192, 7, 1);
    }
    56% {
        /* Rises straight up out of the opening — x held at fly-x-top */
        transform: translate(var(--fly-x-top, var(--fly-x, 0px)), calc(var(--fly-y-top, 0px) - 52px)) scale(0.78) rotate(24deg);
        opacity: 1;
        color: rgba(254, 192, 7, 1);
    }
    80% {
        /* Spreading toward scattered positions — gentle fade begins */
        transform: translate(calc(var(--fly-x, 0px) * 0.4), calc(var(--fly-y, 0px) * 0.4)) scale(0.95) rotate(12deg);
        opacity: 0.9;
        color: rgba(254, 192, 7, 0.7);
    }
    100% {
        /* Fully back to scattered, faded to match the default heroFloat tint */
        transform: translate(0, 0) scale(1) rotate(0deg);
        opacity: 0.55;
        color: rgba(254, 192, 7, 0.42);
    }
}
@media (prefers-reduced-motion: reduce) {
    .hero-logo {
        animation: none;
    }
    .hero-logo .box-lid,
    .hero-section.suck-in .hero-shapes svg,
    .hero-section.release .hero-shapes svg {
        transition: none;
        animation: none !important;
    }
}

/* Hero Section */
.hero-section {
    position: relative;
    margin-top: -2.5rem;
    overflow-x: clip;
    overflow-y: visible;
}
/* Don't pull the hero up any further on mobile: at -3.25rem the logo's top edge
   (52px) crossed under the header's bottom (56px). -2.5rem already leaves only an
   8px gap there. */
.hero-section > * {
    position: relative;
    z-index: 1;
}
.hero-shapes {
    position: absolute !important;
    inset: 0;
    overflow-x: clip;
    overflow-y: visible;
    z-index: 0 !important;
}
.hero-shapes svg {
    position: absolute;
    display: block;
    width: var(--s);
    height: var(--s);
    left: var(--x);
    top: var(--y);
    color: rgba(254, 192, 7, 0.42);
    animation-name: heroFloat;
    animation-duration: var(--d);
    animation-delay: var(--del);
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
    animation-direction: alternate;
}
.hero-shapes svg:nth-child(3n+2) {
    animation-name: heroFloat2;
}
.hero-shapes svg:nth-child(3n) {
    animation-name: heroFloat3;
}
:root.dark .hero-shapes svg {
    color: rgba(254, 192, 7, 0.2);
}
@keyframes heroFloat {
    0%   { transform: translate(0, 0) rotate(-8deg); opacity: 0.55; }
    100% { transform: translate(2px, -32px) rotate(14deg); opacity: 1; }
}
@keyframes heroFloat2 {
    0%   { transform: translate(0, 0) rotate(10deg) scale(1); opacity: 0.5; }
    100% { transform: translate(-7px, -24px) rotate(-12deg) scale(1.06); opacity: 0.95; }
}
@keyframes heroFloat3 {
    0%   { transform: translate(0, 0) rotate(0deg); opacity: 0.6; }
    100% { transform: translate(-9px, -20px) rotate(20deg); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .hero-shapes svg {
        animation: none;
    }
}
@media (max-width: 480px) {
    .hero-shapes svg {
        width: calc(var(--s) * 0.5);
        height: calc(var(--s) * 0.5);
        left: min(var(--x), calc(100% - var(--s) * 0.5 - 6px));
    }
}

/* Sticky Header */
/* Pre-set body padding so the fixed header doesn't overlap content before the
   JS in header.php has run (which measures the live header height and overrides
   this). Without this, slow JS execution causes a visible content shift (CLS). */
body {
    padding-top: 64px;
}
.header-sticky {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 997;
    border-bottom: 1px solid transparent;
    transition: background-color 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease, transform 0.25s ease;
}
.header-sticky.header-hidden {
    transform: translateY(-100%);
}
.header-sticky > div {
    transition: padding 0.3s ease;
}
/* Index page pre-scroll: push nav to inline-end, minimal padding */
.header-sticky:not(.header-has-title):not(.header-scrolled) nav {
    margin-inline-start: auto;
}
.header-sticky:not(.header-has-title):not(.header-scrolled) > div {
    padding-top: 0.75rem;
    padding-bottom: 0.5rem;
}
/* Index page: hide logo+title until scrolled */
.header-sticky .header-title,
.header-sticky .header-logo {
    opacity: 0;
    max-width: 0;
    max-height: 0;
    overflow: hidden;
    transition: opacity 0.3s ease, max-width 0.3s ease, max-height 0.3s ease;
}
.header-scrolled .header-title,
.header-scrolled .header-logo {
    opacity: 1;
    max-width: 300px;
    max-height: 3rem;
}
/* Non-index pages: always show logo, title and border */
.header-sticky.header-has-title .header-title,
.header-sticky.header-has-title .header-logo {
    opacity: 1;
    max-width: 300px;
    max-height: 3rem;
}
.header-sticky.header-has-title {
    background-color: var(--bg-card);
    border-bottom-color: var(--card-border);
}
.header-scrolled {
    background-color: var(--bg-card);
    border-bottom-color: var(--card-border);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
:root.dark .header-scrolled {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
/* Dark mode: index page header transparent before scroll */
:root.dark .header-sticky:not(.header-has-title):not(.header-scrolled) {
    background-color: transparent;
    border-color: transparent;
}
@media (prefers-reduced-motion: reduce) {
    .header-sticky,
    .header-sticky > div {
        transition-duration: 0.01s;
    }
}

/* Tools Picker Drawer */
.tools-picker-btn,
.header-search-btn,
.header-install-btn {
    display: flex;
    width: 40px;
    height: 40px;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--card-border);
    border-radius: 8px;
    background: var(--bg-card);
    color: var(--text-primary);
    cursor: pointer;
    transition: all 0.2s;
}
.tools-picker-btn:hover,
.header-search-btn:hover,
.header-install-btn:hover {
    background: var(--primary-accent);
    border-color: var(--border-color);
    color: var(--icon-color);
}

/* Header install icon — hidden until the browser fires beforeinstallprompt
   (i.e. the PWA is genuinely installable and not already installed). JS adds
   .show. Self-removes on appinstalled. Kept out of the monetized promo-banner
   zone on purpose — it lives in the top bar, never as a bottom sheet. */
.header-install-btn { display: none; }
.header-install-btn.show {
    display: flex;
    animation: headerSearchIn 0.18s ease-out;
}
/* Desktop browsers already surface an install button in the address bar, so
   ours would just be redundant clutter there. Keep the header icon mobile/tablet
   only; the menu button stays as the desktop + iOS fallback. */
@media (min-width: 1024px) {
    .header-install-btn.show { display: none; }
}

/* Pulse ring on the install icon while the coachmark is visible */
.header-install-btn.nudge {
    animation: installNudge 1.6s ease-out 4;
}
@keyframes installNudge {
    0% { box-shadow: 0 0 0 0 rgba(254, 192, 7, 0.75); }
    70% { box-shadow: 0 0 0 10px rgba(254, 192, 7, 0); }
    100% { box-shadow: 0 0 0 0 rgba(254, 192, 7, 0); }
}

/* PWA install coachmark — fixed tooltip positioned under the header install
   icon by JS (left/top + --arrow-x are set inline). Shown once per visit, max
   twice per device, only while a real native install prompt is available. */
.pwa-coachmark {
    display: none;
    position: fixed;
    z-index: 9990;
    align-items: center;
    gap: 8px;
    max-width: min(300px, calc(100vw - 24px));
    padding: 10px 14px;
    background: var(--bg-card);
    border: 2px solid var(--primary-accent);
    border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
    color: var(--text-primary);
    font-size: 0.85rem;
    font-weight: 700;
    line-height: 1.5;
    cursor: pointer;
}
.pwa-coachmark.show {
    display: flex;
    animation: coachmarkIn 0.25s ease-out;
}
@keyframes coachmarkIn {
    from { opacity: 0; transform: translateY(-6px); }
    to { opacity: 1; transform: translateY(0); }
}
.pwa-coachmark::before {
    content: '';
    position: absolute;
    top: -9px;
    left: var(--arrow-x, 50%);
    width: 14px;
    height: 14px;
    margin-left: -7px;
    transform: rotate(45deg);
    background: var(--bg-card);
    border-top: 2px solid var(--primary-accent);
    border-left: 2px solid var(--primary-accent);
}
.pwa-coachmark > svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    color: var(--primary-accent);
}
.pwa-coachmark > span {
    white-space: nowrap;
}
.pwa-coachmark-close {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    background: transparent;
    border: none;
    border-radius: 6px;
    color: var(--text-secondary);
    cursor: pointer;
}
.pwa-coachmark-close svg {
    width: 14px;
    height: 14px;
}

/* Narrow phones: the header holds up to 4 icon buttons next to the logo and
   the brand text was wrapping onto multiple lines. Tighten paddings, gaps and
   icon size to buy the title ~50px back. */
@media (max-width: 430px) {
    .header-sticky .container {
        padding-left: 0.75rem;
        padding-right: 0.75rem;
    }
    .header-sticky nav {
        gap: 0.5rem;
    }
    .header-sticky .hamburger-btn,
    .header-sticky .favorite-btn-header,
    .header-sticky .tools-picker-btn,
    .header-sticky .header-search-btn,
    .header-sticky .header-install-btn {
        width: 36px;
        height: 36px;
    }
}
/* Homepage header search icon — hidden until the on-page search box scrolls
   out of view behind the sticky header (JS toggles .visible). */
.header-search-btn { display: none; }
.header-search-btn.visible {
    display: flex;
    animation: headerSearchIn 0.18s ease-out;
}
@keyframes headerSearchIn {
    from { opacity: 0; transform: scale(0.85); }
    to   { opacity: 1; transform: scale(1); }
}
.tools-picker-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 1001;
    opacity: 0;
    visibility: hidden;
    transition: all 0.3s ease;
}
.tools-picker-overlay.active {
    opacity: 1;
    visibility: visible;
}
.tools-picker-drawer {
    position: fixed;
    top: 0;
    inset-inline-end: 0;
    width: 360px;
    max-width: 100%;
    height: 100%;
    background: var(--bg-card);
    border-inline-start: 1px solid var(--card-border);
    z-index: 1002;
    transform: translateX(100%);
    transition: transform 0.3s ease;
    display: flex;
    flex-direction: column;
}
[dir="rtl"] .tools-picker-drawer {
    transform: translateX(-100%);
}
.tools-picker-drawer.active,
[dir="rtl"] .tools-picker-drawer.active {
    transform: translateX(0);
}
.tools-picker-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--card-border);
    flex-shrink: 0;
}
.tools-picker-title {
    font-weight: 700;
    color: var(--text-primary);
}
.tools-picker-close {
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--card-border);
    border-radius: 8px;
    background: var(--bg-card);
    color: var(--text-primary);
    cursor: pointer;
    transition: all 0.2s;
}
.tools-picker-close:hover {
    background: var(--primary-accent);
    border-color: var(--border-color);
    color: var(--icon-color);
}
.tools-picker-body {
    flex: 1;
    overflow-y: auto;
    padding: 0.5rem 0.75rem 0.75rem;
}
.tools-picker-category {
    margin-top: 0.75rem;
}
.tools-picker-category:first-child {
    margin-top: 0;
}
.tools-picker-cat-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.25rem;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}
.tools-picker-cat-header svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}
.tools-picker-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.25rem;
}
.tools-picker-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.7rem;
    border-radius: 0.6rem;
    background: var(--bg-input);
    color: var(--text-primary);
    text-decoration: none;
    font-size: 0.82rem;
    font-weight: 600;
    transition: all 0.15s ease;
    min-height: 44px;
}
.tools-picker-item:hover {
    background: var(--primary-accent);
    color: #202935;
}
.tools-picker-item.is-current {
    background: var(--bg-input); /* fallback: color-mix() needs Chromium 111+ (Smart-TV browsers) */
    background: color-mix(in srgb, var(--primary-accent) 20%, var(--bg-input));
    cursor: default;
    pointer-events: none;
}
.tools-picker-item-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    /* Force monochrome: icons that embed var(--primary-accent) render in item text color */
    --primary-accent: currentColor;
}
.tools-picker-item-icon svg {
    width: 20px;
    height: 20px;
}
.tools-picker-text-icon {
    font-weight: 900;
    font-size: 11px;
    line-height: 1;
}
.tools-picker-item-title {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.25;
    min-width: 0;
    word-break: break-word;
}
.tools-picker-see-all {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    margin-top: 0.5rem;
    padding: 0.55rem 1rem;
    border: 2px dashed var(--border-color);
    border-radius: 0.6rem;
    background: transparent;
    color: var(--text-secondary);
    text-decoration: none;
    font-size: 0.85rem;
    font-weight: 600;
    transition: all 0.15s ease;
}
.tools-picker-see-all:hover {
    background: var(--bg-input);
    color: var(--text-primary);
    border-style: solid;
}
.tools-picker-see-all svg {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

/* Bottom sheet on small screens */
@media (max-width: 640px) {
    .tools-picker-drawer {
        top: auto;
        inset-inline-end: 0;
        inset-inline-start: 0;
        bottom: 0;
        width: 100%;
        max-width: 100%;
        height: auto;
        max-height: 92vh;
        border-inline-start: none;
        border-top: 1px solid var(--card-border);
        border-radius: 1rem 1rem 0 0;
        transform: translateY(100%);
    }
    [dir="rtl"] .tools-picker-drawer {
        transform: translateY(100%);
    }
    .tools-picker-drawer.active,
    [dir="rtl"] .tools-picker-drawer.active {
        transform: translateY(0);
    }
}

/* Free up header space on narrow phones when the tool-picker button is present */
@media (max-width: 640px) {
    .header-sticky.header-has-title .header-title {
        font-size: 0.85rem;
        line-height: 1.45;
        white-space: normal;
        max-width: 110px;
        max-height: 3rem;
        word-break: break-word;
        overflow: hidden;
    }
}
.header-title-brand {
    white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
    .tools-picker-drawer,
    .tools-picker-overlay {
        transition-duration: 0.01s;
    }
}

/* ==========================================================================
   Tool Card Enhancements — favorite heart, popular pill, hover arrow
   ========================================================================== */
.tool-fav-btn {
    width: 22px;
    height: 22px;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
    border-radius: 999px;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0.35;
    transition: opacity 0.18s ease-out, color 0.18s ease-out, background-color 0.18s ease-out, transform 0.18s ease-out;
    flex-shrink: 0;
}
.tool-fav-btn svg { width: 16px; height: 16px; }
.tool-card:hover .tool-fav-btn,
.tool-widget:hover .tool-fav-btn,
.tool-fav-btn:hover,
.tool-fav-btn:focus-visible,
.tool-fav-btn.is-fav {
    opacity: 1;
}
@media (hover: none) {
    .tool-fav-btn { opacity: 1; }
}
.tool-fav-btn:hover {
    background: #fef2f2;
    color: #ef4444;
    transform: scale(1.15);
}
.tool-fav-btn.is-fav { color: #ef4444; }
.tool-fav-btn.is-fav svg { fill: currentColor; }
:root.dark .tool-fav-btn:hover,
:root.dark .tool-fav-btn.is-fav {
    background: transparent;
    color: #f87171;
}

.widget-badge-group {
    margin-inline-start: auto;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.card-arrow {
    position: absolute;
    bottom: 12px;
    inset-inline-end: 14px;
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--text-muted);
    opacity: 0;
    transform: translateX(4px);
    transition: opacity 0.25s ease-out, transform 0.25s ease-out, color 0.25s ease-out;
    line-height: 1;
    pointer-events: none;
}
[dir="rtl"] .card-arrow { transform: translateX(-4px); }
.tool-card:hover .card-arrow {
    opacity: 1;
    transform: translateX(0);
    color: var(--primary-accent);
}
.tool-card:hover { border-color: var(--primary-accent); }

/* Site-wide: slightly smaller base font on mobile so dense layouts feel
   proportionate. Subtle (1rem → 0.95rem) so we don't shrink interactive
   targets or break tool pages that hard-code sizes. */
@media (max-width: 639px) {
    html { font-size: 0.95rem; }
}

/* ==========================================================================
   Tool Editor Scaffold — shared input/output editor layout used by base64,
   code-beautifier, json-formatter, diff-checker. The responsive column count
   differs per tool, so each page keeps its own `@media` override on
   `.editor-container`; the base (single-column) grid lives here.
   ========================================================================== */
.editor-container {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
}
.editor-box {
    display: flex;
    flex-direction: column;
}
.editor-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.5rem;
    flex-wrap: wrap;
    gap: 0.5rem;
}
.editor-label {
    font-weight: 700;
    color: var(--text-primary);
}
.editor-actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
}
.btn-small {
    padding: 0.35rem 0.75rem;
    font-size: 0.8rem;
    font-weight: 600;
    border: 1px solid var(--border-color);
    border-radius: 0.375rem;
    background: var(--bg-card);
    color: var(--text-primary);
    cursor: pointer;
    transition: all 0.2s;
}
.btn-small:hover {
    background: var(--primary-accent);
}

/* ==========================================================================
   Upload drop-zone — shared dashed-border file-upload box.
   Used by image-optimizer, favicon-generator, image-to-pdf, pdf-merge.
   Tools keep their own icon / text / loading styling locally.
   The active-drag state is `.drop-zone.drag-over`.
   ========================================================================== */
.drop-zone {
    border: 2px dashed var(--border-color);
    border-radius: 1rem;
    padding: 3rem 2rem;
    text-align: center;
    cursor: pointer;
    transition: all 0.2s;
    background-color: var(--bg-card);
}
.drop-zone:hover,
.drop-zone.drag-over {
    border-color: var(--primary-accent);
    background-color: var(--bg-input);
}

/* Once files are chosen the zone stays put as the add/replace target, but
   shrinks so it stops dominating the page. Shared by the compressor family
   (image-optimizer, audio-converter-shell, video-compressor) — keep them
   behaving alike. Only the first line of copy survives the collapse. */
.drop-zone.is-compact,
.ac-drop-zone.is-compact {
    padding: 1.1rem 1rem;
}
.drop-zone.is-compact > svg,
.ac-drop-zone.is-compact > svg {
    width: 2rem;
    height: 2rem;
    margin-bottom: 0.5rem;
}
.drop-zone.is-compact p ~ p,
.ac-drop-zone.is-compact p ~ p {
    display: none;
}

/* ==========================================================================
   Drop-zone placeholder — shared file-drop target overlaid on an input.
   Used by base64, code-beautifier, code-compressor, json-formatter,
   diff-checker. Supports two label conventions: `.hint` (single line) and
   `.main-text`/`.sub-text`/`.formats-text` (multi-line).
   ========================================================================== */
.drop-zone-placeholder {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
    border: 2px dashed var(--border-color);
    border-radius: 0.5rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    transition: all 0.2s;
    background-color: var(--bg-input);
    padding: 1rem;
    cursor: text;
}
.drop-zone-placeholder.drag-over {
    border-color: var(--primary-accent);
    background-color: var(--primary-accent);
    opacity: 0.3;
}
.drop-zone-placeholder svg {
    width: 48px;
    height: 48px;
    color: var(--text-secondary);
    opacity: 0.5;
    margin-bottom: 0.75rem;
}
.drop-zone-placeholder p {
    color: var(--text-secondary);
    margin: 0.25rem 0;
}
.drop-zone-placeholder .main-text {
    font-weight: 600;
}
.drop-zone-placeholder .sub-text {
    font-size: 0.9rem;
    opacity: 0.8;
}
.drop-zone-placeholder .formats-text {
    font-size: 0.8rem;
    opacity: 0.6;
    margin-top: 0.75rem;
}
.drop-zone-placeholder .hint {
    font-size: 0.8rem;
    opacity: 0.7;
    margin-top: 0.5rem;
}

/* ============================================================
   Shared tooltip — desktop hover, mobile tap.
   Markup:  <span class="tooltip-container">
              <chip/>
              <span class="tooltip-text">Tooltip body</span>
            </span>
   JS auto-toggle lives in footer.php (delegated click).
   ============================================================ */
.tooltip-container {
    position: relative;
    display: inline-block;
}
.tooltip-text {
    visibility: hidden;
    width: max-content;
    max-width: 240px;
    background-color: #1e293b;
    color: #fff;
    text-align: center;
    border-radius: 6px;
    padding: 8px;
    position: absolute;
    z-index: 10;
    bottom: 125%;
    left: 50%;
    transform: translateX(-50%);
    opacity: 0;
    transition: opacity 0.3s;
    font-size: 0.8rem;
    font-weight: 500;
    line-height: 1.6;
    pointer-events: none;
}
/* Beside variant — for triggers at the very top corner of a card (e.g. the
   «جدید» dot): the default above-placement floats over the previous grid
   row and a below-placement covers the title, so this one opens on the
   inline side toward the card center (right of the dot in RTL, left in
   LTR), vertically centered on the trigger. */
.tooltip-container.tooltip-beside .tooltip-text {
    bottom: auto;
    top: 50%;
    left: auto;
    inset-inline-end: 150%;
    transform: translateY(-50%);
}
@media (hover: hover) and (pointer: fine) {
    .tooltip-container:hover .tooltip-text {
        visibility: visible;
        opacity: 1;
    }
}
@media (hover: none), (pointer: coarse) {
    .tooltip-text {
        max-width: min(240px, 70vw);
        white-space: normal;
    }
    .tooltip-text.show {
        visibility: visible;
        opacity: 1;
    }
}

/* ── Samsung Internet «Dark sites» ────────────────────────────────────────────
   Samsung recolours CSS colours but never touches images, and it ignores the
   color-scheme opt-out in header.php. The body's paper texture therefore stayed
   light under a fully inverted page. Dropping the texture on Samsung Internet
   (class set in header.php's theme bootstrap) leaves a flat background colour,
   which Samsung recolours along with the rest — light mode still looks light,
   and «Dark sites» now produces a consistent dark page instead of a grey wash.
   Cost: no paper texture on Samsung Internet. */
:root.sbrowser body,
:root.sbrowser.dark body {
    background-image: none;
}
/* Hero logo on Samsung Internet: the inline SVG gets recoloured (dark box body →
   light grey plate), the <img> copy in index.php does not. Swap them there. */
.hero-logo-img { display: none; width: var(--hero-logo-size); height: var(--hero-logo-size); }
:root.sbrowser .hero-logo { display: none; }
:root.sbrowser .hero-logo-img { display: block; }
