/* ==========================================================================
   base.css — Sales Booster design tokens and core components
   --------------------------------------------------------------------------
   Single source of truth for color, radius, type and buttons.
   Loaded first in base.html, before any per-page {% block head_css %}.

   Decisions recorded 2026-08-07 (see .claude/skills/frontend-salesbooster):
     neutrals  Tailwind gray ramp
     brand     #F5A623
     semantics Apple (success #34C759 / danger #FF3B30 / warning #FF9500)
     buttons   compact — 8px 16px, radius 8px, 14px/600
     naming    .button-* (never .btn-*)
     radius    8px base, 12/16 above it
     type      Manrope with system fallbacks
     tabs      underlined .context-tabs — never a segmented pill (2026-08-17)

   When adding a value here, add it as a token. Never hardcode a hex in a
   template — if a shade is missing from the ramp, extend the ramp.
   ========================================================================== */

:root {
    /* ---- neutral ramp — Tailwind gray -------------------------------- */
    --gray-50:  #F9FAFB;
    --gray-100: #F3F4F6;
    --gray-200: #E5E7EB;
    --gray-300: #D1D5DB;
    --gray-400: #9CA3AF;
    --gray-500: #6B7280;
    --gray-600: #4B5563;
    --gray-700: #374151;
    --gray-800: #1F2937;
    --gray-900: #111827;

    /* ---- brand ------------------------------------------------------- */
    --brand-orange: #F5A623;
    --brand-orange-hover: #E09612;   /* pressed / hover fill */
    --brand-orange-ink: #B26A00;     /* orange TEXT on white — #F5A623 is 2.03:1 and unreadable */
    --brand-orange-tint: #FEF6E7;    /* selected rows, soft highlights */
    --brand-orange-rgb: 245, 166, 35; /* for rgba() when an alpha is needed */
    --brand-black: #1D1D1F;

    /* Marketplace identity colors — used to tag a listing/sale by origin.
       These are the partners' brand colors, not ours; do not restyle them. */
    --ml-yellow: #FFE600;
    --shopee-orange: #EE4D2D;
    --shopee-orange-hover: #E04A11;

    /* ---- text and surfaces ------------------------------------------- */
    --text-primary: #111827;
    --text-secondary: #6B7280;
    --text-muted: #9CA3AF;
    --background-color: #FFFFFF;
    --container-bg: #FFFFFF;
    --surface-subtle: var(--gray-50);

    /* border-color stays deliberately faint (legacy app-wide look).
       Use --border-strong wherever an edge must actually be visible:
       buttons, inputs, table cells. */
    --border-color: rgba(0, 0, 0, 0.05);
    --card-border-color: rgba(0, 0, 0, 0.05);
    --border-strong: var(--gray-200);

    /* ---- semantic — Apple palette ------------------------------------ */
    /* *-base   fills, dots, icons
       *-ink    text on the matching tint (all >= 4.5:1)
       *-tint   badge / soft button background
       *-border tint outline                                              */
    --success-base:   #34C759;
    --success-ink:    #1E7A34;
    --success-tint:   #E8FAEE;
    --success-border: #A7E8BC;

    --danger-base:    #FF3B30;
    --danger-ink:     #D70015;
    --danger-tint:    #FFEBEA;
    --danger-border:  #FFC5C1;

    --warning-base:   #FF9500;
    --warning-ink:    #A85600;
    --warning-tint:   #FFF4E5;
    --warning-border: #FFD9A0;

    --info-base:      #0066CC;
    --info-ink:       #0F4C91;
    --info-tint:      #EAF3FC;
    --info-border:    #B9D6F2;

    --accent-blue: #0066CC;

    /* ---- lightning — the Mercado Livre flash offer -------------------- */
    /* Yellow and not the warning orange: the two sit side by side in the
       same pill row of an ad card, and a flash offer is not a warning. Same
       four parts as the semantic families above; the ink clears 4.5:1 on
       the tint.                                                          */
    --lightning-base:   #EAB308;
    --lightning-ink:    #854D0E;
    --lightning-tint:   #FEF9C3;
    --lightning-border: #FDE047;

    /* ---- radius — 8px base, scale 8 / 12 / 16 ------------------------ */
    --radius-sm: 6px;      /* chips, icon buttons                         */
    --radius: 8px;         /* DEFAULT — buttons, inputs, small cards      */
    --radius-card: 12px;   /* cards, panels                               */
    --radius-modal: 16px;  /* modals                                      */
    --radius-pill: 999px;  /* pills — note: 99px also appears in old code */

    /* Legacy container tokens. analytics.html and metas.css depend on
       these exact values (13 usages); changing them reflows those pages.
       Prefer --radius-card / --radius-modal in new code. */
    --radius-md: 16px;
    --radius-lg: 24px;

    /* ---- controls ----------------------------------------------------
       One height for everything that sits on a control row. Before this
       token a .button computed to 37.6px and a .form-control to 43px, so a
       button next to an input was always ~5px short and the row read as
       broken. Both now resolve to exactly this. */
    --control-height: 40px;
    --control-height-sm: 32px;   /* .button-sm, compact rows */
    /* A floating-label field is taller on purpose: the shrunken label sits
       inside the box, above the value, so it needs its own height. */
    --control-height-floating: 56px;

    /* ---- app shell ---------------------------------------------------
       The left rail carries navigation; the top bar carries only account,
       jobs and profile. Both sizes are tokens because <body>, the fixed top
       bar and every full-screen loading overlay have to agree on them.
       The rail is --rail-width-collapsed at rest and --rail-width while the
       pointer is on it; only the <aside> itself reads the wide one. */
    --rail-width: 236px;
    --rail-width-collapsed: 68px;
    --topbar-height: 56px;

    /* ---- type -------------------------------------------------------- */
    --font-sans: 'Manrope', -apple-system, BlinkMacSystemFont, system-ui, 'Segoe UI', sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

    --text-xs: 12px;
    --text-sm: 13px;
    --text-base: 14px;   /* DEFAULT for controls and body copy in the app */
    --text-lg: 16px;
    --text-xl: 20px;
    --text-2xl: 24px;

    /* ---- spacing ----------------------------------------------------- */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;

    /* ---- charts ------------------------------------------------------
       Categorical series colors. Line 1 is the brand orange so the primary
       series matches the rest of the UI; the remaining nine are ordered for
       adjacent-hue separation. charts.css consumes these — do not redeclare
       them there. */
    --chart-line-1:  var(--brand-orange);
    --chart-line-2:  #16A34A;
    --chart-line-3:  #6366F1;
    --chart-line-4:  #EC4899;
    --chart-line-5:  #0EA5E9;
    --chart-line-6:  #8B5CF6;
    --chart-line-7:  #F43F5E;
    --chart-line-8:  #14B8A6;
    --chart-line-9:  #D97706;
    --chart-line-10: #64748B;

    /* Categorical FILL palette — stacked bars and areas, where the color is a
       surface and not a 2px stroke. The --chart-line-* set is tuned for lines
       and does not survive being filled: --chart-line-1 reads 1.97:1 against
       white, and six of those hues shown at once fail the colorblind
       separation check. These are the Okabe–Ito steps, verified with the
       dataviz validator over ALL pairs on a white surface — chroma and
       normal-vision floors pass; CVD separation and contrast land in the
       warn band, which the legend, the direct labels and the table that
       accompanies every chart answer for. Assign them in order, never cycled;
       the seventh category is --chart-fill-other, which is deliberately
       neutral because it means "everything else". */
    --chart-fill-1: #D55E00;
    --chart-fill-2: #0072B2;
    --chart-fill-3: #CC79A7;
    --chart-fill-4: #009E73;
    --chart-fill-5: #E69F00;
    --chart-fill-6: #56B4E9;
    /* = --gray-500, written out because chart code reads these through
       getComputedStyle and hands them straight to a canvas. */
    --chart-fill-other: #6B7280;

    --chart-bg:        var(--container-bg);
    --chart-border:    var(--gray-200);
    --chart-grid:      var(--gray-100);
    --chart-grid-text: var(--text-muted);
    --chart-axis-text: var(--text-secondary);
    --chart-radius:    var(--radius-card);
    --chart-dot-radius: 4px;
    --chart-transition: var(--transition);

    /* ---- elevation --------------------------------------------------- */
    --shadow-soft: 0 4px 24px rgba(0, 0, 0, 0.04);
    --shadow-hover: 0 10px 40px rgba(0, 0, 0, 0.08);
    --shadow-modal: 0 10px 30px rgba(0, 0, 0, 0.2);

    --focus-ring: 0 0 0 3px rgba(245, 166, 35, 0.35);
    --transition: 0.2s ease;
}


/* ==========================================================================
   HIDDEN
   The `hidden` attribute only gets `display: none` from the user-agent sheet,
   so ANY component here that sets its own `display` silently defeats it —
   `.button` (inline-flex), `.context-tab` (inline-flex), `.empty-state`
   (flex), `.modal-overlay.active` (flex). `el.hidden = true` then does
   nothing, which is a bug that looks like a JavaScript bug and is not one.
   `!important` is deliberate: `hidden` means "not in the page", and no
   component's layout should be able to argue with it.
   ========================================================================== */

[hidden] { display: none !important; }


/* ==========================================================================
   APP SHELL — fixed left rail + slim top bar
   --------------------------------------------------------------------------
   Replaces the horizontal .navbar (2026-08-23). Navigation had grown to ten
   destinations plus five utilities and needed ~1560px to fit on one line, so
   it broke on every laptop between the 768px hamburger breakpoint and that
   width. The rail grows downward, which is the only axis that is not a fixed
   budget, and it groups the destinations without hiding any of them.

   Structure (base.html):
     <header class="app-topbar"> logo, page title, account, jobs, profile
     <aside class="app-rail">   grouped nav, starting under the top bar
     <body> pays for both with padding-left / padding-top.

   The top bar spans the full width and the rail hangs under it (2026-08-27).
   The logo lives in the bar and no longer changes with the rail's state: it
   was the one thing on screen that moved every time the pointer crossed the
   left edge, and a brand that redraws itself on hover reads as a glitch.

   Three states: at rest it is a 68px strip of icons, hovering (or tabbing
   into) it widens it to 236px over the page — dimming and blurring the rest
   through .rail-scrim, the same treatment a modal gets — and below 1024px it
   is an off-canvas drawer that is always full width.
   ========================================================================== */

/* Both are fixed, so the page pays for them here. Two reasons this lives in
   base.css and not in base.html's <style>: the drawer media query at the
   bottom of this block has to be able to win, and a template rule always
   loads later. The .app-shell class is what keeps it off base_public.html,
   which loads this same file for the tokens. */
body.app-shell {
    /* How much horizontal space the rail actually takes from the page. It is
       declared here and not on :root so that the drawer breakpoint can zero
       it in one line. Anything that has to clear the rail (a
       full-screen loading overlay, the top bar) reads --rail-offset, never
       --rail-width.

       On the desktop this is the *narrow* width and it never changes: the
       rail widens on hover over the page instead of pushing it, because
       reflowing the whole content area every time the pointer crosses the
       left edge is unusable. */
    --rail-offset: var(--rail-width-collapsed);
    padding-top: var(--topbar-height);
    padding-left: var(--rail-offset);
    /* The shell is tinted and the page is white (2026-08-27). Barra and rail
       used to be white too, which left three white planes with only borders
       between them — and the borders were the whole problem: four of them met
       in the top-left corner at three different weights. There is now nothing
       to align, because there are no lines. See .app-canvas below. */
    background-color: var(--gray-100);
    transition: padding-left var(--transition), padding-top var(--transition);
}

/* The page. It is the only white surface in the shell, and the single rounded
   corner where the bar and the rail meet is what replaced the four lines that
   used to cross there.

   It bleeds to the right and bottom edges on purpose (decided 2026-08-27): a
   plane that also floated away from those edges would need four corners and
   would stop reading as "the page", which is the one thing it has to say. The
   1400px reading width moved down to .main-container inside it, so nothing
   about the content's measure changed.

   It must fill the viewport even when the page is short, or the tint reappears
   under it as a stripe. dvh for the same reason it is used everywhere else:
   100vh is taller than the visible area on a phone. */
.app-canvas {
    background-color: var(--container-bg);
    border-radius: var(--radius-modal) 0 0 0;
    min-height: calc(100vh - var(--topbar-height));
    min-height: calc(100dvh - var(--topbar-height));
}

.app-rail {
    position: fixed;
    /* Under the bar, not beside it: the bar owns the full width now.
       The 8px the open panel needs above and below is held from the start
       (2026-08-27): at rest the rail is transparent, so the gap costs nothing
       and cannot be seen — but if it were added on hover, opening would drop
       the whole menu 8px at the same time as it widened. Only `left` still
       moves, so the gesture reads as one direction. */
    top: calc(var(--topbar-height) + var(--space-2));
    left: 0;
    bottom: var(--space-2);
    /* Below 1000 on purpose. The old .navbar sat at 1000 and every modal in
       the app was written to clear it — campanhas, produtos and the settings
       pages use 1000–1010. A rail above those would punch a white column
       through their overlay. */
    z-index: 900;
    width: var(--rail-width-collapsed);
    display: flex;
    flex-direction: column;
    /* Same tint as the shell: at rest the rail IS the ground, not a column
       drawn on it. It turns into a floating white panel when it opens — see
       the desktop block below. */
    background-color: var(--gray-100);
    /* O conteúdo é sempre diagramado na largura cheia (ver .rail-head e
       .rail-nav) e sobra para fora enquanto o trilho está estreito. */
    overflow: hidden;
    transition: width var(--transition), transform 0.3s cubic-bezier(0.23, 1, 0.32, 1),
                inset 0.2s ease, background-color 0.2s ease, border-radius 0.2s ease,
                box-shadow 0.2s ease;
}

/* Drawer only. On the desktop the brand sits in the top bar (.topbar-brand);
   the drawer has no top bar above it to sit in, so it keeps its own head —
   and being always full width, it never needs the square mark. */
.rail-head {
    display: none;
    align-items: center;
    box-sizing: border-box;
    width: var(--rail-width);
    /* Sized by its own padding, not by --topbar-height: on a phone that token
       now changes with the scroll and would take the drawer's head with it. */
    padding: 14px 18px;
    flex-shrink: 0;
}

.rail-logo {
    display: flex;
    align-items: center;
}

.rail-logo-full {
    /* Same trimmed artwork as .topbar-brand img; see the note there. */
    height: 32px;
    display: block;
}

.rail-nav {
    /* A largura NÃO acompanha a do trilho. Diagramar esta lista dentro de
       68px é o que fazia "Estratégias, custos e impostos" quebrar uma letra
       por linha durante a abertura; aqui ela nasce com 236px, o aside
       recorta o que sobra e a animação só descobre o que já estava pronto. */
    box-sizing: border-box;
    width: var(--rail-width);
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    padding: var(--space-3) 15px var(--space-3);
}

/* Group heading. At rest the words are transparent and the empty box is what
   separates the groups — the 38px rule that used to stand in for them was
   dropped on 2026-08-27 along with every other line in the shell. The box is
   kept in both states, so opening the rail moves nothing vertically. */
.rail-group {
    position: relative;
    margin: var(--space-4) 0 var(--space-1);
    padding: 0 10px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    white-space: nowrap;
}


.rail-nav > .rail-group:first-child {
    margin-top: var(--space-1);
}

.rail-item {
    /* .sb-tooltip-wrapper (ui.css) is inline-flex and loads after this file,
       so the two-class selector below is what keeps the item full width. */
    position: relative;
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 8px 10px;
    border-radius: var(--radius-pill);
    font-size: var(--text-base);
    font-weight: 500;
    color: var(--text-secondary);
    text-decoration: none;
    white-space: nowrap;
    /* The background lives on ::before, below. */
    transition: color var(--transition);
}

.rail-item svg {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

/* A count on a rail item (the Central de Ações: overdue + today). It sits on
   the icon's corner so it reads the same in the 68px strip and the open rail.
   Dark ink on the brand fill: white on orange is 2:1. */
.rail-badge {
    position: absolute;
    top: 1px;
    left: 20px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--radius-pill);
    background-color: var(--brand-orange);
    color: var(--gray-900);
    font-size: var(--text-xs);
    font-weight: 700;
    line-height: 18px;
    text-align: center;
    pointer-events: none;
    z-index: 1;
}

/* Something is late: the count turns red. The -ink red, not -base: white on
   -base is 3.6:1, on -ink it clears 4.5:1 for these 12px digits. */
.rail-badge.is-overdue {
    background-color: var(--danger-ink);
    color: #FFFFFF;
}

/* The highlight is a layer of its own, because the row is not the shape the
   highlight has to be. .rail-nav lays every row out at the full 236px in both
   states and the aside clips what does not fit, so a background painted on the
   row itself never ends at rest: it runs to the cut and reads as a bar
   escaping to the right instead of a button under the icon. This overlay
   narrows to 38px while the strip does (see the desktop block below) — the
   same width .rail-group::after uses, which is what centres it on the icon. */
.rail-item::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    width: 100%;
    border-radius: var(--radius-pill);
    background-color: transparent;
    transition: background-color var(--transition), width var(--transition);
}

/* The overlay is positioned and the icon and the label are not, so they would
   paint underneath it. Relative is enough: both are then painted in tree
   order, and ::before comes first. */
.rail-item > svg,
.rail-item > .rail-label {
    position: relative;
}

.rail-item:hover {
    color: var(--text-primary);
    text-decoration: none;
}

.rail-item:hover::before {
    /* One step darker than --gray-100: at rest the rail's own ground is
       --gray-100, so the old hover simply did not show. */
    background-color: var(--gray-200);
}

.rail-item.active {
    color: var(--brand-orange-ink);
    font-weight: 700;
}

.rail-item.active::before {
    background-color: var(--brand-orange-tint);
}

/* A page the plan does not include. Not a link, so it has no href and no
   hover — the tooltip explains why. */
.rail-item.disabled {
    color: var(--text-muted);
}

.rail-item.disabled:hover {
    color: var(--text-muted);
}

.rail-item.disabled:hover::before {
    background-color: transparent;
}

/* ---- a section with sub-pages ------------------------------------------
   The whole row — chevron included — opens the group, and the page of the
   section is the first item inside it ("Gestão de …"). One target and not
   two, because opening is the only intention the row has to serve: going to
   the page is one of the things inside. The row still carries `active` for
   the section, because at rest the icon is the only thing on screen. */

.rail-branch-main {
    width: 100%;
    border: none;
    background: none;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

.rail-branch-main .rail-branch-chevron {
    width: 16px;
    height: 16px;
    margin-left: auto;
    color: var(--text-muted);
    transition: transform var(--transition);
}

.rail-branch.open .rail-branch-chevron {
    transform: rotate(90deg);
}

/* 0fr -> 1fr animates the sub-list open without measuring anything in JS. */
.rail-branch-panel {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.22s ease;
}

.rail-branch.open .rail-branch-panel {
    grid-template-rows: 1fr;
}

/* The rule sits under the parent icon and ties the sub-items to it. */
.rail-sub {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    margin-left: 19px;
    padding-left: 9px;
    border-left: 1px solid var(--border-strong);
}

.rail-branch.open .rail-sub {
    margin-bottom: var(--space-2);
}

/* These labels are full sentences ("Estratégias, custos e impostos") and the
   rail is 236px wide, so they wrap instead of being cut off — renaming a
   feature to fit the menu is the wrong trade. */
.rail-subitem {
    padding: 6px 10px;
    border-radius: var(--radius);
    font-size: var(--text-sm);
    font-weight: 500;
    line-height: 1.35;
    color: var(--text-secondary);
    text-decoration: none;
}

.rail-subitem:hover {
    background-color: var(--gray-100);
    color: var(--text-primary);
    text-decoration: none;
}

.rail-subitem.active {
    color: var(--brand-orange-ink);
    font-weight: 700;
}

/* Same treatment as .rail-item.disabled: a page outside the plan stays
   visible and inert, and the tooltip says why. */
.rail-subitem.disabled {
    color: var(--text-muted);
}

/* ---- at rest and on hover (desktop) ------------------------------------
   There is no pin control any more (2026-08-26): keeping the menu open was a
   decision the seller had to make once and then live with, and the rail is
   only ever needed for the second it takes to pick a destination.

   Nothing in here changes the *layout* of the rail's contents: .rail-head and
   .rail-nav are 236px wide in both states and the aside clips them. All that
   is left to do is hide the text while the strip is too narrow to show it,
   and bring it back once the width has finished animating — a fade, never a
   reflow. Everything below is scoped to the desktop; the drawer under 1024px
   is always wide and always shows everything. */

@media (min-width: 1025px) {
    .app-rail:hover,
    .app-rail:focus-within {
        width: var(--rail-width);
        /* At rest the rail is part of the tinted ground; open, it detaches
           into a white panel floating on it. That is the whole reason the
           borderless shell works: there is no edge to grow out of and no
           corner to reconcile, so opening reads as a layer arriving. */
        left: var(--space-2);
        background-color: var(--container-bg);
        border-radius: var(--radius-card);
        box-shadow: 0 10px 32px -8px rgba(17, 24, 39, 0.28);
        /* Opening waits a beat: crossing the rail on the way to something
           else should not flash the whole menu open. Closing is immediate,
           because by then the pointer has already left. */
        transition-delay: 0.12s;
    }

    /* Out on the way in, back on the way out. The 0.32s is the open delay
       (0.12s) plus the width animation (0.2s): the words only appear once
       there is room for them, which is what stops the half-cut label from
       sliding into view. */
    .rail-label,
    .rail-subitem,
    .rail-branch-chevron {
        opacity: 0;
        visibility: hidden;
        transition: opacity 0.12s ease, visibility 0.12s ease;
    }

    .app-rail:hover .rail-label,
    .app-rail:hover .rail-subitem,
    .app-rail:hover .rail-branch-chevron,
    .app-rail:focus-within .rail-label,
    .app-rail:focus-within .rail-subitem,
    .app-rail:focus-within .rail-branch-chevron {
        opacity: 1;
        visibility: visible;
        transition-delay: 0.32s;
    }

    /* Same trade for the group heading. At rest it is an empty box that holds
       the spacing between groups; the words fade in with the labels. */
    .rail-group {
        color: transparent;
        transition: color 0.12s ease;
    }

    .app-rail:hover .rail-group,
    .app-rail:focus-within .rail-group {
        color: var(--text-muted);
        transition-delay: 0.32s;
    }

    /* At rest the strip is 68px wide and the row inside it is still 206px, so
       the highlight is cut by the aside instead of ending. It shrinks to a
       button around the icon, and grows back on the same delay as the width
       of the rail so that the two move as one thing. */
    .app-rail:not(:hover):not(:focus-within) .rail-item::before {
        width: 38px;
    }

    .app-rail:hover .rail-item::before,
    .app-rail:focus-within .rail-item::before {
        /* background-color, then width — the order they are declared in. */
        transition-delay: 0s, 0.12s;
    }

    /* An open group folds back up while the rail is at rest — the sub-list
       under a 68px strip of icons is a list of invisible links. It reuses the
       accordion's own 0fr, so it unfolds with the same animation. */
    .app-rail:not(:hover):not(:focus-within) .rail-branch-panel {
        grid-template-rows: 0fr;
    }

    .app-rail:not(:hover):not(:focus-within) .rail-sub {
        margin-bottom: 0;
    }
}

/* ---- top bar ------------------------------------------------------------ */

.app-topbar {
    position: fixed;
    top: 0;
    right: 0;
    /* Edge to edge, over the rail's column too: the rail starts below it. */
    left: 0;
    /* Above the rail (900) and its scrim (895) — the profile menu drops out of
       this bar and must not be dimmed by a rail the pointer wandered into.
       Still below the modals, which start at 1000. */
    z-index: 910;
    height: var(--topbar-height);
    /* The two-row phone bar has vertical padding, and every other piece of the
       shell positions itself from --topbar-height. Without this the bar would
       measure padding taller than the space reserved for it and cover the top
       of the page. */
    box-sizing: border-box;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0 var(--space-5);
    /* Tinted like the rest of the shell and with no bottom border: the white
       page below is what draws the edge now. It still has to be opaque — the
       page scrolls underneath it. */
    background-color: var(--gray-100);
    transition: height var(--transition), padding var(--transition);
}

/* The brand, fixed in the bar. It is laid out over the rail's collapsed
   column, so the eye finds it in the same place the old rail head had it. */
.topbar-brand {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    margin-right: var(--space-2);
    /* No divider between the mark and the page title (2026-08-27). It used to
       land at whatever x the logo file happened to be wide — 43px to the right
       of the rail's edge, near enough to read as a mistake. Space and the
       contrast between artwork and text separate them now. */
    padding-right: var(--space-4);
}

.topbar-brand img {
    display: block;
    /* The file is trimmed to the artwork (2026-08-27), so this is the height
       the mark actually reads at — the previous 40px included about 5px of
       baked-in whitespace above and below, and centred the box rather than
       the mark. 34px in a 56px bar leaves 11px of air on each side. */
    height: 34px;
}

.topbar-right {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.topbar-icon {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    border: none;
    border-radius: var(--radius);
    background: none;
    color: var(--text-secondary);
    cursor: pointer;
}

.topbar-icon:hover {
    /* --gray-100 is the bar's own ground; the hover has to be a step darker. */
    background-color: var(--gray-200);
    color: var(--text-primary);
}

.topbar-icon svg {
    width: 22px;
    height: 22px;
}

/* Two classes: .sb-tooltip-wrapper from ui.css would otherwise force this
   button visible on desktop. */
.topbar-icon.rail-mobile-toggle {
    display: none;
}

/* The brand mark, phone only. The wordmark is 52px wide at the height this bar
   allows, and measured against the longest page names it does not fit: with it
   in place even "Dashboard" is clipped at 360px. The three bars are 22px wide
   at the same height, which the bar can pay for. */
.topbar-mark {
    display: none;
    align-items: center;
    flex: none;
}

.topbar-mark svg {
    display: block;
    width: auto;
    height: 24px;
}

/* ---- page title --------------------------------------------------------- */
/* The page name lives here, next to the account controls, and is rendered from
   {% block page_title %} in base.html. Pages must not repeat it in an <h1>. */

.topbar-title {
    margin: 0;
    transition: font-size var(--transition), margin-top var(--transition);
    font-size: var(--text-lg);
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* A page whose title has a subject can say which one, beside it: "Revisão de
   08/09/2026 · 16 de 930 revisados". It is the same line, so it costs no
   vertical space, and it is quieter than the title so the title still reads
   as the title. The separator comes from the rule and not from the text,
   because the span is empty until the page loads its data — an empty one
   draws nothing. The ellipsis on the title above cuts here first, which is
   the right order: the name survives, the detail is what a narrow screen
   gives up. */
.topbar-title-meta:not(:empty) {
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-secondary);
    letter-spacing: 0;
}

.topbar-title-meta:not(:empty)::before {
    content: "·";
    margin: 0 var(--space-2);
    color: var(--text-muted);
}

/* A header that gave its <h1> to the top bar has only actions left, and they
   stay where they were — on the right. */
.page-header:not(:has(h1, .subtitle, .back-link)),
.content-header:not(:has(h1, .subtitle, .back-link)) {
    justify-content: flex-end;
}

/* ---- tooltips ----------------------------------------------------------- */
/* The bubble itself is .sb-tooltip-msg from ui.css — these rules only decide
   where it may appear. ui.css forces display:inline-flex on any wrapper and
   loads later, so each control that already has a display of its own keeps it
   through a two-class selector (specificity beats source order). */

.task-monitor-btn.sb-tooltip-wrapper {
    display: flex;
}

.profile-btn.sb-tooltip-wrapper {
    display: grid;
}

/* .rail-nav scrolls, and a scroll container clips on both axes — an absolutely
   positioned bubble inside it is cut off at the rail's edge. So the rail's
   tooltips are `fixed` and get their coordinates from JS on hover; ui.css
   still owns how they look. They are also not .sb-tooltip-wrapper, because
   the shared smart-position script would immediately reset those coordinates. */
.sb-tooltip-msg.rail-tip {
    /* Two classes on purpose: ui.css sets position: absolute on .sb-tooltip-msg
       and loads after this file, so a single-class rule here would lose. */
    position: fixed;
    top: auto;
    bottom: auto;
    left: auto;
    right: auto;
    transform: none;
}

/* Hovering an item is also what widens the rail, so its label is on screen by
   the time the pointer lands — a tooltip repeating it would be noise. Only
   the plan-blocked items show one, because their message is not the label. */
.rail-item.disabled:hover .rail-tip,
.rail-subitem.disabled:hover .rail-tip {
    visibility: visible;
    opacity: 1;
}

/* ---- profile menu ------------------------------------------------------- */
/* Conexões, Configurações and Sair used to be three permanent icons in the
   bar. They are low-frequency destinations, so they live behind one control. */

.profile {
    position: relative;
}

.profile-btn {
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    background-color: var(--container-bg);
    color: var(--text-secondary);
    cursor: pointer;
}

.profile-btn:hover {
    border-color: var(--brand-orange);
    color: var(--text-primary);
}

.profile-btn svg {
    width: 19px;
    height: 19px;
}

.profile-menu {
    display: none;
    position: absolute;
    right: 0;
    top: calc(100% + 8px);
    min-width: 232px;
    padding: 6px;
    background-color: var(--container-bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-soft);
    z-index: 1100;
}

.profile-menu.open {
    display: block;
}

.profile-menu a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-radius: var(--radius);
    font-size: var(--text-base);
    color: var(--text-primary);
    text-decoration: none;
}

.profile-menu a svg {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    color: var(--text-secondary);
}

.profile-menu a:hover {
    background-color: var(--gray-100);
    text-decoration: none;
}

.profile-menu a.active {
    color: var(--brand-orange-ink);
    font-weight: 600;
}

.profile-menu-sep {
    height: 1px;
    margin: 6px 12px;
    background-color: var(--border-color);
}

/* ---- drawer below 1024px ------------------------------------------------ */
/* The old breakpoint was 768px, which left every laptop between 769px and
   ~1560px with a broken bar and no hamburger. The rail fits comfortably down
   to 1024px; below that it becomes an off-canvas drawer. */

/* Two jobs, one element: the drawer's tap-to-close backdrop below 1024px, and
   the dim-and-blur behind the hovered rail above it (see the desktop block
   above). `display` is unconditional — `visibility: hidden` is what keeps it
   inert, and it also keeps it from catching a click while it is invisible. */
.rail-scrim {
    display: block;
    position: fixed;
    /* Below the bar in both breakpoints: the bar is chrome, not the page, and
       on a phone it is also where the button that closes the menu lives —
       dimming it would dim the way out. */
    top: var(--topbar-height);
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 895;
    background-color: rgba(17, 24, 39, 0.32);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0.3s ease, top var(--transition);
}

/* The desktop half of that: it has to sit after the base rule above, because
   a @media block adds no specificity and would otherwise lose the colour. */
@media (min-width: 1025px) {
    /* ---- the scrim behind an open rail ---------------------------------
       Opening the rail lays 168px of menu over the page, and without this the
       two competed: the content kept reading as the thing in front. It is the
       same dim-and-blur a modal uses, so the gesture is already learned.

       pointer-events stay off — unlike a modal this is not dismissed by
       clicking away, it closes the moment the pointer leaves the rail, and a
       live scrim would swallow the first click of whatever the seller was
       heading for. It also starts below the bar: the bar is chrome, not the
       page, and dimming the account switcher with it would be a lie. */
    .rail-scrim {
        pointer-events: none;
    }

    /* Same 0.12s the rail waits before widening: crossing it on the way
       somewhere else must not flash the whole page dark. */
    .app-rail:hover ~ .rail-scrim,
    .app-rail:focus-within ~ .rail-scrim {
        opacity: 1;
        visibility: visible;
        transition-delay: 0.12s;
    }
}

@media (max-width: 1024px) {
    /* The rail is off-canvas here, so it costs the page nothing. A drawer is
       also always full width — collapsing it would make no sense. */
    body.app-shell {
        --rail-offset: 0px;
    }

    /* The drawer never shows the icon strip, so the count has no reason to sit
       on the icon's corner, where it covers the label: it goes to the end of
       the row instead. */
    .rail-item > .rail-badge {
        position: relative;
        top: auto;
        left: auto;
        order: 1;
        margin-left: auto;
    }

    /* The same count on the menu button, since the rail is hidden. */
    .topbar-icon.rail-mobile-toggle {
        position: relative;
    }

    /* Hung off the button's top-right corner, clear of the three bars. */
    .rail-toggle-badge {
        top: -6px;
        left: auto;
        right: -4px;
    }

    /* Exactly the panel the desktop rail turns into when it opens — same
       inset, same radius, same shadow — only here it is opened by a tap
       instead of by the pointer, and there is no strip of icons behind it.

       It is sized to its own list rather than to the screen (`height: auto`):
       a full-height slab left two thirds of a phone as empty white, which read
       as a page that failed to load rather than as a menu. Past the cap the
       list scrolls inside the card. */
    .app-rail {
        width: var(--rail-width);
        left: var(--space-2);
        bottom: auto;
        height: auto;
        max-height: calc(100vh - var(--topbar-height) - var(--space-4));
        max-height: calc(100dvh - var(--topbar-height) - var(--space-4));
        transform: translateX(calc(-100% - var(--space-2)));
        background-color: var(--container-bg);
        border-radius: var(--radius-card);
        box-shadow: 0 12px 34px -8px rgba(17, 24, 39, 0.35);
    }

    /* No rail beside it any more, so a single top-left corner would be
       rounding against the edge of the screen. Both top corners instead: the
       page reads as a sheet sitting under the bar, which is the same thing the
       one corner says on the desktop. */
    .app-canvas {
        border-radius: var(--radius-modal) var(--radius-modal) 0 0;
    }

    .app-rail.open {
        transform: translateX(0);
    }

    /* ---- two rows on a phone, collapsing on scroll --------------------
       The wordmark is 52px wide at the height this bar allows and the page
       names run to 261px; on one 360px row, with the menu button and the
       account controls, neither survived — the longest names were already
       being clipped before the brand was even considered.

       Giving the title a row of its own fits both, at the cost of 44px of
       screen. The scroll gives that back: past the first fold the bar returns
       to the 56px single row, with the small mark in place of the wordmark
       (body.topbar-compact, set from base.html). Everything follows from
       --topbar-height, so the rail, the scrim and the page all move with it. */
    :root {
        --topbar-height: 100px;
    }

    .app-topbar {
        flex-wrap: wrap;
        align-content: center;
        column-gap: var(--space-3);
        row-gap: 0;
        padding: 4px var(--space-4) 8px;
    }

    /* Expandida são duas fileiras: o título tem flex-basis 100%, então o que
       vier depois dele cai na linha de baixo — por isso os controles da
       direita vêm antes do título aqui, e depois dele quando a barra é uma
       linha só (ver o bloco compacto). */
    .rail-mobile-toggle { order: 1; }
    .topbar-brand      { order: 2; }
    .topbar-mark       { order: 2; }
    .topbar-right      { order: 3; }

    /* Its own row, so it gets the full width and stops being truncated. */
    .topbar-title {
        order: 4;
        flex-basis: 100%;
        margin-top: 2px;
        font-size: var(--text-xl);
        line-height: 1.2;
        letter-spacing: -0.02em;
    }

    /* Expanded, the bar carries the wordmark; collapsed, the small mark. */
    .topbar-brand {
        display: flex;
        margin: 0;
        padding: 0;
    }

    .topbar-brand img {
        height: 26px;
    }

    .topbar-mark {
        display: none;
    }

    body.topbar-compact {
        --topbar-height: 56px;
    }

    body.topbar-compact .app-topbar {
        flex-wrap: nowrap;
        padding: 0 var(--space-4);
    }

    body.topbar-compact .topbar-brand { display: none; }
    body.topbar-compact .topbar-mark  { display: flex; }

    /* Uma linha só: menu, marca, título e, encostados na direita, a conta e o
       perfil. */
    body.topbar-compact .topbar-title { order: 3; }
    body.topbar-compact .topbar-right { order: 4; }

    body.topbar-compact .topbar-title {
        flex-basis: auto;
        margin-top: 0;
        font-size: var(--text-lg);
    }

    .rail-head {
        display: flex;
    }

    .topbar-icon.rail-mobile-toggle {
        display: grid;
    }

    /* The tooltip on the hamburger opens on :focus-within, which is what makes
       tooltips work on a touch screen at all — but tapping this one leaves the
       button focused, so "Abrir menu" stayed parked over the drawer's own logo
       for as long as the menu was open. While the menu is open the button has
       already done its explaining. */
    body.rail-open .rail-mobile-toggle .sb-tooltip-msg {
        visibility: hidden;
        opacity: 0;
    }

    .rail-scrim.active {
        opacity: 1;
        visibility: visible;
    }

    body.rail-open {
        overflow: hidden;
    }
}

/* On a phone the account name alone can take 220px and push the jobs icon and
   the profile menu off the bar. The building icon and the chevron still read
   as "trocar de conta", and the full name is the first thing in the menu. */
@media (max-width: 640px) {
    .account-switcher-btn .switcher-label {
        display: none;
    }
}


/* ==========================================================================
   BUTTONS
   Canonical class is .button plus one variant. `.btn-*` is deprecated —
   do not add new uses; convert on sight when editing a file.
   ========================================================================== */

.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);

    padding: 8px 16px;
    border-radius: var(--radius);
    border: 1px solid transparent;

    font-family: var(--font-sans);
    font-size: var(--text-base);
    font-weight: 600;
    line-height: 1.4;
    text-align: center;
    text-decoration: none;
    white-space: nowrap;

    min-height: var(--control-height);
    cursor: pointer;
    transition: background-color var(--transition), border-color var(--transition),
                color var(--transition), opacity var(--transition);
}

.button:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

/* Never use `cursor: not-allowed` — rely on the native disabled attribute. */
.button:disabled,
.button[aria-disabled="true"] {
    opacity: 0.5;
    pointer-events: none;
}

/* ---- primary ---------------------------------------------------------
   White on #F5A623 measures 2.03:1, below the WCAG AA 4.5:1 threshold.
   This is a deliberate brand decision. If you ever want the accessible
   variant without changing brand identity, swap the background here for
   var(--brand-orange-ink) — nothing else needs to change.               */
.button-primary {
    background-color: var(--brand-orange);
    border-color: var(--brand-orange);
    color: #FFFFFF;
}
.button-primary:hover {
    background-color: var(--brand-orange-hover);
    border-color: var(--brand-orange-hover);
}

/* ---- secondary — the default for anything that is not the main action */
.button-secondary {
    background-color: var(--gray-50);
    border-color: var(--border-strong);
    color: var(--text-secondary);
}
.button-secondary:hover {
    background-color: var(--gray-100);
    border-color: var(--gray-300);
    color: var(--text-primary);
}

/* ---- danger — tinted at rest, fills on hover ------------------------- */
.button-danger {
    background-color: var(--danger-tint);
    border-color: var(--danger-border);
    color: var(--danger-ink);
}
.button-danger:hover {
    background-color: var(--danger-ink);
    border-color: var(--danger-ink);
    color: #FFFFFF;
}

/* Use inside a confirmation modal, where destroying is the point. */
.button-danger-solid {
    background-color: var(--danger-ink);
    border-color: var(--danger-ink);
    color: #FFFFFF;
}
.button-danger-solid:hover {
    background-color: #B3000F;
    border-color: #B3000F;
}

/* ---- success ---------------------------------------------------------- */
.button-success {
    background-color: var(--success-tint);
    border-color: var(--success-border);
    color: var(--success-ink);
}
.button-success:hover {
    background-color: var(--success-ink);
    border-color: var(--success-ink);
    color: #FFFFFF;
}

/* ---- AI-assisted action ----------------------------------------------
   For actions where the work is done by AI. Brand-tinted at rest so it
   stands out from a plain secondary without stealing the single primary
   of the view; fills on hover. Always pair it with the sparkle icon —
   the AI cue has to be visual, not only in the label.                    */
.button-ai {
    background-color: var(--brand-orange-tint);
    border-color: rgba(var(--brand-orange-rgb), 0.45);
    color: var(--brand-orange-ink);
}
.button-ai:hover {
    background-color: var(--brand-orange);
    border-color: var(--brand-orange);
    color: #FFFFFF;
}

/* ---- ghost / text ----------------------------------------------------- */
.button-ghost {
    background-color: transparent;
    border-color: transparent;
    color: var(--text-secondary);
}
.button-ghost:hover {
    background-color: var(--gray-100);
    color: var(--text-primary);
}

/* ---- icon-only — always give it an aria-label and an SVG, never an emoji */
.button-icon {
    padding: 6px;
    border-radius: var(--radius-sm);
    background: transparent;
    border-color: transparent;
    color: var(--text-secondary);
}
.button-icon:hover {
    background-color: var(--gray-100);
    color: var(--text-primary);
}
.button-icon.danger:hover {
    background-color: var(--danger-tint);
    color: var(--danger-ink);
}

/* ---- sizes ------------------------------------------------------------ */
.button-sm {
    /* Own height: without it the .button min-height would inflate every small
       button to 40px and there would be no small button left. 12px of side
       padding because these carry real sentences ("Usar dados do produto"),
       not one-word labels. */
    min-height: var(--control-height-sm);
    padding: 5px 12px;
    font-size: var(--text-sm);
    border-radius: var(--radius-sm);
}
.button-lg {
    padding: 11px 22px;
    font-size: var(--text-lg);
}
.button-block {
    display: flex;
    width: 100%;
}

/* ---- touch targets below 640px ------------------------------------------
   A finger needs 44x44 CSS px (docs/mobile_refactor.md §3, check 2). Raising
   the two control-height tokens is the whole fix for buttons, inputs, selects
   and filter triggers at once — and it keeps them the same height as each
   other, which is the reason the tokens exist (see their comment at the top).
   --control-height-sm collapses onto the same 44px: a "small" button is a
   density decision, and there is no density below one fingertip.

   The rail and the top bar do not read these tokens (they size their icons at
   36px), so the shell settled on 2026-08-23 is untouched.

   .button-icon and .modal-close-btn are square and sized by their padding, so
   they need the floor stated on both axes. Deliberately min-*, not width: the
   drawn icon does not change, only the box around it. */

@media (max-width: 640px) {
    :root {
        --control-height: 44px;
        --control-height-sm: 44px;
    }

    .button-icon,
    .modal-close-btn,
    /* ui.css's global edit pencil — sized only by its glyph, and it is the
       control that renames an account. Found in Phase 2. */
    .button-edit-icon {
        min-width: 44px;
        min-height: 44px;
    }

    /* A back-link is an ordinary anchor 17px tall — the smallest target on
       most pages, and always at the top of the screen where it is tapped. */
    .back-link {
        min-height: 44px;
    }

    /* A checkbox or a radio renders at 13x13 and cannot be given padding, so
       the box itself has to grow — and it grows to 24px, not to 44px, because
       a 44px checkbox reads as a button. The label beside it is part of the
       target and carries the rest. Found in Phase 2 (vendas.html has 15 of
       them in one dialog); §6.6 had only covered buttons and text controls.
       A page that draws its own control with `appearance: none` sets its own
       size and loads after this file, so it keeps it. */
    input[type="checkbox"],
    input[type="radio"] {
        width: 24px;
        height: 24px;
    }

    /* The native colour swatch is a control the finger has to hit exactly —
       there is no label beside it to share the target, so unlike a checkbox it
       goes to the full 44px. Three pages draw one (status colour, tag colour);
       all three had it inline-styled to 40px. Phase 3. */
    input[type="color"] {
        width: 56px;
        height: 44px;
        padding: 2px;
        border: 1px solid var(--border-strong);
        border-radius: var(--radius);
        background-color: var(--background-color);
        cursor: pointer;
    }

    /* A context tab is 42px from its padding alone — two pixels short of the
       floor, and it is the primary navigation of the pages that use it. */
    .context-tab {
        min-height: 44px;
    }

    /* ui.css's .button-filter is 42px for the same reason. It does not read
       --control-height (it predates the token), so it is stated here. */
    .button-filter {
        min-height: 44px;
    }
}



/* ==========================================================================
   STATUS BADGE
   ========================================================================== */

.badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 12px;
    border-radius: var(--radius-pill);
    border: 1px solid transparent;
    font-size: var(--text-xs);
    font-weight: 600;
    line-height: 1.5;
    white-space: nowrap;
}
.badge-success { background-color: var(--success-tint); color: var(--success-ink); border-color: var(--success-border); }
.badge-danger  { background-color: var(--danger-tint);  color: var(--danger-ink);  border-color: var(--danger-border);  }
.badge-warning { background-color: var(--warning-tint); color: var(--warning-ink); border-color: var(--warning-border); }
.badge-info    { background-color: var(--info-tint);    color: var(--info-ink);    border-color: var(--info-border);    }
.badge-neutral { background-color: var(--gray-100);     color: var(--text-secondary); border-color: var(--gray-200);    }
.badge-lightning { background-color: var(--lightning-tint); color: var(--lightning-ink); border-color: var(--lightning-border); }

/* A badge that opens something.
   `.badge` is a <span> by default; this is the modifier for the cases where
   it is a real <button> — it resets what the browser does to a button and
   says so with the pointer. On a phone it grows to a 44px target, because a
   badge that is the only way into a screen is an action, not a label. */
.badge-action {
    cursor: pointer;
    font-family: inherit;
    background-image: none;
    transition: box-shadow var(--transition), border-color var(--transition);
}
.badge-action:hover {
    box-shadow: var(--focus-ring);
    border-color: var(--border-strong);
}
.badge-action:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

@media (max-width: 640px) {
    .badge-action {
        min-height: 44px;
        padding-inline: 14px;
    }
}


/* ==========================================================================
   CONTEXT TABS
   Underlined tab strip used to switch the context of a page (tag contexts,
   marketplace, rule type). Buttons or links; mark the current one `.active`.
   ========================================================================== */

.context-tabs {
    display: flex;
    gap: 4px;
    margin-bottom: var(--space-5);
    /* The strip holds one tab per connected marketplace account, so its width
       is not something the page controls. It scrolls rather than wrapping:
       a wrapped tab strip stops reading as one row of tabs. */
    overflow-x: auto;
    /* `overflow-x: auto` on its own also computes overflow-y to `auto` — the
       spec does not let `visible` pair with a scrolling axis. The strip was
       overflowing vertically by exactly one pixel (the tab's old
       `margin-bottom: -1px`, which pulled the active underline down over the
       border below), so that computed `auto` painted a full vertical
       scrollbar *inside the tab strip* on every machine that draws classic
       scrollbars: Windows, Linux, and macOS set to "Show scroll bars:
       Always". Invisible on a Mac with overlay scrollbars, which is why it
       shipped. The baseline is an inset shadow now and the tabs sit entirely
       inside the strip, so there is nothing left to overflow; `hidden` states
       that, and stops a future taller child from bringing the bar back.
       Fixed 2026-09-02. */
    overflow-y: hidden;
    /* Was `border-bottom`. An inset shadow paints in the same place, after
       the background and before the children — so the active tab's 2px
       underline still covers it — but it lives inside the padding box, which
       is what removes the overhang above. */
    box-shadow: inset 0 -1px 0 var(--border-color);
}

.context-tab {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 10px 18px;
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: var(--text-base);
    color: var(--text-secondary);
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    cursor: pointer;
    white-space: nowrap;
}

.context-tab:hover {
    color: var(--text-primary);
}

.context-tab.active {
    color: var(--brand-orange);
    border-bottom-color: var(--brand-orange);
}


/* ==========================================================================
   CARD
   ========================================================================== */

.card {
    background-color: var(--container-bg);
    border: 1px solid var(--card-border-color);
    border-radius: var(--radius-card);
    padding: var(--space-5);
}

/* ---- settings card ----
   A card split into header / body / action footer. Three account pages
   (settings_login, settings_profile, settings_organization) each carried an
   identical copy of these five rules, all three hardcoding #fff and a 16px
   radius; Phase 3 promoted them here and deleted the copies. Below 640px the
   padding tightens and the footer's buttons go full width — two side by side
   at 360px leave neither of them a comfortable target. */

.settings-container {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
}

.settings-card {
    background-color: var(--container-bg);
    border: 1px solid var(--card-border-color);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-soft);
}

.settings-card-header {
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--card-border-color);
}
.settings-card-header h2 {
    margin: 0;
    font-size: var(--text-xl);
}
.settings-card-header p {
    margin: var(--space-1) 0 0 0;
    font-size: var(--text-base);
    color: var(--text-secondary);
}

.settings-card-body {
    padding: var(--space-5);
}

.settings-card-footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    background-color: var(--surface-subtle);
    border-top: 1px solid var(--card-border-color);
    border-radius: 0 0 var(--radius-card) var(--radius-card);
}

@media (max-width: 640px) {
    .settings-card-header,
    .settings-card-body,
    .settings-card-footer {
        padding: var(--space-4);
    }
    .settings-card-footer {
        flex-direction: column;
        align-items: stretch;
    }
}


/* ==========================================================================
   FORM CONTROLS
   ========================================================================== */

.form-group { margin-bottom: var(--space-4); }

.form-group label {
    display: block;
    margin-bottom: var(--space-2);
    font-weight: 600;
    color: var(--text-primary);
}

/* The one-line hint under a control. Declared on the bare class and not only
   under .form-group because half the pages that use it put it outside one
   (admin_plan_form, admin_user_form) — and every local copy measured 0.8rem,
   which is 12.8px and under the 13px floor of §3 check 4. Phase 3. */
.help-text,
.form-group .help-text {
    display: block;
    margin-top: var(--space-1);
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.form-control {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    box-sizing: border-box;
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: 1.5;
    color: var(--text-primary);
    background-color: var(--background-color);
    transition: border-color var(--transition), box-shadow var(--transition),
                background-color var(--transition);
}
.form-control::placeholder {
    color: var(--text-muted);
    opacity: 1;
}
.form-control:hover {
    border-color: var(--gray-300);
    background-color: var(--gray-50);
}
.form-control:focus {
    outline: none;
    border-color: var(--brand-orange);
    background-color: var(--background-color);
    box-shadow: var(--focus-ring);
}
.form-control:disabled,
.form-control[readonly] {
    background-color: var(--gray-50);
    border-color: var(--gray-200);
    color: var(--text-muted);
}

/* Single-line controls lock to the row height. `padding-block: 0` lets the
   flex/line centring do the vertical work, so the box is exactly
   --control-height and lines up with any .button beside it. */
input.form-control:not([type="checkbox"]):not([type="radio"]),
select.form-control:not([multiple]):not([size]) {
    height: var(--control-height);
    padding-block: 0;
}

textarea.form-control {
    min-height: 100px;
    resize: vertical;
}

/* Safari on iOS zooms the viewport when a focused input, select or textarea
   computes below 16px — and it never zooms back out, so the rest of the page
   is left scrolling sideways. This one declaration is the fix for the single
   most common "quebrou no meu iPhone" report; do not lower it, and do not
   re-declare a smaller font-size on a control in a page stylesheet.
   scripts/check_mobile_css.py (Phase 4) will enforce it. */
@media (max-width: 640px) {
    .form-control {
        font-size: var(--text-lg);
    }

    /* The net for the controls that were never given .form-control: several
       pages style `.form-group input` directly, at 14px or 15px or at the
       browser default, and a class selector there beats a bare element rule.
       Two classes and an element outrank all of them. Converting that markup
       is a per-page job (Phase 2/3) — the zoom is not allowed to wait for it. */
    .form-group :is(input, select, textarea) {
        font-size: var(--text-lg);
    }
}


/* ==========================================================================
   PAGE HEADER
   Title on the left, actions on the right. `.content-header` is the older
   name for the same thing — both are kept so existing markup keeps working.
   ========================================================================== */

.page-header,
.content-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin-bottom: var(--space-5);
}

.page-header h1,
.content-header h1 {
    margin: 0;
    font-size: var(--text-2xl);
    font-weight: 700;
    color: var(--text-primary);
}

.page-header .subtitle,
.content-header .subtitle {
    margin: var(--space-1) 0 0;
    font-size: var(--text-base);
    color: var(--text-secondary);
}

/* Wrapping is right here and not everywhere: this is an action group, and two
   buttons that do not fit belong on a second line rather than off the edge. */
.page-header-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
}

/* "‹ Voltar" style link that sits above or beside a page title. */
.back-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--text-base);
    color: var(--text-secondary);
    text-decoration: none;
}
.back-link:hover { color: var(--text-primary); }

/* ---- stacked header below 640px ----------------------------------------
   The base row is already `flex-wrap: wrap`, but wrapping alone leaves the
   action group hugging the right edge under a left-aligned title, which reads
   as a mistake. Below 640px the header becomes one column and the actions
   become a full-width wrapping row. A back-link is pulled above the title
   rather than sitting beside it, and the primary action leads the group —
   on a phone the first button is the one the thumb reaches without aiming. */

@media (max-width: 640px) {
    .page-header,
    .content-header {
        flex-direction: column;
        align-items: stretch;
    }

    .page-header .back-link,
    .content-header .back-link {
        order: -1;
    }

    .page-header-actions {
        width: 100%;
        flex-wrap: wrap;
    }

    .page-header-actions .button-primary {
        order: -1;
    }
}


/* ==========================================================================
   MODAL
   Overlays are toggled with an inline `display` by the page scripts, so the
   base state stays `none` and `.active` is only a convenience for newer code.
   ========================================================================== */

.modal-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 2000;
    align-items: center;
    justify-content: center;
    padding: var(--space-4);
    background-color: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.modal-overlay.active,
.modal-overlay.show,
.modal-overlay.visible { display: flex; }

.modal-content {
    width: 100%;
    max-width: 500px;
    /* dvh, not vh: the on-screen keyboard shrinks the visual viewport and a
       vh-capped dialog then runs under it. The vh line is the fallback for
       browsers without dvh. */
    max-height: 90vh;
    max-height: 90dvh;
    overflow-y: auto;
    overflow-x: hidden;
    padding: var(--space-6);
    background-color: var(--container-bg);
    border-radius: var(--radius-modal);
    box-shadow: var(--shadow-modal);
}

.modal-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}
.modal-header h2,
.modal-header h3 {
    margin: 0;
    font-size: var(--text-xl);
    font-weight: 700;
}

.modal-body {
    font-size: var(--text-base);
    line-height: 1.6;
    color: var(--text-secondary);
}

.modal-footer,
.modal-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-5);
}

/* Centered SVG above the title in confirmation dialogs. */
.modal-icon-wrapper {
    margin-bottom: var(--space-4);
    text-align: center;
    color: var(--brand-orange);
}

.modal-close-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-1);
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    cursor: pointer;
    line-height: 0;
}
.modal-close-btn:hover {
    background-color: var(--gray-100);
    color: var(--text-primary);
}

/* ---- bottom sheet below 640px -------------------------------------------
   A centered box with padding on four sides wastes a phone screen and leaves
   the footer buttons in a row that does not wrap. The dialog instead docks to
   the bottom edge: full width, rounded only on top, and its actions stacked
   with the confirming one bottom-most, nearest the thumb.

   Modifiers: .modal-wide widens the box above 640px (form-heavy dialogs);
   .modal-centered on the overlay opts out of the sheet and keeps the card.
   No dialog uses .modal-centered today (checked 2026-08-26); it is the
   documented escape hatch for one that genuinely must stay a centered card. */

.modal-content.modal-wide { max-width: 760px; }

/* A dialog that carries a whole working area rather than a form: the stock
   review's product detail puts three charts side by side inside one
   (docs/working_capital_stock_review.md §25.7), and at 760px the two-column
   chart row has no room to be two columns. Still bounded — a dialog that grows
   with the window stops being a dialog — and below 640px it becomes the same
   bottom sheet as every other one. */
.modal-content.modal-xwide { max-width: 1080px; }

@media (max-width: 640px) {
    .modal-overlay:not(.modal-centered) {
        align-items: flex-end;
        padding: 0;
    }

    /* Deliberately at plain .modal-content specificity, not under the overlay:
       a page whose dialog genuinely differs (regras_fiscais keeps padding on
       the header and footer so the scrollbar can reach the edge) must be able
       to win from its own stylesheet, which loads after this one. The overlay
       geometry above is not negotiable and stays specific. */
    .modal-content {
        max-width: none;
        max-height: 90dvh;
        padding: var(--space-4);
        border-radius: var(--radius-modal) var(--radius-modal) 0 0;
    }

    /* Wrap is the fallback for a header carrying more than a title and a close
       control; the two-child case is handled by the basis rule below. */
    .modal-header {
        flex-wrap: wrap;
    }

    /* flex-basis 0, not auto. Line breaking in a wrapping flex container is
       decided on base sizes BEFORE any shrinking, so an `auto` basis means a
       long title pushes the close button onto a second line no matter how far
       it could have shrunk. A zero basis lets the title wrap its own text and
       keeps the button in its corner. */
    .modal-header > :first-child {
        flex: 1 1 0;
        min-width: 0;
    }

    .modal-close-btn {
        flex-shrink: 0;
    }

    /* Plain `column`, not `column-reverse`. The goal is the confirming action
       bottom-most, nearest the thumb — and in this codebase the footer is
       written cancel-first, confirm-last (50 of 56 footers, measured
       2026-08-25), so DOM order already puts the confirm at the bottom and
       reversing it would hand the thumb the Cancelar button. The six footers
       that lead with a destructive Excluir end up with it farthest from the
       thumb, which is also what we want. `stretch` gives every child the full
       width without a rule per child. */
    .modal-footer,
    .modal-actions {
        flex-direction: column;
        align-items: stretch;
    }
}


/* ==========================================================================
   ENTITY LIST
   One card per record — the clean alternative to a data table for short,
   editable lists (fiscal rules, tags). Those lists carry two or three facts
   and an action set per record, which a table turns into mostly-empty
   columns and a spreadsheet header. The card keeps the record readable and
   wraps on a phone instead of scrolling sideways.

   Markup:
     <div class="entity-list">
       <div class="entity-list-head"><span>N regras</span> ...</div>
       <div class="entity-card">
         <span class="entity-card-dot" style="background: #F5A623"></span>
         <div class="entity-card-body">
           <div class="entity-card-title">Nome <span class="badge">…</span></div>
           <p class="entity-card-desc">…</p>
           <div class="entity-card-meta"><span>…</span></div>
         </div>
         <div class="entity-card-actions"> buttons </div>
       </div>
     </div>
   ========================================================================== */

.entity-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* Deliberately not a table header: one quiet line of context, no fill,
   no column rules. */
.entity-list-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding: 0 var(--space-1) var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.entity-card {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-4);
    background-color: var(--container-bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-card);
    transition: border-color var(--transition), box-shadow var(--transition);
}

.entity-card:hover {
    border-color: var(--gray-300);
    box-shadow: var(--shadow-soft);
}

/* A card whose whole surface opens something. The anchor stays a real
   <a href> — so middle-click, ctrl-click and "abrir em nova guia" all behave
   natively — and its ::after stretches over the card to make the entire
   surface the target. One link, one tab stop, no JS click handler to
   intercept (and swallow) a modified click.

   Anything else interactive inside the card must be lifted above the overlay,
   which the rule below does for buttons, inputs and selects. */
.entity-card-link { position: relative; }

.entity-card-link .entity-card-anchor::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: var(--radius-card);
}

.entity-card-link .entity-card-anchor:focus-visible {
    outline: none;
}

.entity-card-link .entity-card-anchor:focus-visible::after {
    box-shadow: var(--focus-ring);
    border-color: var(--brand-orange);
}

.entity-card-link :is(button, input, select, a:not(.entity-card-anchor)) {
    position: relative;
    z-index: 1;
}

/* Color chip — a tag color, a status. The color itself is data, so it is
   set inline by whoever renders the card. */
.entity-card-dot {
    flex-shrink: 0;
    width: 14px;
    height: 14px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-strong);
}

.entity-card-body {
    flex: 1 1 auto;
    min-width: 0;
}

.entity-card-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--text-primary);
}

.entity-card-desc {
    margin: 2px 0 0;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    overflow-wrap: anywhere;
}

.entity-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
    margin-top: var(--space-1);
    font-size: var(--text-xs);
    color: var(--text-muted);
}

/* The "nothing here yet" card takes the place of a record, so it keeps the
   list's shape — dashed, to read as absence rather than as a row. */
.entity-list .empty-state {
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-card);
}

.entity-card-actions {
    flex-shrink: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-2);
}

@media (max-width: 640px) {
    .entity-card {
        flex-wrap: wrap;
    }
    .entity-card-actions {
        width: 100%;
        justify-content: flex-start;
    }
}


/* ==========================================================================
   TABLE
   `.data-table` is canonical. The `.styled-table` / `.standard-table` aliases
   it used to carry were removed once the last 7 uses were converted
   (docs/mobile_refactor.md §6.4).
   ========================================================================== */

.table-container {
    width: 100%;
    overflow-x: auto;
    background-color: var(--container-bg);
    border: 1px solid var(--card-border-color);
    border-radius: var(--radius-card);
}

.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-base);
}

.data-table th {
    padding: var(--space-3) var(--space-4);
    background-color: var(--surface-subtle);
    border-bottom: 1px solid var(--border-strong);
    text-align: left;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-secondary);
    white-space: nowrap;
}

.data-table td {
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border-strong);
    color: var(--text-primary);
    vertical-align: middle;
}

.data-table tbody tr:last-child td { border-bottom: none; }

.data-table tbody tr:hover { background-color: var(--gray-50); }

/* Right-align money and quantity columns. */
.data-table .is-numeric { text-align: right; font-variant-numeric: tabular-nums; }


/* ---- .table-cards — one card per row below 768px -------------------------
   Decided 2026-08-25 (docs/mobile_refactor.md §2). Horizontal scroll was the
   alternative and was rejected: the lojista reads a sale by scanning one row,
   and a scrolling table turns that into a sideways sweep per row.

   CSS only, no JavaScript — the same markup serves both layouts, so nothing
   here has to know about the CSP nonce.

   Markup: add `table-cards` beside `data-table`, and give EVERY <td> a
   pt-BR `data-label` matching its <th>:

     <table class="data-table table-cards">
       <thead><tr><th>Pedido</th><th class="is-numeric">Valor</th></tr></thead>
       <tbody><tr>
         <td data-label="Pedido">#402913</td>
         <td data-label="Valor" class="is-numeric">R$ 1.240,00</td>
         <td><button class="button button-sm">Abrir</button></td>
       </tr></tbody>
     </table>

   A <td> with no data-label (an action cell, a colspan empty state) spans the
   whole card width instead of showing a blank label column — which is why the
   actions land on their own line at the foot of the card.

   Not for every table: where the task is comparing numbers DOWN a column
   (analytics.html), cards destroy the comparison and horizontal scroll is the
   correct answer. Where the "table" is really a short list of records, prefer
   .entity-list / .entity-card, which is already the house pattern. */

@media (max-width: 768px) {
    /* The container's own frame and scrollbar are the table's, not the
       cards' — a border around a stack of bordered cards reads as a mistake.
       Browsers without :has() keep the frame, which is cosmetic only. */
    .table-container:has(.table-cards) {
        overflow-x: visible;
        border: none;
        background: none;
    }

    .table-cards,
    .table-cards tbody,
    .table-cards tfoot {
        display: block;
        width: 100%;
    }

    .table-cards thead {
        display: none;
    }

    .table-cards tbody tr,
    .table-cards tfoot tr {
        display: grid;
        gap: var(--space-2);
        padding: var(--space-4);
        margin-bottom: var(--space-3);
        background-color: var(--container-bg);
        border: 1px solid var(--border-strong);
        border-radius: var(--radius-card);
    }

    .table-cards tbody tr:last-child,
    .table-cards tfoot tr:last-child {
        margin-bottom: 0;
    }

    /* A phone has no hover, but iOS makes it sticky after a tap and the card
       would stay gray. */
    .table-cards tbody tr:hover {
        background-color: var(--container-bg);
    }

    /* The row gap does the separating, so the cell rules from .data-table are
       both unnecessary here and wrong — a full-width line under every value. */
    .table-cards tbody tr td,
    .table-cards tfoot tr td {
        display: block;
        padding: 0;
        border-bottom: none;
    }

    .table-cards tbody tr td[data-label],
    .table-cards tfoot tr td[data-label] {
        display: grid;
        grid-template-columns: minmax(0, 38%) minmax(0, 1fr);
        gap: var(--space-3);
        align-items: baseline;
    }

    .table-cards tbody tr td[data-label]::before,
    .table-cards tfoot tr td[data-label]::before {
        content: attr(data-label);
        font-size: var(--text-sm);
        font-weight: 600;
        color: var(--text-secondary);
        overflow-wrap: anywhere;
    }

    /* .is-numeric already right-aligns the cell; the label has to stay left or
       the two halves collide in the middle of the card. */
    .table-cards tbody tr td.is-numeric::before,
    .table-cards tfoot tr td.is-numeric::before {
        text-align: left;
    }

    /* An action cell gets the full width and its buttons wrap. */
    .table-cards tbody tr td:not([data-label]),
    .table-cards tfoot tr td:not([data-label]) {
        display: flex;
        flex-wrap: wrap;
        gap: var(--space-2);
    }
}


/* ==========================================================================
   EMPTY STATE
   ========================================================================== */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    padding: var(--space-6) var(--space-5);
    text-align: center;
    color: var(--text-secondary);
}
.empty-state svg { color: var(--text-muted); }
.empty-state h3 {
    margin: 0;
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--text-primary);
}
.empty-state p {
    margin: 0;
    max-width: 420px;
    font-size: var(--text-base);
}


/* ==========================================================================
   INLINE MESSAGE
   Static feedback inside a page or card. Transient feedback is a toast.
   ========================================================================== */

.message {
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius);
    border: 1px solid transparent;
    font-size: var(--text-base);
    line-height: 1.5;
    text-align: center;
}
.message.success { background-color: var(--success-tint); color: var(--success-ink); border-color: var(--success-border); }
.message.error   { background-color: var(--danger-tint);  color: var(--danger-ink);  border-color: var(--danger-border);  }
.message.warning { background-color: var(--warning-tint); color: var(--warning-ink); border-color: var(--warning-border); }
.message.info    { background-color: var(--info-tint);    color: var(--info-ink);    border-color: var(--info-border);    }


/* ==========================================================================
   AUTH SHELL
   The centered single-card layout shared by the standalone pages that sit
   outside base.html: verificação de e-mail, redefinição de senha, exclusão
   de conta, erro.
   ========================================================================== */

.auth-shell {
    display: flex;
    align-items: center;
    justify-content: center;
    /* dvh: mobile browser chrome makes 100vh taller than the visible area, so
       the bottom of the card sits under the address bar. vh is the fallback. */
    min-height: 100vh;
    min-height: 100dvh;
    margin: 0;
    padding: var(--space-5);
    box-sizing: border-box;
    background-color: var(--surface-subtle);
    font-family: var(--font-sans);
    color: var(--text-primary);
    -webkit-font-smoothing: antialiased;
}

.auth-card {
    box-sizing: border-box;
    width: 100%;
    max-width: 450px;
    padding: var(--space-6);
    background-color: var(--container-bg);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-soft);
    text-align: center;
}

.auth-card .logo {
    height: 100px;
    margin-bottom: var(--space-5);
}

.auth-card h1 {
    margin: 0 0 var(--space-2);
    font-size: var(--text-2xl);
    font-weight: 700;
}

.auth-card p {
    margin: 0 0 var(--space-5);
    font-size: var(--text-base);
    line-height: 1.6;
    color: var(--text-secondary);
}

.auth-card p:last-child { margin-bottom: 0; }

.auth-card a { color: var(--brand-orange-ink); font-weight: 600; text-decoration: none; }
.auth-card a:hover { text-decoration: underline; }

/* A link that stands alone as the page's action, not a word inside a sentence
   — on the auth pages it is often the only thing to tap. It gets the 44px
   floor of §3 check 2. Deliberately a class and not `p > a:only-child`: CSS
   cannot see the text nodes beside the link, so the structural selector also
   catches links inside running text, and `inline-flex` on one of those stops
   the sentence wrapping and pushes the page sideways. Phase 3. */
.auth-action-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
}

.auth-card .message { margin-top: var(--space-5); }


/* ==========================================================================
   CHECKLIST
   Confirmation copy where each line is a commitment. The check is an SVG —
   never an emoji.
   ========================================================================== */

.checklist {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin: var(--space-4) 0;
    padding: 0;
    list-style: none;
    text-align: left;
}

.checklist li {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    line-height: 1.6;
}

.checklist li svg {
    flex-shrink: 0;
    margin-top: 3px;
    color: var(--success-base);
}


/* ==========================================================================
   FORM LAYOUT
   ========================================================================== */

.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: var(--space-4);
}

/* auto-fit already collapses to one track when the container is narrow, but
   only if the container itself is narrow — inside a padded card at 360px the
   260px floor can still win and overflow. Force it. */
@media (max-width: 640px) {
    .form-grid {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ---- KPI row ----
   The dashboard-style row of stat cards. `auto-fit` alone drops straight from
   four columns to one, which wastes a phone screen on cards that hold six
   characters; two columns is the readable shape down to ~400px. */

.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--space-4);
}

@media (max-width: 640px) {
    .stat-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 400px) {
    .stat-grid {
        grid-template-columns: minmax(0, 1fr);
    }
}

.form-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-5);
}

/* Same reasoning as .modal-footer: plain `column`, because a form's action row
   is written cancel-first, submit-last, so DOM order already puts the
   confirming button nearest the thumb. Full width, because two buttons side by
   side at 360px leave neither of them a comfortable target. Phase 3. */
@media (max-width: 640px) {
    .form-actions {
        flex-direction: column;
        align-items: stretch;
    }
}


/* ==========================================================================
   TOOLBAR
   The filter/action row above a list: a search field, two or three selects,
   maybe a button. It is the single most common layout in the app — measured
   2026-08-25, 454 flex rows in templates had no `flex-wrap` — and every page
   had invented its own. This is that component.

   Markup:
     <div class="toolbar">
       <input class="form-control toolbar-grow" placeholder="Buscar…">
       <select class="form-control">…</select>
       <button class="button button-secondary">Filtrar</button>
     </div>

   `.toolbar-grow` marks the one child that should absorb the slack, normally
   the search field. It composes with .control-row / .control-row-label (label
   above a control) — do not re-declare either.

   Below 640px every direct child takes the full width: at 360px two controls
   side by side leave neither of them usable.
   ========================================================================== */

.toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
}

/* Without this a long <select> or a wide filter label sets the flex base size
   from its content and pushes the row past the viewport instead of wrapping. */
.toolbar > * {
    min-width: 0;
}

/* Controls sitting directly on the row share the one control height, so the
   row reads as a single line whatever it is made of. Wrappers that carry a
   label above their control (.control-row-field) are deliberately excluded. */
.toolbar > .button,
.toolbar > .button-filter,
.toolbar > .form-control {
    height: var(--control-height);
}

.toolbar-grow {
    flex: 1 1 240px;
}

.toolbar-end {
    margin-left: auto;
}

@media (max-width: 640px) {
    .toolbar > * {
        flex: 1 1 100%;
    }

    /* margin-left: auto in a full-width column does nothing but confuse. */
    .toolbar-end {
        margin-left: 0;
    }
}


/* ---- a filter row that is applied on demand ----
   Both Analytics pages reload hundreds of aggregations, so editing a date or
   picking a store does not reload anything by itself — the seller finishes
   choosing and presses the button. Until then nothing on screen has moved,
   and the ring on the button is the only thing saying the page is out of
   date. Shared because the two pages carry the same row. */

.filter-stack {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--space-2);
}

.filter-hint {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--brand-orange-ink);
}

.button.is-dirty {
    box-shadow: 0 0 0 3px var(--brand-orange-tint), 0 0 0 4px var(--brand-orange);
}

@media (max-width: 640px) {
    .filter-stack {
        width: 100%;
        align-items: stretch;
    }
}


/* ---- control row ----
   A label above, controls below, everything on one baseline.

   No page consumes this today (checked 2026-08-26). It is kept because
   .toolbar's contract names it as the wrapper for a labelled control, and
   re-inventing that per page is what §1 measured 454 times. */

.control-row {
    display: flex;
    align-items: flex-end;
    gap: var(--space-3);
    flex-wrap: wrap;
}

.control-row-field label,
.control-row-label {
    display: block;
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: var(--space-1);
}


/* ==========================================================================
   INTERACTION UTILITIES  (replace the old inline on* handlers)
   ==========================================================================
   These exist because of the CSP work: `onmouseover="this.style...="` and
   `onfocus="this.style...="` are inline handlers, and a policy without
   'unsafe-inline' blocks them. Hover and focus are states CSS already has, so
   the fix is a class, not a listener.

   The `!important` is deliberate and is the whole reason these are utilities
   instead of ordinary rules: every element that carried such a handler also
   carries a `style="..."` attribute holding the resting value, and an inline
   declaration beats a normal stylesheet rule. Drop the `!important` and the
   hover silently stops working.

   New markup should use tokens and plain rules instead of reaching for these.
   ========================================================================== */

/* was: onmouseover="this.style.backgroundColor='var(--gray-100)'" */
.hover-row:hover {
    background-color: var(--gray-100) !important;
}

/* was: onmouseover="this.style.color='var(--brand-orange)'; this.style.background='var(--warning-tint)'" */
.hover-icon-brand:hover {
    color: var(--brand-orange) !important;
    background: var(--warning-tint) !important;
}

/* was: onfocus="this.style.borderColor='var(--brand-orange)'; this.style.boxShadow='0 0 0 2px ...'" */
.focus-ring-brand:focus {
    border-color: var(--brand-orange) !important;
    box-shadow: 0 0 0 2px rgba(245, 166, 35, 0.2) !important;
}

.focus-ring-shopee:focus {
    border-color: var(--shopee-orange) !important;
    box-shadow: 0 0 0 2px rgba(238, 77, 45, 0.2) !important;
}


/* ==========================================================================
   ACCESSIBILITY
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
    * {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
    }
}

/* ============================================================================
   FILTER SELECT — multi-seleção com busca
   ============================================================================
   Substitui `<select multiple>`, que não tem busca, esconde a convenção do
   ctrl-clique e ou trunca as opções ou vira uma torre ao lado dos outros
   controles. Reintroduzido em 2026-09-01 para a página de Boosters.

   Quatro decisões que este componente carrega, e que são o motivo de ele
   existir em vez de ser refeito em cada página:

   1. O visto é um SVG que nunca sai do fluxo (`visibility`, não `display`),
      então marcar uma opção não empurra o rótulo ao lado.
   2. O gatilho diz a seleção por extenso — "Todos os tópicos", "2
      selecionados" —, não um número solto num rótulo genérico.
   3. Aplicar e limpar ficam DENTRO do painel, e só aplicar fecha: uma
      seleção de vários valores se monta numa passada só.
   4. Quem usa isto atualiza a lista no lugar. Recriar os nós a cada clique
      destrói o elemento clicado no meio da propagação, e o fechamento por
      clique-fora passa a disparar em cada toque.
*/

.filter-select {
    position: relative;
}

.filter-select-trigger {
    display: inline-flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    width: 100%;
    min-width: 180px;
    height: var(--control-height);
    padding: 0 12px;
    background-color: var(--container-bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-primary);
    text-align: left;
}

.filter-select-trigger:hover {
    border-color: var(--text-muted);
}

.filter-select-trigger:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.filter-select-trigger[aria-expanded="true"] {
    border-color: var(--brand-orange);
}

.filter-select-trigger .filter-select-caret {
    flex-shrink: 0;
    color: var(--text-secondary);
}

.filter-select-panel {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 800;
    width: 280px;
    max-width: calc(100vw - 32px);
    padding: var(--space-3);
    background-color: var(--container-bg);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-soft);
}

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

.filter-select-search {
    width: 100%;
    margin-bottom: var(--space-2);
}

.filter-select-options {
    display: flex;
    flex-direction: column;
    max-height: 260px;
    overflow-y: auto;
}

.filter-select-option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 36px;
    padding: 6px 4px;
    border-radius: var(--radius);
    font-size: var(--text-sm);
    color: var(--text-primary);
}

.filter-select-option:hover {
    background-color: var(--gray-50);
}

.filter-select-option input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

/* O quadrado do visto nunca sai do fluxo — só fica invisível. Trocar por
   display:none faria cada rótulo pular ao ser marcado. */
.filter-select-check {
    flex-shrink: 0;
    color: var(--brand-orange);
    visibility: hidden;
}

.filter-select-option input:checked ~ .filter-select-check {
    visibility: visible;
}

.filter-select-option input:focus-visible ~ .filter-select-check {
    visibility: visible;
    color: var(--text-muted);
}

.filter-select-empty {
    padding: var(--space-3) 0;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    text-align: center;
}

.filter-select-actions {
    display: flex;
    justify-content: space-between;
    gap: var(--space-2);
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--border-color);
}

@media (max-width: 640px) {
    .filter-select-panel {
        width: 100%;
    }
}
