/*
 * Public Shelf share page — static stylesheet shared by every generated
 * /shelf/<token>.html page. Deliberately checked in as a plain file (not
 * regenerated per player) so publishing/refreshing a shelf never duplicates
 * styling — see PublicShelfHelper.php.
 *
 * Values mirror the React app's theme (app/src/styles/theme.js), ShelfView.js
 * and ItemCard.js as closely as a static, non-interactive snapshot can — same
 * wood/board colors, float/shadow animations, badges. No selection/hover
 * states are needed since this page has no interactivity.
 */

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    /* min-height statt height (seit 2026-09-18): body ist ein Flex-Container,
       und mit einer FESTEN Hoehe von 100 % streckte er den weissen Kasten
       (.ls-frame) auf genau eine Bildschirmhoehe. Laengerer Inhalt — eine
       Rangliste mit 200 Plaetzen, ein Regal mit vier Brettern — lief unten
       aus dem Kasten heraus, und darunter lag nur noch der Seitenhintergrund.
       Mit min-height waechst der Kasten mit seinem Inhalt und bleibt auf einer
       kurzen Seite trotzdem bildschirmhoch (.ls-frame's min-height: 100vh). */
    min-height: 100%;
}

body {
    font-family: "Inter", "Segoe UI", system-ui, sans-serif;
    -webkit-font-smoothing: antialiased;
    background-color: #F5EFE8;
    background-image: repeating-linear-gradient(135deg, #FAF6F1 0px, #FAF6F1 2px, transparent 2px, transparent 16px);
    color: #1C110A;
    display: flex;
    justify-content: center;
    min-height: 100%;
}

.ls-frame {
    width: 100%;
    max-width: 560px;
    min-height: 100vh;
    background: #FFFFFF;
    box-shadow: 0 0 40px rgba(0, 0, 0, 0.12);
    display: flex;
    flex-direction: column;
}

/* ── Header ─────────────────────────────────────────────────────────────── */

.ls-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
    flex-shrink: 0;
}

.ls-brand-signet {
    height: 32px;
    width: auto;
    flex-shrink: 0;
}

.ls-brand-name {
    font-weight: 700;
    font-size: 0.78rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #9E7A4F;
}

.ls-header-spacer { flex: 1; }

.ls-player-block {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 3px;
}

.ls-badge-row {
    display: flex;
    align-items: center;
    gap: 6px;
}

.ls-player-name {
    font-size: 0.82rem;
    color: #6B4F3A;
    line-height: 1;
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ls-score-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: rgba(158, 122, 79, 0.15);
    border: 1px solid rgba(158, 122, 79, 0.35);
    border-radius: 20px;
    padding: 2px 10px 2px 7px;
    font-size: 0.78rem;
    font-weight: 700;
    color: #9E7A4F;
    line-height: 1;
    white-space: nowrap;
}

.ls-updated {
    padding: 6px 16px;
    font-size: 0.68rem;
    color: #B09080;
    text-align: right;
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

/* Read-only snapshot count (as of last publish/refresh) — liking itself
   happens from ProfileView.js in the live app, not this static page. */
.ls-like-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: rgba(200, 60, 60, 0.1);
    border: 1px solid rgba(200, 60, 60, 0.3);
    border-radius: 20px;
    padding: 2px 10px 2px 7px;
    font-size: 0.78rem;
    font-weight: 700;
    color: #C83C3C;
    line-height: 1;
    white-space: nowrap;
}

/* ── Group frame ────────────────────────────────────────────────────────────
   Die Gruppenzugehoerigkeit des Spielers, unter der Kopfzeile. Bewusst breit,
   zentriert und mit doppelter Rahmenlinie: das ist ein Wappen, kein Datenfeld
   — deshalb auch hier, ueber dem Regal, und nicht als weiteres Badge in der
   Reihe neben Punktzahl und Herzen.

   Das Markup dazu baut PlayerGroupDisplayHelper::renderStaticFrame(); die
   Bild- und Link-Varianten (.ls-group-image, .ls-group-frame--link) sind schon
   mitgestaltet, obwohl eine Gruppe beides noch nicht hat. */

.ls-group-frame {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    margin: 14px 16px 0;
    padding: 12px 18px;
    border: 1px solid rgba(158, 122, 79, 0.45);
    /* Zweite, innen liegende Linie ueber box-shadow statt eines zweiten
       Elements — haelt das Markup bei einem einzigen Kasten. */
    box-shadow: inset 0 0 0 4px rgba(255, 255, 255, 0.55),
                inset 0 0 0 5px rgba(158, 122, 79, 0.25),
                0 2px 10px rgba(0, 0, 0, 0.08);
    border-radius: 14px;
    background: linear-gradient(160deg, rgba(158, 122, 79, 0.16), rgba(158, 122, 79, 0.04));
    text-align: center;
    text-decoration: none;
}

.ls-group-eyebrow {
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: #B09080;
    line-height: 1;
}

.ls-group-name {
    font-size: 1.05rem;
    font-weight: 800;
    color: #6E5233;
    line-height: 1.25;
    /* Ein langer Gruppenname darf umbrechen statt abgeschnitten zu werden —
       anders als der Spielername oben ist das hier der Inhalt des Rahmens. */
    overflow-wrap: anywhere;
}

/* Reserviert (siehe PlayerGroupDisplayHelper): Gruppenbild ueber dem Namen. */
.ls-group-image {
    width: 44px;
    height: 44px;
    object-fit: contain;
    display: block;
}

/* Reserviert: verlinkte Gruppe. */
.ls-group-frame--link:hover {
    border-color: rgba(158, 122, 79, 0.7);
}

/* ── Shelf ──────────────────────────────────────────────────────────────── */

.ls-scroll {
    flex: 1;
    /* Unten KEIN Abstand mehr: den traegt das Regal selbst (.ls-shelf), damit
       er innerhalb der Tapete liegt. Lag er hier, endete die Wand oberhalb
       davon und darunter stand die weisse Seite — genau der Streifen, den es
       in der App auch schon gab (siehe ShelfView.js's SHELF_FLOOR). */
    padding: 0;
    /* Bezugspunkt fuer die absolut gesetzten Blaetter-Griffe darin. */
    position: relative;
    /* Definierte Hoehe fuer die min-height-Kette darunter. */
    display: flex;
    flex-direction: column;
}

/* Die Wand soll den GANZEN Regalbereich fuellen, nicht nur bis zum letzten
   Brett reichen. Dafuer wird die Hoehe des Rollbereichs bis zum Regal
   durchgereicht — die Tapete liegt als Ebene IM Regal (inset: 0) und ist damit
   genau so hoch wie dieses.

   Ueber flex: 1 und nicht ueber min-height: 100%: eine Prozenthoehe braucht
   einen Vorfahren mit definierter Hoehe, und die Kette darueber (.ls-frame hat
   nur min-height: 100vh) liefert die nicht zuverlaessig. Ein Flex-Kind, das
   den Rest nimmt, kommt ohne diese Voraussetzung aus. */
.ls-shelves {
    flex: 1;
    display: flex;
    flex-direction: column;
}

.ls-shelf-container {
    /* 560 = dieselbe Rechnung wie in der App (styles/spacing.js's
       contentBoxWidth(): CONTENT_MAX_WIDTH 528 plus zweimal 16 Innenabstand).
       Stand bis 2026-09-08 auf 480 und liess damit jedes Item hier rund ein
       Sechstel kleiner erscheinen als im Spiel. */
    max-width: 560px;
    margin: 0 auto;
    padding: 0 16px;
}

.ls-item-row {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    /* 12 wie in der App (shared/shelfBoard.js's ItemRow) — dort von 2 auf 12
       erhoeht, als die Schlagschatten unter den Karten breiter wurden: bei
       2px Luft liefen die Schatten benachbarter Karten ineinander. Diese
       Seite trug die alte 2 bis 2026-09-08 weiter. */
    gap: 12px;
    align-items: flex-end;
    position: relative;
    z-index: 1;
}

.ls-item-card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
    min-width: 0;
}

.ls-float {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
    min-width: 0;
    animation: ls-float 3.4s ease-in-out infinite;
    animation-fill-mode: backwards;
}

.ls-item-badge {
    align-self: center;
    margin-bottom: 5px;
    background: rgba(10, 5, 0, 0.56);
    backdrop-filter: blur(5px);
    -webkit-backdrop-filter: blur(5px);
    border-radius: 7px;
    padding: 2px 7px;
    font-size: 0.58rem;
    font-weight: 700;
    color: rgba(255, 248, 228, 0.92);
    white-space: nowrap;
    max-width: 94%;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 14px;
    letter-spacing: 0.02em;
}

.ls-item-img {
    width: 100%;
    max-width: 120px;
    height: auto;
    aspect-ratio: 80 / 68;
    object-fit: contain;
    display: block;
    position: relative;
    z-index: 1;
}

.ls-item-qty {
    position: absolute;
    bottom: -2px;
    right: 20%;
    z-index: 2;
    background: rgba(158, 122, 79, 0.92);
    border-radius: 8px;
    padding: 1px 6px;
    font-size: 0.6rem;
    font-weight: 800;
    color: #fff;
    line-height: 14px;
    letter-spacing: 0.2em;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}

.ls-cast-shadow {
    width: 68%;
    height: 9px;
    background: #000;
    filter: blur(6px);
    border-radius: 50%;
    flex-shrink: 0;
    margin-top: 10px;
    z-index: 0;
    opacity: 0.38;
    animation: ls-shadow 3.4s ease-in-out infinite;
    animation-fill-mode: backwards;
    transform-origin: center;
}

/* ── Collection board row ──────────────────────────────────────────────── */

.ls-collection-row-wrap { position: relative; }

.ls-collection-slot {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    width: 100%;
    min-width: 0;
}

.ls-collection-slot-img {
    width: 100%;
    max-width: 80px;
    height: auto;
    aspect-ratio: 80 / 68;
    object-fit: contain;
    opacity: 0.3;
    filter: grayscale(1);
}

.ls-collection-slot-img.ls-filled {
    opacity: 1;
    filter: none;
}

.ls-collection-slot-shadow {
    width: 68%;
    height: 9px;
    background: #000;
    filter: blur(6px);
    border-radius: 50%;
    flex-shrink: 0;
    margin-top: 10px;
    opacity: 0;
    transform-origin: center;
}

.ls-collection-slot-shadow.ls-visible {
    opacity: 0.38;
    animation: ls-shadow 3.4s ease-in-out infinite;
    animation-fill-mode: backwards;
}

/* ── Board (shelf-skin graphic under each row) ─────────────────────────────
   Mirrors app/src/components/shared/shelfBoard.js's ShelfBoardSkin 1:1 (same
   skin data, see SimpleShelfSkinModel/PublicShelfHelper) — a whole pre-
   colored SVG via <img>, aspect-ratio forced on the wrapper (inline
   style="aspect-ratio: …" in shelf.phtml, from $skin['width']/['height'])
   so it can't be stretched/squashed independently on either axis, shadow as
   a ::after pseudo-element instead of a separate DOM node. */

.ls-board-skin {
    width: 100%;
    margin-top: -9%;
    position: relative;
    z-index: 0;
}

/* Wie ShelfBoardSkin in der App (shared/shelfBoard.js): auf schmalen
   Bildschirmen ein Prozent weniger Ueberlappung. Die Hoehe des Bretterstapels
   ist die Bezugsflaeche der Deko — jede Abweichung hier verschiebt sie. */
@media (max-width: 425px) {
    .ls-board-skin {
        margin-top: -8%;
    }
}

/* Ein ausgeblendetes Brett (wohnliches Regal, ShelfHelper::BOARDS_FIELD).
   `visibility` und nicht `display`: der Kasten behaelt seine Hoehe, und mit
   ihr behaelt der Bretterstapel seine — daran haengt die Lage jedes
   Deko-Stuecks (Promille des Stapels, siehe .ls-decor-piece-wrap). Der
   Wurfschatten unten liegt als ::after IM Kasten und geht deshalb mit,
   ohne dass er hier noch einmal genannt werden muesste. */
.ls-board-skin--hidden {
    visibility: hidden;
}

.ls-board-skin::after {
    content: "";
    position: absolute;
    left: 16%;
    right: 16%;
    bottom: -10%;
    height: 22%;
    background: rgba(0, 0, 0, 0.32);
    filter: blur(9px);
    border-radius: 50%;
    z-index: -1;
    pointer-events: none;
}

.ls-board-skin-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    position: relative;
    z-index: 1;
    pointer-events: none;
}

/* ── Empty state ────────────────────────────────────────────────────────── */

.ls-empty {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #B09080;
    font-size: 0.9rem;
    padding: 40px 24px;
    text-align: center;
}

/* ── Sponsor / Baker (Partnerschaft) ───────────────────────────────────────
   A paid placement on one of the shelf owner's own created items (see PHP
   BakerModel). Rendered in two spots, deliberately with the SAME markup and
   classes so they can't drift: as a banner above the shelf (shelf.phtml) and
   above the item inside the lightbox (shelf.js builds it).

   NOT rendered behind the item cards themselves: a board row carries four
   ~90px cards, and a 3:1 panorama cropped to that is unrecognisable.

   The "Anzeige" tag is mandatory disclosure for paid placement — it is
   hardcoded in both renderers, never optional. */

.ls-sponsor {
    position: relative;
    display: block;
    margin: 8px 8px;
    /*height: 120px;*/
    border-radius: 12px;
    overflow: hidden;
    background-color: #EDE6DC;
    background-size: cover;
    background-position: center;
    text-decoration: none;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.14);
    aspect-ratio: 4 / 1;
}

/* Keeps the logo and title legible over an arbitrary photo the sponsor chose. */
.ls-sponsor::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.42));
}

.ls-sponsor-tag {
    position: absolute;
    top: 8px;
    left: 8px;
    z-index: 2;
    background: rgba(10, 5, 0, 0.55);
    color: rgba(255, 255, 255, 0.92);
    font-size: 0.55rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 2px 7px;
    border-radius: 4px;
}

.ls-sponsor-inner {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: row-reverse;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 0 16px;
}

.ls-sponsor-inner.ls-sponsor-left { flex-direction: row; }

/* White ground + padding + black border — the sponsor's logo usually assumes
   a light background, so this guarantees one regardless of the panorama. */
.ls-sponsor-logo {
    flex-shrink: 0;
    width: 62px;
    height: 62px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #fff;
    padding: 8px;
    border: 2px solid #000;
    border-radius: 10px;
    box-sizing: border-box;
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.28);
}

.ls-sponsor-logo img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.ls-sponsor-title {
    color: #fff;
    font-size: 0.92rem;
    font-weight: 800;
    letter-spacing: 0.01em;
    text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Inside the lightbox the banner sits in the stage's normal flow (above the
   item render), not in the page margin — so it drops the outer margin and
   matches the stage's own width. */
.ls-sponsor-lightbox {
    margin: 0;
    width: 100%;
    height: 96px;
    cursor: pointer;
}

/* ── Rankings (PublicRankingHelper) ────────────────────────────────────────
   Mirrors RankingView.js's Row/Rank/Name/Score/MeBadge styling as closely as
   a static, light-themed, non-interactive page can — same gold/brown accent
   (#9E7A4F family) the rest of this page already uses for score/like badges,
   just applied to a full ranked list instead. No "this is you" highlighting
   here (see PublicRankingHelper's docblock) — every row looks the same.
   Used both inline (embedded in every /shelf/<token>.html) and on the
   standalone /shelf/index.html (see ranking_page.phtml). */

.ls-rank-section {
    margin: 8px 0 0;
    padding: 0 16px;
    /* Die Farben des gewaehlten Reiters. Als Variablen, weil die Regeln, die
       sie anwenden, teils hier und teils in dem von ranking.phtml erzeugten
       <style>-Block stehen — die FARBE soll trotzdem nur an einer Stelle
       stehen. */
    --ls-rank-tab-active-bg: #9E7A4F;
    --ls-rank-tab-active-fg: #fff;
}

.ls-scroll-plain {
    padding-top: 24px;
}

.ls-rank-title {
    margin: 0 0 12px;
    font-size: 0.95rem;
    font-weight: 800;
    color: #1C110A;
}

/* Pure CSS tab switcher — hidden radio inputs + <label for>, panels shown
   via the ~ :checked selectors below. No JavaScript involved at all. */
.ls-rank-tab-input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.ls-rank-tabs {
    display: flex;
    gap: 6px;
    margin-bottom: 12px;
    flex-wrap: wrap;
}

.ls-rank-tab {
    display: inline-block;
    padding: 5px 12px;
    border-radius: 20px;
    border: 1px solid rgba(158, 122, 79, 0.3);
    background: rgba(158, 122, 79, 0.08);
    color: #9E7A4F;
    font-size: 0.74rem;
    font-weight: 700;
    cursor: pointer;
    user-select: none;
}

/* Das Aussehen des gewaehlten Reiters. Welche Kennung gerade gewaehlt IST,
   verdrahtet jede Seite fuer sich (ranking.phtml schreibt dafuer einen
   kleinen <style>-Block aus den Kennungen, die sie eben ausgegeben hat) —
   hier stehen nur die Farben, damit sie an einer Stelle liegen.

   Die Liste unten ist der Rueckfall fuer BESTANDSSEITEN: jede
   /shelf/<token>.html, die vor dem 2026-09-18 erzeugt wurde, bringt den
   <style>-Block noch nicht mit, laedt dieses Stylesheet aber frisch. Sie
   nennt deshalb alle heute registrierten Listen (RankingRegistry) — und
   genau das war der Fehler: sie kannte nur score/inventory/unique, und die
   vier Listen daneben (newcomer, active, quality, archive) hatten zwar einen
   Reiter, liessen sich aber nicht oeffnen. Eine neue Liste braucht hier
   nichts mehr; die Seite bringt ihre Verdrahtung selbst mit. */
#ls-rank-tab-score:checked ~ .ls-rank-tabs label[for="ls-rank-tab-score"],
#ls-rank-tab-newcomer:checked ~ .ls-rank-tabs label[for="ls-rank-tab-newcomer"],
#ls-rank-tab-active:checked ~ .ls-rank-tabs label[for="ls-rank-tab-active"],
#ls-rank-tab-quality:checked ~ .ls-rank-tabs label[for="ls-rank-tab-quality"],
#ls-rank-tab-unique:checked ~ .ls-rank-tabs label[for="ls-rank-tab-unique"],
#ls-rank-tab-inventory:checked ~ .ls-rank-tabs label[for="ls-rank-tab-inventory"],
#ls-rank-tab-archive:checked ~ .ls-rank-tabs label[for="ls-rank-tab-archive"] {
    background: var(--ls-rank-tab-active-bg);
    border-color: var(--ls-rank-tab-active-bg);
    color: var(--ls-rank-tab-active-fg);
}

.ls-rank-panel { display: none; }

#ls-rank-tab-score:checked ~ .ls-rank-panel[data-panel="score"],
#ls-rank-tab-newcomer:checked ~ .ls-rank-panel[data-panel="newcomer"],
#ls-rank-tab-active:checked ~ .ls-rank-panel[data-panel="active"],
#ls-rank-tab-quality:checked ~ .ls-rank-panel[data-panel="quality"],
#ls-rank-tab-unique:checked ~ .ls-rank-panel[data-panel="unique"],
#ls-rank-tab-inventory:checked ~ .ls-rank-panel[data-panel="inventory"],
#ls-rank-tab-archive:checked ~ .ls-rank-panel[data-panel="archive"],
.ls-rank-panel-solo {
    display: block;
}

.ls-rank-empty {
    padding: 20px 0;
    text-align: center;
    color: #B09080;
    font-size: 0.85rem;
}

.ls-rank-list {
    list-style: none;
    margin: 0 0 20px;
    padding: 0;
}

.ls-rank-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    margin-bottom: 5px;
    border-radius: 10px;
    background: #FAF6F1;
    border: 1px solid rgba(0, 0, 0, 0.06);
}

.ls-rank-row.ls-rank-top {
    background: rgba(158, 122, 79, 0.08);
    border-color: rgba(158, 122, 79, 0.25);
}

.ls-rank-num {
    width: 26px;
    flex-shrink: 0;
    text-align: center;
    font-size: 0.72rem;
    font-weight: 700;
    color: #B09080;
}

.ls-rank-row.ls-rank-top .ls-rank-num {
    font-size: 1.05rem;
}

.ls-rank-name {
    flex: 1;
    min-width: 0;
    font-size: 0.84rem;
    font-weight: 600;
    color: #1C110A;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

a.ls-rank-link {
    color: #9E7A4F;
    text-decoration: none;
}

a.ls-rank-link:hover {
    text-decoration: underline;
}

.ls-rank-score {
    flex-shrink: 0;
    font-size: 0.8rem;
    font-weight: 700;
    color: #9E7A4F;
    letter-spacing: 0.02em;
}

/* ── Footer ─────────────────────────────────────────────────────────────── */

.ls-footer {
    padding: 14px 16px;
    text-align: center;
    font-size: 0.68rem;
    color: #B09080;
    border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.ls-footer-link {
    color: #9E7A4F;
    font-weight: 700;
    text-decoration: none;
}

.ls-footer-link:hover {
    text-decoration: underline;
}

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

@keyframes ls-shadow {
    0%, 100% { transform: scaleX(1); opacity: 0.38; }
    50%      { transform: scaleX(0.68); opacity: 0.20; }
}

/* ── Item lightbox ──────────────────────────────────────────────────────────
   Tapping a regular shelf item opens a full-size preview (built entirely by
   shelf.js — no per-item markup needed from PublicShelfHelper). Backdrop and
   button styling deliberately mirror the live app's own overlay conventions
   (app/src/styles/feedbackStyle.js's PORTAL_BACKDROP/PORTAL_BLUR, and
   ViewHeader.js's circular RoundBtn), so this "static" page still feels like
   the same product. */

.ls-item-card { cursor: pointer; }

.ls-lightbox {
    position: fixed;
    inset: 0;
    z-index: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.55);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    animation: ls-lightbox-fadein 0.2s ease both;
    padding: 24px;
    box-sizing: border-box;
    cursor: pointer;
}

.ls-lightbox-stage {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    max-width: min(92vw, 320px);
    cursor: default;
    animation: ls-lightbox-popin 0.22s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

/* Real "outline" render, take 3 — the previous two attempts (scaled mask
   layers, then a long chained drop-shadow() stack) either grew the shape
   unevenly or made ordering/width control fiddly (each drop-shadow's ring
   is measured from whatever the chain already produced, not straight from
   the artwork). An SVG filter's feMorphology "dilate" is the actual right
   tool: it expands the SOURCE alpha mask by a given radius directly, so a
   black ring (dilate 22) and a white ring (dilate 20) are each independent,
   both measured straight from the artwork's own shape — the white ring
   (0-20) and the extra 2px sliver of black beyond it (20-22) can't drift out
   of proportion the way chained shadow copies could. Defs live in the hidden
   <svg> shelf.js injects once (see ensureOutlineFilter()); this is just the
   CSS that references it, plus the one soft "under everything" shadow. */
.ls-lightbox-render {
    position: relative;
    width: 256px;
    height: 256px;
    max-width: 70vw;
    max-height: 70vw;
}

.ls-lightbox-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    filter: url(#ls-outline-filter) drop-shadow(0 6px 10px rgba(0, 0, 0, 0.35));
    /* Ueber dem Rarity-Schein (siehe unten), der als ::before in derselben
       Box liegt und ohne das ueber dem Bild stuende. */
    position: relative;
    z-index: 1;
}

/* ── Rarity-Akzent ─────────────────────────────────────────────────────────
   Nur in der Detail-Ansicht: die Stufe faerbt einen weichen Schein hinter dem
   Stueck und ein Schildchen unter dem Namen. Die Farbe kommt aus
   SimpleRarityModel (ueber data-rarity-color, siehe shelf.js's
   renderRarity()) und liegt als --ls-rarity-color an der Buehne — dieselbe
   Farbe, die die App fuer dieselbe Stufe verwendet.

   Beides haengt an .ls-has-rarity: kennt die Seite die Stufe nicht (alte
   Momentaufnahme ohne data-rarity-*, oder RARITY_UNKNOWN), bleibt die
   Ansicht exakt die bisherige, statt in einem Standardgrau zu leuchten. */
.ls-lightbox-render::before {
    content: '';
    position: absolute;
    /* Groesser als das Bild, damit der Schein hinter der Kontur hervorkommt
       und nicht an der Bildkante abbricht. */
    inset: -14%;
    border-radius: 50%;
    background: radial-gradient(circle at 50% 50%,
        var(--ls-rarity-color, transparent) 0%,
        rgba(255, 255, 255, 0) 68%);
    opacity: 0;
    transition: opacity 0.25s ease;
    pointer-events: none;
}

.ls-has-rarity .ls-lightbox-render::before { opacity: 0.42; }

.ls-lightbox-rarity {
    font-size: 0.72rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #fff;
    /* Der Farbwert selbst traegt den Akzent — der dunkle Schlagschatten haelt
       helle Stufen (Legendary/Uncommon) auf dem hellen Hintergrund lesbar. */
    background: var(--ls-rarity-color, #9E9E9E);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
    padding: 4px 14px;
    border-radius: 999px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
    margin-top: -4px;
}

/* Keeps the filter defs out of layout/paint entirely — same "invisible host"
   pattern as any hidden SVG sprite sheet. */
.ls-svg-defs {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
}

.ls-lightbox-name {
    font-size: 1rem;
    font-weight: 700;
    color: #1C110A;
    text-align: center;
    max-width: 80vw;
    overflow-wrap: break-word;
    background: rgba(255, 255, 255, 0.7);
    padding: 6px 16px;
    border-radius: 12px;
}

.ls-lightbox-close,
.ls-lightbox-nav {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 2px solid rgba(0, 0, 0, 0.08);
    background: #fff;
    color: #6B4F3A;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 510;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
    padding: 0;
    line-height: 1;
}

.ls-lightbox-close:active,
.ls-lightbox-nav:active { opacity: 0.7; }

.ls-lightbox-close {
    position: fixed;
    top: 16px;
    right: 16px;
    font-size: 1.3rem;
    font-weight: 700;
}

/* Die Blaetter-Knoepfe sind seit 2026-09-20 deutlich groesser als der
   Schliessen-Knopf (64 statt 44), und sie sitzen in einer unsichtbaren ZONE,
   die weit ueber den Knopf hinausreicht.

   Warum die Zone: der Hintergrund schliesst die Ansicht, und die Knoepfe stehen
   am Bildschirmrand — genau dort, wo der Daumen beim einhaendigen Halten
   ohnehin liegt. Wer knapp danebentippt, hatte damit nicht "nichts" getroffen,
   sondern die Ansicht zugemacht und musste sich das Stueck neu heraussuchen.

   Die Zone faengt den Klick nur AB (stopPropagation in shelf.js), sie blaettert
   nicht: ein Tippen daneben soll folgenlos sein, nicht ungefragt
   weiterspringen. Dasselbe Verhalten wie in der App
   (shared/ItemStickerLightbox.js's NavZone) — zwei Oberflaechen, ein
   Verhalten. */
.ls-lightbox-navzone {
    position: fixed;
    bottom: 0;
    /* Das Polster IST die Trefferflaeche: 64er Knopf plus ringsum 26/20 ergibt
       gut 110 x 116 — knapp die dreifache Flaeche von vorher. */
    padding: 26px 20px calc(26px + env(safe-area-inset-bottom, 0px));
    z-index: 509;
    display: flex;
    cursor: default;
}

.ls-lightbox-nav {
    width: 64px;
    height: 64px;
    font-size: 2.1rem;
    font-weight: 800;
}

.ls-lightbox-navzone-prev { left: 0; }
.ls-lightbox-navzone-next { right: 0; }

@keyframes ls-lightbox-fadein {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@keyframes ls-lightbox-popin {
    0%   { opacity: 0; transform: scale(0.92) translateY(8px); }
    100% { opacity: 1; transform: scale(1) translateY(0); }
}

/* ── Mehrere Regale ────────────────────────────────────────────────────────
   Namensschild und Blaettern, Gegenstueck zu shared/ShelfSignPlate.js und der
   Karussell-Navigation in ShelfView.js. Die Farben sind hier wie dort fest
   verdrahtet: das Schild haengt an der Regalwand, es gehoert zur Szene und
   nicht zur Oberflaeche, und die Szene hat kein Farbschema.

   Ohne JavaScript setzt niemand .ls-carousel-ready — dann stehen alle Regale
   untereinander und die Pfeile bleiben verborgen. Das ist der Normalfall, den
   die Seite koennen muss; das Blaettern ist die Zugabe. */

/* Namensschild — 1:1 Portierung von shared/ShelfSignPlate.js (React), inkl.
   derselben vier SVG-Teile (assets/signs/, hierher unter /shelf/assets/signs/
   kopiert statt ueber den gehashten Webpack-Output der App verlinkt, der sich
   bei jedem Deploy aendert). Feste Hoehe 88px / max-Breite 420px, weil diese
   Seite immer nur EINE Auspraegung braucht — die React-Komponente parametrisiert
   das nur, weil sie an mehreren Stellen (u.a. kleiner in ProfileView) wiederverwendet wird.
   Zahlen unten (34.71%/11.2%/99.65px) sind aus ShelfSignPlate.js's eigenen
   UNITS_H/UNITS_CAP/FIELD_TOP/FIELD_BOTTOM-Konstanten bei genau dieser Hoehe
   ausgerechnet — bei einer Aenderung dort muessen sie hier neu berechnet werden. */
.ls-sign-frame {
    position: relative;
    /* 10px oben wie in der App (shared/ShelfSignPlate.js) — fehlten hier, das
       Schild sass dadurch enger unter dem Kopfbereich als im Spiel. */
    margin: 10px auto 20px;
    width: 100%;
    max-width: min(100%, 420px);
    /* 88px ist die MINDEST-Hoehe — shelf.js's Block 4 misst, ob ein
       umbrechender Name mehr Platz braucht, und setzt height dann per
       Inline-Style hoeher (Kappen/Ornament sind height:100%/width:auto und
       skalieren automatisch mit, kein separates Nachziehen noetig). */
    height: 89px;
    /* Ohne Mindestbreite schieben sich die beiden Kappen ineinander. */
    min-width: 99.65px;
    display: flex;
    align-items: stretch;
    user-select: none;
}

.ls-sign-cap {
    height: 100%;
    width: auto;
    flex-shrink: 0;
    display: block;
    pointer-events: none;
}

/* Einziges gestrecktes Teil — als Hintergrund statt <img>, weil
   background-size:100% 100% das Seitenverhaeltnis bewusst NICHT wahrt. */
.ls-sign-body {
    flex: 1;
    min-width: 0;
    background-image: url(signs/shelf-sign-body.svg);
    background-size: 100% 100%;
    background-repeat: no-repeat;
}

/* Mittig ueber der Naht zwischen Kappen und Koerper, in Originalgroesse —
   von shelf.js ausgeblendet (display:none), sobald der Rahmen dafuer zu
   schmal ist (siehe dort, MIN_ORNAMENT_UNITS). */
.ls-sign-ornament {
    position: absolute;
    left: 50%;
    top: 0;
    transform: translateX(-50%);
    height: 100%;
    width: auto;
    pointer-events: none;
}

/* Ueber dem Koerper, nicht ueber den Kappen — auf das helle Innenfeld
   gesetzt (FIELD_TOP/FIELD_BOTTOM), nicht auf den ganzen Rahmen.

   Bricht um statt abzuschneiden (white-space normal + overflow-wrap
   anywhere) — ein zu langer Name wird nicht mehr mit "…" gekappt, sondern
   wandert auf eine zweite/dritte Zeile; `anywhere` statt nur `break-word`,
   damit auch EIN einzelnes, zu langes Wort ohne Leerzeichen notfalls
   mitten im Wort umbricht, statt ueber den Rahmen hinauszulaufen oder
   abgeschnitten zu werden — vollstaendig sichtbar schlaegt huebsch
   umbrochen. Wie hoch dafuer .ls-sign-frame werden muss, misst shelf.js's
   Block 4. text-align:center haelt auch mehrzeiligen Text mittig. */
.ls-sign-label {
    position: absolute;
    left: 0;
    right: 0;
    top: 34.71%;
    bottom: 11.2%;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Kappen aussparen (~12% der Breite bei dieser Groesse), damit die
       Schrift nie auf dem Schnitzwerk landet. */
    padding: 0 13%;
    box-sizing: border-box;
    font-size: 0.92rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    text-align: center;
    color: #4A3411;
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.45), 0 -1px 0 rgba(70, 50, 15, 0.35);
    white-space: normal;
    overflow-wrap: anywhere;
    line-height: 1.15;
    pointer-events: none;
}

.ls-shelf + .ls-shelf { margin-top: 28px; }

/* Bezugspunkt fuer die Tapeten-Ebene darin (siehe shelf.phtml). Ohne ihn
   haengt sie am naechsten positionierten Vorfahren und deckt das falsche
   Stueck Seite ab.

   flex: 1 laesst das Regal den sichtbaren Bereich fuellen, auch wenn nur zwei
   Bretter darin stehen — sonst endete die Wand am letzten Brett. Der
   Innenabstand unten ist der Boden: Wand unter dem untersten Brett statt
   sofort die Kante (dieselbe Ueberlegung wie SHELF_FLOOR in der App). */
.ls-shelf {
    position: relative;
    flex: 1;
    padding-bottom: 56px;
}

/* Die Tapete des Regals — eine eigene Ebene hinter Schild, Bretter und
   Items, damit ihre Deckkraft nur sie betrifft. Seit 2026-10-02 nur noch im
   Rahmen (.ls-shelf-stage), nicht mehr ueber das ganze Regal — wie in der
   App (shared/shelfStage.js's WallpaperFrame). Rundet selbst ab, weil der
   Rahmen kein overflow: hidden hat (Deko darf hinausragen). */
.ls-shelf-wallpaper {
    position: absolute;
    inset: 0;
    z-index: 0;
    border-radius: inherit;
    pointer-events: none;
}

/* Alles andere im Regal steht davor. */
.ls-shelf > *:not(.ls-shelf-wallpaper) { position: relative; z-index: 1; }

/* ── Frei aufgestellte Deko ──────────────────────────────────────────────
   Dieselbe Ebene wie in der App (shared/ShelfDecorLayer.js): ueber Brettern
   und Items, denn eine Vase steht vor dem Regal und nicht darin. Die
   Koordinaten kommen als Prozent der Regalflaeche aus der Vorlage, der
   Bezugspunkt ist der FUSS des Stuecks (unten Mitte) — so steht es auf einer
   Stelle, statt um seinen Mittelpunkt zu schweben.

   Hier ist nichts anfassbar: das Aufstellen passiert in der App. */
/* Schild und Bretter zusammen — das Regal. Bis zum 2026-09-09 auch die
   Flaeche, auf der Deko steht; jetzt ist das der Bretterstapel darunter
   (.ls-board-stack), siehe dort. */
/* Seit 2026-10-02 zugleich der Tapeten-Rahmen: runde Ecken, Rand, leichter
   Schatten wie ein Mini-Regal, nur mit dem Schild darin. Masse wie in der App
   (WallpaperFrame in ShelfContainer): 16 Rand aussen, 8 innen, 1 Rand —
   der Innenabstand des Containers darin entfaellt, damit die Bretter genau
   so breit sind wie im Spiel (die Deko misst sich gegen sie). */
.ls-shelf-stage {
    position: relative;
    margin: 8px 16px 0;
    padding: 4px 8px 20px;
    border-radius: 16px;
    border: 1px solid rgba(0, 0, 0, 0.10);
    /* KEIN eigener Grund: Seiten, die vor dem 2026-10-02 erzeugt wurden,
       tragen die Tapete noch ueber dem ganzen Regal (.ls-shelf-wallpaper als
       Kind von .ls-shelf) und teilen sich diese Datei — ein deckender Grund
       laege dort WEISS ueber ihrer Tapete. Ohne Tapete scheint ohnehin das
       Weiss der Seite (.ls-frame) durch. */
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.14);
}

.ls-shelf-stage .ls-shelf-container { padding: 0; }

/* Der Bretterstapel — die Flaeche, auf der Deko steht, dieselbe wie in der
   App (ShelfView.js's BoardStack). Seine Hoehe kommt aus dem INHALT und nicht
   aus dem Fenster: eine Bezugsflaeche, die am Fenster haengt, verschoebe
   jedes Stueck, sobald der Besucher die Seite zoomt. Und NICHT mehr das
   Schild dazu: das wird auf schmalen Bildschirmen hoeher, weil sein Titel
   umbricht, und rueckte die Bretter unter einem Stueck weg, das nur um den
   Anteil seiner Hoehe mitging. */
.ls-board-stack {
    position: relative;
    /* Ueber dem Schild davor (.ls-sign-frame, position: relative ohne
       z-index): Deko, die nach oben herausragt, liegt damit VOR dem Schild,
       nicht dahinter — wie in der App. */
    z-index: 1;
}

.ls-shelf-decor {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    /* KEIN overflow: hidden mehr (seit 2026-09-11): ein Stueck darf ueber die
       Oberkante des Bretterstapels hinausragen und VOR dem Namensschild
       stehen — wie in der App (shared/ShelfDecorLayer.js). Vorher wurden
       Stuecke am oberen Rand abgeschnitten. */
}

/* Breite als Anteil des Bretterstapels, nicht in Pixeln — 72px auf dessen
   Referenzbreite von 528 (dieselbe Rechnung wie PIECE_WIDTH_PCT in
   shared/ShelfDecorLayer.js). Ein festes 72px blieb gleich gross, waehrend
   Bretter und Items mit dem Bildschirm schrumpften; das Stueck wuchs damit
   im Verhaeltnis und lag auf dem Telefon ueber den Items daneben. */
/* Ein Stueck ist seit 2026-09-11 ein Kasten (Schatten + Bild) statt eines
   nackten <img>, damit der Wurfschatten (.ls-decor-shadow) mitskalieren und
   je Stueck weggelassen werden kann — dieselbe Bauform wie Piece/PieceShadow
   in shared/ShelfDecorLayer.js. */
.ls-decor-piece-wrap {
    position: absolute;
    width: 13.636%;
    transform: translate(-50%, -100%) scale(var(--ls-decor-scale, 1));
    transform-origin: bottom center;
    pointer-events: none;
}
.ls-decor-shadow {
    position: absolute;
    left: 11%;
    right: 11%;
    bottom: -7%;
    height: 16%;
    border-radius: 50%;
    background: radial-gradient(ellipse at center, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.5) 42%, rgba(0,0,0,0) 70%);
    opacity: 0.38;
}
.ls-decor-piece-wrap .ls-decor-piece {
    position: relative;
    /* BLOCK, wie PieceImage in der App. Als inline stand das Bild auf einer
       Zeilenbox mit 4px Unterlaenge: der Kasten wurde 76 statt 72 hoch, sein
       Boden (der Anker, an dem der Schatten haengt) lag unter dem Bild, und
       das Stueck schwebte je nach Skalierung 8 bis 12px ueber seinem Schatten
       — gemessen am 2026-09-12, der Unterschied zur App. */
    display: block;
    width: 100%;
    /* Groesse traegt der Kasten; hier die Drehung um die Bildmitte — und
       davor 5.556 % (4 von 72 px) nach unten in den Schatten hinein, wie
       PieceImage in shared/ShelfDecorLayer.js. Bis zum 2026-09-20 waren es
       13.889 % (10 von 72); die Ellipse sass damit zu dicht am Stueck.
       Die Zahl gehoert zu PIECE_SHADOW_LIFT_PCT dort — wer eine aendert,
       aendert beide, sonst steht dieselbe Deko hier anders als in der App. */
    transform: translateY(5.556%) rotate(var(--ls-decor-rotation, 0deg));
    transform-origin: center center;
}

.ls-decor-piece {
    position: absolute;
    width: 13.636%;
    height: auto;
    aspect-ratio: 1;
    object-fit: contain;
    /* Groesse vom Fuss nach oben, Drehung um die BILDMITTE — wie in der App
       (shared/ShelfDecorLayer.js dreht das Bild im Rahmen). Hier gibt es
       nur das eine Element, deshalb: zur Mitte hoch, drehen, zurueck. */
    transform: translate(-50%, -100%) scale(var(--ls-decor-scale, 1))
               translateY(-50%) rotate(var(--ls-decor-rotation, 0deg)) translateY(50%);
    transform-origin: bottom center;
    /* Kein drop-shadow mehr am Stueck selbst (bis 2026-09-20: 0 6px 8px fest) —
       siehe den Kommentar an Piece in shared/ShelfDecorLayer.js. Ein Stueck
       trug damit zwei Schatten, und der zweite klebte an seiner Silhouette.
       Als WAHL gibt es ihn weiter, siehe --wallshadow unten. */
    pointer-events: none;
    user-select: none;
}

/* Der Rahmen um das Bild — er traegt den Wand-Schatten und dreht sich NICHT
   mit (siehe PieceImageWrap in shared/ShelfDecorLayer.js, dort steht auch,
   warum es dieses Element ueberhaupt gibt). */
.ls-decor-figure {
    position: relative;
    z-index: 1;
    display: block;
    width: 100%;
}

/* Der Wand-Schatten, wenn der Spieler ihn fuer dieses Stueck angeschaltet hat
   (PHP ShelfDecorHelper::FIELD_WALLSHADOW). Dieselben Werte wie PieceImageWrap
   in shared/ShelfDecorLayer.js — wer eine Seite aendert, aendert beide. */
.ls-decor-figure--wallshadow {
    filter: drop-shadow(0 5px 5px rgba(0, 0, 0, 0.5));
}

/* Erst mit JavaScript wird aus der Liste ein Karussell. */
.ls-carousel-ready .ls-shelf { display: none; margin-top: 0; }
.ls-carousel-ready .ls-shelf-current { display: block; }

/* Randgriffe wie in der App (ShelfView.js's ShelfNav): halbe Hoehe, an der
   Aussenkante angewachsen, nur zur Aussenseite hin abgerundet. */
.ls-shelf-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 6;
    width: 20px;
    height: 62px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0 solid rgba(70, 50, 15, 0.35);
    background: rgba(255, 248, 236, 1);
    color: #4A3411;
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    opacity: 0.85;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.6);
}

.ls-shelf-arrow-left  { left: 0;  border-radius: 0 14px 14px 0;  }
.ls-shelf-arrow-right { right: 0; border-radius: 14px 0 0 14px; }

.ls-shelf-arrow:hover,
.ls-shelf-arrow:active { opacity: 1; }

.ls-shelf-dots {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 10px 0 4px;
}

.ls-shelf-dot {
    width: 8px;
    height: 8px;
    padding: 0;
    border: none;
    border-radius: 4px;
    background: rgba(70, 50, 15, 0.28);
    cursor: pointer;
    transition: width 0.18s ease, background 0.18s ease;
}

.ls-shelf-dot.ls-active {
    width: 20px;
    background: #8A6A2F;
}

/* ── Feste Bauform: vier Bretter je Regal (seit 2026-09-10) ──────────────────
   Jedes Brett zeigt vier Plaetze, auch leere — alle Bretter gleich, ohne
   Hinweis darauf, was der Besitzer noch nicht freigeschaltet hat (das zeigt
   nur das Spiel, ShelfView.js). Ein leerer Platz haelt Spalte und Hoehe. */

.ls-board {
    position: relative;
}

.ls-slot {
    position: relative;
    width: 100%;
    min-width: 0;
    /* Dieselbe Hoehe wie eine Karte (Bild plus Schatten), damit ein Brett mit
       Luecken nicht niedriger wird als ein volles. */
    aspect-ratio: 80 / 82;
    max-width: 120px;
    margin: 0 auto;
}

/* Das Namensschild eines wohnlichen Regals (PublicShelfHelper: 'cozy') —
   derselbe warme Ton wie in der App (CozyShelfView.js' SIGN_TINT), damit
   man auch auf der Seite sieht, dass dieses Regal gestaltet und nicht
   gewertet ist. */
.ls-sign-cozy {
    filter: sepia(0.35) hue-rotate(-18deg) saturate(1.15) brightness(1.06);
}

/* ── Zurueck zur App (?from=app, siehe shelf.js) ────────────────────────────
   Derselbe Streifen wie in der App (shared/DetailShell.js' DetailCloseDock):
   100px hoch, fest am unteren Rand, weicher Verlauf zur Flaeche, in der
   Mitte der rote Knopf mit warmem Hof — die ganze Hofflaeche ist Tippflaeche. */
.ls-app-dock {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: 100px;
    z-index: 30;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    background: linear-gradient(to top, #FFFFFF 40%, rgba(255, 255, 255, 0));
}
.ls-app-dock[hidden] { display: none; }
/* Luft unter der Seite, damit der Fuss nicht unter dem Streifen liegt. */
body.ls-from-app .ls-frame { padding-bottom: 150px; }

.ls-app-dock-btn {
    pointer-events: auto;
    position: relative;
    isolation: isolate;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 3px solid #FFFFFF;
    background: #E5484D;
    color: #fff;
    font-size: 1.3rem;
    font-weight: 800;
    line-height: 1;
    text-decoration: none;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.28), 0 4px 14px rgba(0, 0, 0, 0.3);
    transition: transform 0.15s;
    -webkit-tap-highlight-color: transparent;
}
.ls-app-dock-btn::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 130px;
    height: 130px;
    margin: -65px 0 0 -65px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(158,122,79,0) 0%, rgba(158,122,79,0) 16%, rgba(158,122,79,0.35) 30%, rgba(158,122,79,0.15) 50%, rgba(158,122,79,0) 70%);
    filter: blur(2px);
    z-index: -1;
}
.ls-app-dock-btn:active { transform: scale(0.95); }

/* ── Promo-Seiten (/promo/<slug>.html, seit 2026-09-27) ──────────────────
   Der Werbe-Rahmen um ein Promo-Regal: Claim, Sammlungsraster, "Play now".
   Das Regal selbst ist dasselbe Markup wie auf der Spielerseite
   (Templates/Public/_shelf_section.phtml), siehe promo_shelf.phtml. */

.ls-promo-claim {
    margin: 0;
    padding: 12px 16px;
    font-size: 0.95rem;
    font-weight: 600;
    color: #6B4F3A;
    text-align: center;
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

.ls-promo-collection {
    padding: 20px 16px 8px;
    border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.ls-promo-collection-title {
    margin: 0;
    font-size: 1.05rem;
    color: #1C110A;
    text-align: center;
}

.ls-promo-collection-sub {
    margin: 4px 0 14px;
    font-size: 0.72rem;
    color: #B09080;
    text-align: center;
}

.ls-promo-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 10px;
}

.ls-promo-piece {
    margin: 0;
    padding: 6px 4px 8px;
    background: #FAF6F1;
    border-radius: 10px;
    border-bottom: 3px solid var(--ls-promo-rc, #B09080);
    text-align: center;
    min-width: 0;
}

.ls-promo-piece img {
    width: 100%;
    aspect-ratio: 1;
    object-fit: contain;
    display: block;
}

.ls-promo-piece figcaption {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-top: 4px;
}

.ls-promo-piece-name {
    font-size: 0.66rem;
    line-height: 1.2;
    color: #1C110A;
    overflow-wrap: anywhere;
}

.ls-promo-piece-rarity {
    font-size: 0.6rem;
    font-weight: 700;
    color: var(--ls-promo-rc, #B09080);
}

.ls-promo-cta-row {
    padding: 18px 16px 22px;
    text-align: center;
}

.ls-promo-cta {
    display: inline-block;
    padding: 12px 34px;
    border-radius: 999px;
    background: #9E7A4F;
    color: #FFFFFF;
    font-weight: 700;
    font-size: 1rem;
    text-decoration: none;
    box-shadow: 0 3px 0 #6B4F3A;
    transition: transform 0.1s ease, box-shadow 0.1s ease;
}

.ls-promo-cta:hover { transform: translateY(-1px); box-shadow: 0 4px 0 #6B4F3A; }
.ls-promo-cta:active { transform: translateY(2px); box-shadow: 0 1px 0 #6B4F3A; }

.ls-promo-cta--small {
    padding: 6px 16px;
    font-size: 0.8rem;
    box-shadow: 0 2px 0 #6B4F3A;
}

@media (max-width: 380px) {
    .ls-promo-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
