/* ═══════════════════════════════════════════════════════════
   Panel — slide-in side drawer (settings, chat, detail views)

   A general-purpose overlay panel: a backdrop plus a header/body/[footer]
   surface that slides in from an edge. This is NOT the fullscreen camera
   takeover (.tn-scanner-sheet, scanner.css) — deliberately a separate,
   simpler primitive. See "Why this isn't built on .tn-scanner-sheet" at
   the bottom of this comment.

     <div class="tn-panel-backdrop"></div>
     <aside class="tn-panel tn-panel--open">
       <div class="tn-panel-header">
         <h2 class="tn-panel-title">Inställningar</h2>
         <button class="tn-panel-close" aria-label="Stäng"><i class="bi bi-x-lg"></i></button>
       </div>
       <div class="tn-panel-body">
         … any content, including .tn-loading / .tn-empty / .tn-empty--error
         states from feedback.css …
       </div>
       <div class="tn-panel-footer"> <!-- optional -->
         <button class="tn-btn tn-btn--secondary tn-btn--block">Stäng</button>
       </div>
     </aside>

   Toggle `.tn-panel--open` to slide in/out (a transform transition, not
   `display`, so the animation actually runs). `.tn-panel--left` slides
   from the left instead of the default right. On mobile (≤768px) the
   panel collapses to a bottom sheet instead of a side drawer — see the
   mobile section below for why, it's not just a smaller version of the
   same layout.

   REQUIRED of every consumer — written from real incidents, not
   hypothetically:

   • Lock body scroll while open, release it on close:
       <body class="tn-scroll-lock">
     (defined in scanner.css as `body.tn-scroll-lock { overflow: hidden; }`
     — if you don't vendor scanner.css, copy that one line; do not invent a
     second scroll-lock class name for the same thing.)

   • The close control MUST be reachable — actually hit-testable, in every
     content state your body renders (loading / loaded / empty / error) —
     not merely present in the DOM and visually visible. Presence and
     `toBeVisible()` are not the same fact as "a tap here reaches this
     element": a full-coverage state overlay rendered INSIDE .tn-panel-body
     (e.g. an error state, a loading spinner) sits in a LOWER stacking
     context than .tn-panel-header, by construction, below — so it cannot
     cover the close button unless you deliberately override that z-index
     relationship. Don't. And verify with an actual click/tap assertion in
     whatever state you're testing, not a visibility check — a covered but
     visible element passes `toBeVisible()` and still swallows every tap.

   • This uses the shared z-index scale in tokens.css (--tn-z-panel).
     Anything in your product that needs to render above or below this
     panel should be placed using that scale, not a guessed literal — see
     tokens.css's "Z-index scale" section for the full ordering and where
     a new overlay belongs in it.

   • A trigger that opens this panel and can carry unread/pending state
     (e.g. a chat icon) should be able to signal that state WHILE the
     panel is closed — don't make "open the panel" the only way to
     discover something needs attention. Use `.tn-badge-dot` (badges.css)
     on the trigger for this; see that file's comment for the markup.

   • Thumb reach on mobile: the panel opens one-handed on a phone, so the
     dismiss control cannot only exist at the very top of a tall surface.
     The mobile bottom-sheet collapse (below) already helps — a
     bottom-anchored sheet's header sits closer to the thumb than a
     top-anchored fullscreen one would — but for content-heavy panels
     (settings, multi-step forms) also use `.tn-panel-footer` with a
     `.tn-btn--block` "Stäng"/"Klar" action, which is unambiguously
     reachable regardless of scroll position or panel height.
   ═══════════════════════════════════════════════════════════ */

.tn-panel-backdrop {
    position: fixed;
    inset: 0;
    z-index: var(--tn-z-panel);
    background: rgba(0, 0, 0, 0.4);
    animation: tnPanelFadeIn 0.15s ease;
}

@keyframes tnPanelFadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

.tn-panel {
    --tn-panel-w: 420px;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: var(--tn-z-panel);
    display: flex;
    flex-direction: column;
    width: min(var(--tn-panel-w), 100vw);
    background: var(--tn-bg-card);
    box-shadow: var(--tn-shadow-lg);
    transform: translateX(100%);
    transition: transform var(--tn-transition-slow);
}

.tn-panel--open {
    transform: translateX(0);
}

.tn-panel--left {
    right: auto;
    left: 0;
    transform: translateX(-100%);
}

.tn-panel--left.tn-panel--open {
    transform: translateX(0);
}

/* ── Header — its own stacking context, always above whatever the body
   renders. This is what makes the close button un-coverable by a body
   state (see the required-reading comment above): a later sibling with a
   higher z-index cannot be painted over by an earlier sibling with a
   lower one, regardless of what that earlier sibling's own content does
   internally. Don't add a z-index to .tn-panel-body or its children that
   exceeds this. ── */
.tn-panel-header {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--tn-space-md);
    flex-shrink: 0;
    padding: var(--tn-space-md) var(--tn-space-lg);
    border-bottom: 1px solid var(--tn-border);
}

.tn-panel-title {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--tn-text-primary);
}

.tn-panel-close {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border: none;
    border-radius: var(--tn-radius);
    background: none;
    color: var(--tn-text-muted);
    cursor: pointer;
    transition: all var(--tn-transition);
}

.tn-panel-close:hover {
    background: var(--tn-bg-page);
    color: var(--tn-text-primary);
}

.tn-panel-body {
    position: relative;
    z-index: 0;
    flex: 1;
    overflow-y: auto;
    padding: var(--tn-space-lg);
}

.tn-panel-footer {
    position: relative;
    z-index: 1;
    flex-shrink: 0;
    padding: var(--tn-space-md) var(--tn-space-lg);
    border-top: 1px solid var(--tn-border);
}

/* ── Mobile — a bottom sheet, not a narrower side drawer.
   Two independent reasons, not one: (1) a full-height side drawer on a
   narrow viewport IS effectively the whole screen anyway, so there's no
   width to preserve; (2) a bottom-anchored sheet puts its header — and
   the close control in it — closer to a thumb's natural reach than a
   drawer sliding in from the right ever would, which is the actual fix
   for the one-handed-phone requirement, not a smaller font or a padding
   tweak. Capped at 85vh, not 100vh, so a strip of backdrop stays visible
   and tappable above the sheet — that visible strip is also how a user
   dismisses it without hunting for the close button at all. ── */
@media (max-width: 768px) {
    .tn-panel,
    .tn-panel--left {
        top: auto;
        right: 0;
        left: 0;
        width: 100%;
        max-height: 85vh;
        border-top-left-radius: var(--tn-radius-xl);
        border-top-right-radius: var(--tn-radius-xl);
        transform: translateY(100%);
    }

    .tn-panel--open,
    .tn-panel--left.tn-panel--open {
        transform: translateY(0);
    }
}

/* ═══════════════════════════════════════════════════════════
   Why this isn't built on .tn-scanner-sheet

   They share exactly three trivial, single-line facts: fixed position,
   `body.tn-scroll-lock` while open, and a close action. Everything else
   diverges on purpose:
     - scanner-sheet is fullscreen, edge-to-edge, video-first, almost no
       chrome (a topbar only), permanently dark — the video has to fill
       the frame and nothing can compete with it visually.
     - panel is partial-width, backdrop-revealing, content-first, full
       header/body/footer chrome, matches the rest of the app's light
       .tn-card surface — it's a workspace, not a capture device.
   Sharing a base component for three one-line facts would mean the base
   has almost nothing real in it while every meaningful property (sizing,
   positioning axis, theming, chrome) gets overridden per surface anyway —
   an abstraction that removes no actual duplication. What IS shared is
   already shared correctly: the `.tn-scroll-lock` utility (same class,
   both use it) and the z-index scale in tokens.css (both are entries in
   the same authoritative ordering, not independent guesses). Share the
   atoms; don't force a shared structure where the structures themselves
   don't match.
   ═══════════════════════════════════════════════════════════ */
