        /* ---- Skill Planner page: reuses the class-hero-banner/class-window-card mosaic above
           (same CLASS_TREE/FINAL_CLASSES/crop tables/classAssetPath) for its own class picker,
           then a sidebar (current tier's class icon, points counters, Reset) + tier-tabbed
           skill list below. */
        .skill-planner-layout {
            display: flex;
            gap: 20px;
            align-items: flex-start;
        }

        /* Reuses .panel's own background+blur combo (--skin-content-bg/--skin-content-blur),
           NOT --skin-card-bg -- stacking a second identical pane of the panel's own tinted glass
           on top of itself is what makes nesting read as "one level deeper" (darker/more frosted
           on a dark skin, more opaque on a light one, whichever direction that skin's glass
           already goes), matching how real stacked glass compounds. --skin-card-bg mixes toward
           --skin-content-text instead, which on a dark skin is a light color -- using it for a
           CONTAINER (not an individual chip/tile sitting inside one) made nesting look like it
           got LIGHTER the deeper you went, backwards from what stacking more glass should do. */
        .skill-planner-sidebar {
            flex: 0 0 170px;
            background: var(--skin-content-bg);
            backdrop-filter: blur(var(--skin-content-blur)) saturate(150%);
            -webkit-backdrop-filter: blur(var(--skin-content-blur)) saturate(150%);
            border: 1px solid var(--skin-content-border);
            border-radius: 12px;
            padding: 16px 12px;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 4px;
            text-align: center;
        }

        .skill-sidebar-icon {
            width: 60px;
            height: 60px;
            background: center/contain no-repeat;
            margin-bottom: 4px;
        }

        .skill-sidebar-classname { font-weight: 800; color: var(--skin-content-text); font-size: 0.92em; }
        .skill-sidebar-count { font-size: 1.3em; font-weight: 800; color: var(--skin-accent-text); margin-top: 2px; }
        .skill-sidebar-label {
            font-size: 0.64em;
            font-weight: 800;
            color: var(--skin-text-muted);
            text-transform: uppercase;
            letter-spacing: 0.05em;
            margin-top: 12px;
        }
        .skill-sidebar-points { font-size: 1.6em; font-weight: 800; color: #3ecf8e; }

        /* .skill-reset-btn is a <button> -- explicitly NOT given .tab-btn, so it doesn't pick up
           its opt-in flex:1 (see the button{} gotcha note further up); it just wants to be
           full-width inside its own flex-column sidebar, set directly here. */
        .skill-reset-btn { width: 100%; margin-top: 14px; }

        /* One more glass level than the bare page background, matching .skill-planner-sidebar's
           own treatment right next to it -- previously the tabs+grid group had no surface of its
           own at all and sat directly on the page background, reading as ungrouped. Same
           --skin-content-bg/--skin-content-blur reuse as the sidebar -- see its comment above for
           why (stacking the panel's own glass again, not --skin-card-bg). */
        .skill-planner-main {
            flex: 1;
            min-width: 0;
            background: var(--skin-content-bg);
            backdrop-filter: blur(var(--skin-content-blur)) saturate(150%);
            -webkit-backdrop-filter: blur(var(--skin-content-blur)) saturate(150%);
            border: 1px solid var(--skin-content-border);
            border-radius: 14px;
            padding: 14px;
        }

        .skill-tier-tab .skill-tier-count { font-size: 0.8em; opacity: 0.8; margin-left: 4px; }

        /* Same span+background-image convention as .cls-window-icon/.skill-sidebar-icon --
           these class icons are already self-contained transparent glyphs, so no border/backing
           (see "No borders on self-sufficient icons" in CLAUDE.md). Shows each tab's OWN tier
           class (Swordsman on "1st Job", Knight on "2nd Job", Lord Knight on "Trans. 2nd", etc.)
           so the tab row itself reads as the character's evolution, not just three text labels. */
        .skill-tier-tab-icon {
            display: inline-block;
            width: 18px;
            height: 18px;
            background: center/contain no-repeat;
            vertical-align: -4px;
            margin-right: 5px;
        }

        /* Grid of skill tiles, mirroring the game's own Skills screen layout (icon with a
           corner level badge, name below) instead of a flat list -- see CLAUDE.md "Skill
           Planner" for the reference screenshot this was matched against. */
        /* Positioned by real tree coordinates (ROOCDB's own panelX/panelY per skill), not a
           flowing auto-fill grid -- each .skill-tile gets an inline grid-column/grid-row so the
           branching layout matches the game's real skill tree instead of an arbitrary reading
           order. .skill-tree-lines (an SVG sized to the grid's full scrollable content, not just
           its visible viewport) sits behind the tiles and draws a connector between every
           same-tier prerequisite pair -- see renderSkillTreeLines(). */
        .skill-grid {
            position: relative;
            display: grid;
            /* Fallback only -- both render call sites (renderSkillPlannerPanel/
               renderGuideSkillPanel) set an explicit inline grid-template-columns sized to the
               tier's own real column count (see gridColumnsStyle() in shared/skillTreeRender.js),
               each column a flexible 1fr so they stretch to fill whatever width is actually
               available instead of a fixed pixel width per column -- a fixed width either
               overflowed a narrow panel (too many columns for the space) or left a sparse tree
               clumped in the middle with wasted space on the sides (too few), independent of the
               container's real width. This 118px only applies if something ever renders the grid
               without setting that inline style. */
            grid-auto-columns: 118px;
            /* auto, not a fixed px row height -- a fixed height that's even slightly shorter
               than a tile's real content (icon + name + controls) lets the tile overflow its own
               row track, where it gets visually covered by the next row's tiles (same z-index,
               later in DOM order) -- this is what made the whole first row's +/- controls
               unreachable, not just visually off. Connector-line math reads real
               getBoundingClientRect() values after render (see renderSkillTreeLines), so it
               doesn't care what actually sizes the rows. */
            grid-auto-rows: auto;
            column-gap: 4px;
            row-gap: 20px;
            justify-content: center;
            max-height: 560px;
            overflow: auto;
            padding: 14px 8px;
        }

        .skill-tree-lines {
            position: absolute;
            top: 0;
            left: 0;
            pointer-events: none;
            z-index: 0;
        }
        .skill-tree-line {
            fill: none;
            stroke: var(--skin-text-faint);
            stroke-width: 3;
        }
        .skill-tree-line.learned { stroke: var(--skin-accent-text); stroke-width: 4; }

        /* No card/square background here on purpose -- matches the real game's own skill-tree
           presentation (badge + icon + name pill, "glued" together, no separate box framing the
           group) instead of the earlier boxed-tile look. The badge and the name below both carry
           their own small rounded-pill background instead, same idea as the reference screenshot
           (a dark level pill above the icon, a lighter name pill below it, nothing wrapping the
           two together). */
        /* width:100% (of its own grid cell) with a max-width cap, not a fixed px width -- .skill-
           grid's columns are now sized to fill whatever width is actually available (see below),
           so the tile itself needs to follow that width rather than fight it with its own fixed
           size, which would either overflow a narrower column or leave a wider one under-filled. */
        .skill-tile {
            position: relative;
            z-index: 1;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            gap: 2px;
            margin: auto;
            width: 100%;
            max-width: 118px;
            padding: 10px 6px 6px;
            transition: opacity 0.15s, transform 0.15s;
            cursor: pointer;
        }

        /* Icon flanked by its own -/+ buttons, matching the real game's layout -- badge stays
           centered above the whole row (roughly above the icon, since the two buttons are close
           to symmetric on either side of it). */
        .skill-tile-body { display: flex; align-items: center; gap: 4px; }

        .skill-tile-badge {
            position: absolute;
            top: -6px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 2;
            background: var(--skin-accent-gradient);
            color: var(--skin-active-text);
            font-weight: 800;
            font-size: 0.62em;
            padding: 1px 5px;
            border-radius: 6px;
            white-space: nowrap;
            /* No fade-color outline -- same reasoning as .qty-badge above: this sits on its own
               solid accent pill, not arbitrary art, so it never needed the "unknown backdrop"
               scrim, and that scrim is what caused Doran's dark activeText to render black-on-black. */
        }

        /* No white backing -- per explicit correction, the earlier "white background to match the
           game reference" call (see CLAUDE.md "Skill Planner") reads wrong on a dark skin, where
           a white square glares against the surrounding glass instead of blending into it. The
           icon art itself is a small glyph on a mostly-transparent canvas, so with no backing it
           just floats directly on the tile (which itself already has no background either -- see
           .skill-tile above), matching the "só o icone, nome e nível, como é no jogo" ask. Applies
           to both the real Skill Planner AND the read-only Build Guide tree (they share this same
           class), since the same white-square issue was flagged on both. */
        .skill-tile-icon-wrap {
            position: relative;
            width: 52px;
            height: 52px;
            border-radius: 10px;
            overflow: hidden;
            flex-shrink: 0;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .skill-tile-icon-wrap img.skill-tile-icon {
            width: 100%;
            height: 100%;
            object-fit: contain;
            padding: 4px;
            box-sizing: border-box;
        }
        /* Unlearned (0 points) skills render desaturated, same as the real game -- not faded via
           opacity, which would let the connector-line SVG underneath show through the icon and
           look muddy. A locked tier's skills are always 0-level (nothing can be spent there yet),
           so this one rule covers both cases without a separate tier-locked dimming style. */
        .skill-tile:not(.has-points) .skill-tile-icon-wrap img.skill-tile-icon { filter: grayscale(1); }
        /* Same emoji-fallback mechanism as .card-item.error::before (see CLAUDE.md "Nightmare
           Temple Bosses") -- if the local skill icon 404s, the img is hidden and this
           pseudo-element shows a generic emoji instead (picked per-skill by element/passive). */
        .skill-tile-icon-wrap.error::before {
            content: attr(data-emoji);
            font-size: 1.6em;
        }

        .skill-tile-name {
            font-weight: 700;
            font-size: 0.68em;
            color: var(--skin-content-text);
            text-align: center;
            line-height: 1.2;
            min-height: 1.2em;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
            overflow: hidden;
            background: var(--skin-card-bg);
            border-radius: 10px;
            padding: 3px 8px;
            transition: background 0.15s;
        }
        .skill-tile:hover .skill-tile-name { background: var(--skin-card-bg-hover); }
        /* "Has points" used to darken/border the whole outer card -- now that there's no card,
           the accent tint moves onto the name pill (the badge is already always accent-colored,
           see .skill-tile-badge, so it doesn't need its own has-points state). */
        .skill-tile.has-points .skill-tile-name { background: var(--skin-card-bg-active); color: var(--skin-accent-text); }

        /* Same "-"/"+" drawn-as-bars treatment as .qty-btn (see its own comment) -- flex-centering
           alone (the previous fix here) still leaves the actual glyph's ink off-center in some
           fonts, since flex only centers the LINE BOX, not the character's own visual weight.
           Drawing the icon as real CSS bars sidesteps font metrics entirely. */
        .skill-adj-btn {
            width: 22px;
            height: 22px;
            border: none;
            border-radius: 6px;
            background: var(--skin-accent-text);
            color: var(--skin-active-text);
            font-size: 0;
            cursor: pointer;
            transition: opacity 0.15s, transform 0.15s;
            flex-shrink: 0;
            position: relative;
        }
        .skill-adj-btn::before {
            content: "";
            position: absolute;
            top: 50%;
            left: 50%;
            width: 55%;
            height: 2px;
            background: currentColor;
            border-radius: 1px;
            transform: translate(-50%, -50%);
        }
        .skill-adj-btn[data-delta="1"]::after {
            content: "";
            position: absolute;
            top: 50%;
            left: 50%;
            width: 2px;
            height: 55%;
            background: currentColor;
            border-radius: 1px;
            transform: translate(-50%, -50%);
        }
        .skill-adj-btn:hover:not(:disabled) { transform: scale(1.08); }
        .skill-adj-btn:disabled { background: var(--skin-card-border); cursor: not-allowed; opacity: 0.5; }

