body {
    background: whitesmoke !important;
}

/* Badge de score ponderado por dificuldade (question/progress.html,
   class_subject/list.html) — mesma cor roxa usada no quest-trail/badges de
   XP; era style="background-color:#6f42c1" duplicado nos dois lugares (um
   deles ainda com uma classe bg-purple que não existe no Bootstrap). */
.badge-weighted-score {
    background-color: #6f42c1;
}

/* Contém imagens inseridas em respostas discursivas (question/upload_essay_image)
   ao exibir em corrections.html/progress.html/summary.html — sem isso, uma imagem
   grande estoura a tela com barra de rolagem. Uploads novos já vêm redimensionados
   no servidor, mas isso também protege imagens enviadas antes dessa mudança. */
.essay-answer-content img {
    max-width: 100%;
    height: auto;
}

textarea {
    height: 100px !important;
}

.form-signin {
    width: 100%;
    max-width: 430px;
    padding: 15px;
    margin: auto;
    background-color: white;
}

.form-signin .checkbox {
    font-weight: 400;
}

.form-signin .form-floating:focus-within {
    z-index: 2;
}

.form-signin input {
    margin-bottom: 5px;
    border-color: darkgray;
}

.form {
    width: 100%;
    max-width: 800px;
    padding: 15px;
    margin: auto;
    background-color: white;
}

.form-m {
    width: 100%;
    max-width: 600px;
    padding: 15px;
    margin: auto;
    background-color: white;
}

.img-404 {
    width: 80%;
}

.lead {
    font-size: 1.25rem;
    font-weight: 300;
}

.text-gray-500 {
    color: #b7b9cc !important;
}

.text-gray-800 {
    color: #5a5c69 !important;
}

.breadcrumb {
    display: flex;
    flex-wrap: wrap;
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
    list-style: none;
    border-radius: 0.35rem;
}

.breadcrumb-item + .breadcrumb-item {
    padding-left: 0.5rem;
}

.breadcrumb-item + .breadcrumb-item::before {
    float: left;
    padding-right: 0.5rem;
    color: #858796;
}

.breadcrumb-item + .breadcrumb-item:hover::before {
    text-decoration: underline;
}

.breadcrumb-item + .breadcrumb-item:hover::before {
    text-decoration: none;
}

.breadcrumb-item.active {
    color: #858796;
}

.pagination-page-info {
    text-align: center;
    padding: .6em .6em .6em 0;
    margin: .5em .5em .5em 0;
    font-size: 12px;
}
.pagination-page-info b {
    text-align: center;
    color: black;
    padding: .1em .25em;
}

.form-label {
    margin-left: 5px;
}

/* Cartão de alternativa clicável (answer_question.html, revisit_question.html)
   — o rádio fica dentro do próprio card, então marcar a opção é o mesmo gesto
   de "escolher a letra no papel", em vez de ler o card e depois procurar um
   rádio separado embaixo. */
.answer-option {
    display: block;
    cursor: pointer;
    transition: border-color .15s ease-in-out, background-color .15s ease-in-out;
}

.answer-option:hover {
    border-color: #86b7fe;
}

.answer-option:has(input:checked) {
    border-color: #0d6efd;
    background-color: #eaf2ff;
}

.answer-option .form-check-input {
    cursor: pointer;
}

/* Matching interativo (answer_question.html, revisit_question.html) — ver
   matching_connect.js. Ligar os pontos das duas colunas com uma linha, em vez
   de escolher a letra num select separado. */
.matching-connect {
    position: relative;
}

.matching-item {
    transition: border-color .15s ease-in-out;
}

.matching-lines {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 1;
}

.matching-lines .matching-line {
    stroke: #0d6efd;
    stroke-width: 2.5;
    pointer-events: stroke;
    cursor: pointer;
}

.matching-lines .matching-line:hover {
    stroke: #dc3545;
}

.matching-point {
    width: 22px;
    height: 22px;
    min-width: 22px;
    flex-shrink: 0;
    border-radius: 50%;
    border: 2px solid #adb5bd;
    background-color: #fff;
    padding: 0;
    cursor: pointer;
    position: relative;
    z-index: 2;
}

.matching-point:hover {
    border-color: #0d6efd;
}

.matching-point.pending {
    border-color: #fd7e14;
    background-color: #fff3e0;
    box-shadow: 0 0 0 3px rgba(253, 126, 20, .25);
}

.matching-point.connected {
    border-color: #0d6efd;
    background-color: #0d6efd;
}

/* Breadcrumb "mapa de quest" (ver tutor/templates/_quest_trail.html) — cada
   etapa é um nó circular ligado por uma linha tracejada, com a etapa ativa
   (página atual) destacada na mesma cor roxa dos badges de score/XP já
   usados em progress.html e index/_student.html, com uma pulsação sutil pra
   reforçar "você está aqui". Era só das 5 telas do aluno originalmente (o
   .breadcrumb padrão do Bootstrap ficava pro resto) — unificado pro sistema
   todo (2026-07-16): as telas de ranking, compartilhadas entre aluno/
   professor/admin, destoavam visualmente do resto da experiência do aluno
   (breadcrumb plano ali, quest trail em toda outra tela dele) — resolvido
   estendendo o quest trail pra TODAS as telas com título+breadcrumb do
   sistema, não só as de ranking, em vez de manter dois estilos coexistindo. */
.quest-trail {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    flex-wrap: wrap;
    list-style: none;
    padding: 0;
    margin: 0 0 1rem;
}

.quest-step {
    display: flex;
    /* flex-start (não center): com align-items:center, a posição vertical do
       nó dependia da altura do LABEL mais alto entre as etapas (rótulos de
       2 linhas empurravam etapas de 1 linha vizinhas pra baixo pra
       centralizar) — o que fazia o círculo do ícone (e a linha tracejada
       abaixo, calibrada por margin-top fixo) cair em alturas diferentes
       conforme os rótulos da página. Com flex-start, o ícone sempre começa
       no topo do step (34px de altura, centro em 17px), então o
       margin-top:17px da linha em .quest-step::before sempre acerta o
       centro do ícone, em qualquer página. */
    align-items: flex-start;
}

.quest-step:not(:first-child)::before {
    content: '';
    width: 28px;
    height: 0;
    border-top: 2px dashed #d5c5f0;
    margin: 17px 4px 0;
    flex-shrink: 0;
}

.quest-node {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    width: 92px;
    text-decoration: none;
    color: #6c757d;
}

a.quest-node:hover .quest-icon {
    border-color: #6f42c1;
    background-color: #f2ecfb;
    color: #6f42c1;
    transform: translateY(-2px);
}

a.quest-node:hover .quest-label {
    color: #6f42c1;
}

.quest-icon {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #fff;
    border: 2px solid #d8d8d8;
    color: #6c757d;
    font-size: .85rem;
    transition: border-color .15s ease-in-out, background-color .15s ease-in-out,
                color .15s ease-in-out, transform .15s ease-in-out;
}

.quest-label {
    font-size: .7rem;
    text-align: center;
    max-width: 92px;
    line-height: 1.2;
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

.quest-node-active {
    cursor: default;
}

.quest-node-active .quest-icon {
    background-color: #6f42c1;
    border-color: #6f42c1;
    color: #fff;
    animation: quest-pulse 2s infinite;
}

.quest-node-active .quest-label {
    color: #6f42c1;
    font-weight: 600;
}

@keyframes quest-pulse {
    0% { box-shadow: 0 0 0 0 rgba(111, 66, 193, .45); }
    70% { box-shadow: 0 0 0 8px rgba(111, 66, 193, 0); }
    100% { box-shadow: 0 0 0 0 rgba(111, 66, 193, 0); }
}

@media (prefers-reduced-motion: reduce) {
    .quest-node-active .quest-icon {
        animation: none;
    }
}

/* Mapa de progresso (Home do aluno e perfil, ambos via _get_world_map_for_student — renderizado em PixiJS
   (ver skill_map_pixi.js), com clique nos nós abrindo um painel interativo
   (showNodePanel). .skill-remedial-chip sobrevive de uma versão anterior
   em puro CSS/flex: a lista de reforços sugeridos continua em HTML puro,
   fora do canvas (ver template). */
.skill-map-canvas-wrap {
    overflow-x: auto;
    border-radius: .5rem;
    margin-bottom: 1.5rem;
    /* âncora pro botão de recentralizar câmera e pro painel de nó (showNodePanel). */
    position: relative;
    /* Container query pro HUD (ver .skill-map-hud abaixo) — achado
       2026-07-15: o canvas do PixiJS encolhe sozinho em telas estreitas
       (skill_map_pixi.js mede o container e/ou o CSS max-width:100% já
       cuidava disso antes), mas o HUD é HTML/CSS à parte, sobreposto —
       sem isso ele ficava sempre do mesmo tamanho, a ponto de ocupar o
       mapa inteiro num celular. O wrap é o container "certo" pra medir,
       já que (depois do ajuste em skill_map_pixi.js) sua largura passa a
       bater com a largura de fato renderizada do canvas. */
    container-type: inline-size;
}

.skill-map-canvas-container {
    display: inline-block;
    min-width: 100%;
    min-height: 160px;
    border-radius: .5rem;
}

/* Home/perfil mostram vários mapas lado a lado (accordion com abas, um por
   disciplina matriculada) — min-width:100% (pensado pro mapa único) esticaria
   cada um pra ocupar a coluna toda, quebrando o layout lado a lado. */
.skill-map-canvas-container--compact {
    min-width: 0;
}

/* Câmera com arrasta/solta — só o canvas do
   caminho REAL do aluno (renderPrebuiltWorld) ganha essa classe via JS
   (attachDragPan); renderTileWorld/renderPlaceholderWorld (debug-only,
   sistema procedural antigo, sem câmera) nunca a recebem, então continuam
   exibindo o canvas no tamanho nativo (overflow-x:auto do wrap acima
   ainda cuida deles) — decisão deliberada de não misturar os dois
   comportamentos na mesma regra genérica de `canvas`. max-width/height:auto
   encolhe proporcionalmente o viewport fixo (640×420, ver VIEWPORT_WIDTH/
   HEIGHT em skill_map_pixi.js) em tela estreita (celular) sem JS nenhum —
   a matemática de arrastar já compensa isso via getBoundingClientRect. */
.skill-map-canvas--draggable {
    max-width: 100%;
    height: auto;
    cursor: grab;
    border-radius: .5rem;
}

.skill-map-canvas--dragging {
    cursor: grabbing;
}

.skill-map-recenter-btn {
    position: absolute;
    top: .5rem;
    right: .5rem;
    z-index: 2;
    width: 2.25rem;
    height: 2.25rem;
    border: none;
    border-radius: 50%;
    background: rgba(10, 10, 18, .65);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

.skill-map-recenter-btn:hover {
    background: rgba(10, 10, 18, .85);
}

/* Painel interativo ao clicar num nó — HTML sobreposto ao canvas, mesma
   âncora do botão de recentralizar (.skill-map-canvas-wrap já é
   position:relative). Ancorado num canto fixo (topo-centro), não
   flutuando sobre o nó clicado — fecha assim que o usuário começa a
   arrastar o mapa, então não precisa acompanhar a câmera. Reusa a mesma
   moldura 9-slice + paleta "pedra escura + dourado" já estabelecida pelo
   HUD (.skill-map-hud-body/skill_map_hud_frame.png) em vez da caixa
   roxa genérica da v1 (criada antes de existir qualquer identidade
   visual pro mapa) — o painel e o HUD agora leem como parte do mesmo
   sistema. A cor do texto de status é setada inline por showNodePanel
   com o mesmo hex de STATUS_COLORS que já tinge o marcador do nó no
   mapa, amarrando visualmente painel↔nó. */
.skill-map-node-panel {
    position: absolute;
    top: .5rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 2;
    width: 220px;
    max-width: calc(100% - 1rem);
    background: #232228;
    background-clip: padding-box;
    border-style: solid;
    border-width: 14px;
    border-image-source: url("../images/skill_map_hud_frame.png");
    border-image-slice: 22 fill;
    border-image-repeat: stretch;
    image-rendering: pixelated;
    padding: .6rem .7rem;
    color: #fff;
    font-size: .82rem;
}

.skill-map-node-panel-close {
    position: absolute;
    top: .1rem;
    right: .3rem;
    background: none;
    border: none;
    color: #e8c07d;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}

.skill-map-node-panel-close:hover {
    color: #f6e2a8;
}

.skill-map-node-panel-title {
    font-weight: 600;
    padding-right: 1.2rem;
    margin-bottom: .15rem;
}

/* "Novo!" no painel de nó — mesma paleta azul do botão "Estudar"
   (.skill-map-node-panel-study), reforçando a mesma associação visual do
   badge desenhado no marcador (NEW_SUBJECT_ICON_GLYPH). */
.skill-map-node-panel-new-badge {
    display: inline-block;
    background: linear-gradient(180deg, #bfe3ff, #5aa9e0);
    border: 1px solid #2f7cad;
    color: #0d2b3e;
    font-size: .68rem;
    font-weight: 700;
    padding: .1rem .4rem;
    border-radius: .75rem;
    margin-bottom: .3rem;
}

.skill-map-node-panel-status {
    font-weight: 600;
    margin-bottom: .3rem;
}

.skill-map-node-panel-stars {
    color: #ffd700;
    margin-bottom: .3rem;
}

.skill-map-node-panel-stars .far {
    color: #6c757d;
}

.skill-map-node-panel-accuracy {
    color: #e9ecef;
    margin-bottom: .3rem;
}

.skill-map-node-panel-progress {
    height: 6px;
    background: rgba(255, 255, 255, .15);
    margin-bottom: .2rem;
}

.skill-map-node-panel-progress-bar {
    height: 100%;
    background: linear-gradient(180deg, #f6e2a8, #d8ad57);
}

.skill-map-node-panel-progress-label {
    color: #c5cae9;
    font-size: .72rem;
    margin-bottom: .3rem;
}

.skill-map-node-panel-practice {
    width: 100%;
    background: linear-gradient(180deg, #f6e2a8, #d8ad57);
    border: 1px solid #97702c;
    color: #1a1310;
    font-weight: 600;
}

.skill-map-node-panel-practice:hover {
    background: linear-gradient(180deg, #ffe973, #b39700);
    color: #1a1310;
}

/* "Estudar" — mesma estrutura de .skill-map-node-panel-practice (Praticar/
   Continuar), só a paleta muda pra um azul claro, pra distinguir os três
   botões de ação do painel de nó à primeira vista (âmbar = Praticar/
   Continuar, azul = Estudar, vermelho/dourado = Chefão via .boss-battle-btn). */
.skill-map-node-panel-study {
    width: 100%;
    background: linear-gradient(180deg, #bfe3ff, #5aa9e0);
    border: 1px solid #2f7cad;
    color: #0d2b3e;
    font-weight: 600;
}

.skill-map-node-panel-study:hover {
    background: linear-gradient(180deg, #ddf1ff, #3d8fca);
    color: #0d2b3e;
}

/* Chefão no painel de nó — mesmo critério de cor do resto da feature
   (vermelho = ainda não vencido, dourado = vencido, ver
   .boss-battle-result-box--victory/--defeat). Botão reaproveita
   .boss-battle-btn (já definido pro card/tela de batalha/Meu Progresso) —
   só a largura precisa de ajuste aqui, pra ficar consistente com
   Praticar/Continuar acima (100% da largura do painel, os outros dois já
   são assim). */
.skill-map-node-panel-boss {
    font-size: .78rem;
    font-weight: 600;
    margin-bottom: .4rem;
}

.skill-map-node-panel-boss i {
    margin-right: .3rem;
}

.skill-map-node-panel-boss--active {
    color: #ff6b6b;
}

.skill-map-node-panel-boss--defeated {
    color: #ffd75e;
}

.skill-map-node-panel .boss-battle-btn {
    width: 100%;
    margin-top: .1rem;
}

/* Missões no painel de nó — mesmo
   critério de cor do badge do marcador (âmbar = aberta, verde = todas
   concluídas). Linha por missão ativa daquele nó (label + botão de ação,
   reaproveitando .skill-map-node-panel-practice pro botão). */
.skill-map-node-panel-quest {
    font-size: .78rem;
    font-weight: 600;
    margin-bottom: .4rem;
}

.skill-map-node-panel-quest i {
    margin-right: .3rem;
}

.skill-map-node-panel-quest--alert {
    color: #ffc107;
}

.skill-map-node-panel-quest--check {
    color: #28c76f;
}

.skill-map-node-panel-quest-row {
    font-size: .72rem;
    color: #e9ecef;
    margin-bottom: .2rem;
}

.skill-map-node-panel-quest-btn {
    width: 100%;
    margin-bottom: .5rem;
}

/* Cutscene de transição entre assuntos (subject_complete.html,
   skill_map_pixi.js: renderWalkTransition) — mesmas dimensões/moldura do
   mapa navegável (.skill-map-canvas-wrap), sem os elementos interativos
   (botão de recentralizar, painel de nó) que não existem aqui: é um
   passeio passivo, a câmera anda sozinha. */
.skill-map-walk-canvas-wrap {
    border-radius: .5rem;
    overflow: hidden;
    margin-bottom: 0;
    border: 2px solid #4a3f2e;
}

.skill-map-walk-canvas-container {
    display: block;
    min-height: 160px;
}

/* Encolhe o mapa/cutscene em ~60% na tela de conclusão de assunto pra abrir
   espaço pro card "Enfrentar Chefão" ao lado (subject_complete.html) — só
   define a largura disponível; quem de fato redimensiona o canvas PixiJS é
   measureViewportWidth (skill_map_pixi.js), lendo o clientWidth deste wrap. */
.boss-battle-map-shrink {
    flex: 0 1 40%;
}

/* Card do desafio opcional de fim de assunto. Paleta vermelho/laranja
   deliberadamente fora do roxo usado no resto do app — sinaliza
   "desafio/perigo", não é mais um elemento de navegação comum. */
.boss-battle-card {
    flex: 0 1 320px;
    border: 2px solid #7a1f1f;
    border-radius: .75rem;
    background: linear-gradient(180deg, #1a0d0d, #2b1010);
    box-shadow: 0 0 18px rgba(220, 53, 69, .35);
}

/* Abaixo do breakpoint md do Bootstrap, o flex-wrap do container pai já
   empilha mapa e card, mas flex-basis 40%/320px (pensados pro layout lado a
   lado) deixavam os dois pequenos e desalinhados numa coluna estreita —
   mapa em cima ocupando a largura toda, card embaixo, ambos width:100%. */
@media (max-width: 767.98px) {
    .boss-battle-map-shrink,
    .boss-battle-card {
        flex: 1 1 100%;
        width: 100%;
    }
}

.boss-battle-gif {
    width: 140px;
    height: 140px;
    object-fit: contain;
    image-rendering: pixelated;
}

.boss-battle-card h5,
.boss-battle-card p {
    color: #f1e4e4;
}

.boss-battle-btn {
    background: linear-gradient(180deg, #ff8a5c, #dc3545);
    border: none;
    color: #fff;
    font-weight: 600;
    animation: boss-battle-pulse 1.6s ease-in-out infinite;
}

.boss-battle-btn:hover {
    background: linear-gradient(180deg, #ff9d75, #e14b5a);
    color: #fff;
}

@keyframes boss-battle-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(220, 53, 69, .55); }
    50% { box-shadow: 0 0 0 8px rgba(220, 53, 69, 0); }
}

/* Chefão final da disciplina — mesma estrutura de .boss-battle-card/
   .boss-battle-btn, paleta dourada em vez de vermelha, pra se distinguir
   à primeira vista do chefão de assunto (subject_complete.html mostra os
   dois lado a lado quando aplicável). */
.boss-battle-card--final {
    border-color: #b8860b;
    background: linear-gradient(180deg, #1a160d, #2b2210);
    box-shadow: 0 0 18px rgba(255, 193, 7, .4);
}

.boss-battle-final-crown {
    font-size: 3rem;
    color: #ffd75e;
}

.boss-battle-btn--final {
    background: linear-gradient(180deg, #ffe082, #b8860b);
    animation: boss-battle-pulse-final 1.6s ease-in-out infinite;
}

.boss-battle-btn--final:hover {
    background: linear-gradient(180deg, #fff0b8, #cf9a12);
    color: #1a1310;
}

@keyframes boss-battle-pulse-final {
    0%, 100% { box-shadow: 0 0 0 0 rgba(255, 193, 7, .55); }
    50% { box-shadow: 0 0 0 8px rgba(255, 193, 7, 0); }
}

/* Tela de batalha (question/boss_battle.html) — personagem/chefão lado a
   lado, cada um com barra de vida acima do sprite. Barras em CSS puro
   (width% + transition), sem pedir mais arte pixel-art pra isso ainda. */
.boss-battle-arena {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 2rem;
    flex-wrap: wrap;
}

.boss-battle-fighter {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 220px;
}

.boss-battle-hp-bar {
    width: 100%;
    height: 18px;
    background: #2b1010;
    border: 2px solid #7a1f1f;
    border-radius: .25rem;
    overflow: hidden;
    margin-bottom: .5rem;
}

.boss-battle-hp-fill {
    height: 100%;
    background: linear-gradient(180deg, #7ee787, #2ea043);
    transition: width .4s ease-out;
}

.boss-battle-hp-fill--boss {
    background: linear-gradient(180deg, #ff8a5c, #dc3545);
}

.boss-battle-sprite {
    width: 160px;
    height: 160px;
    object-fit: contain;
    image-rendering: pixelated;
}

.boss-battle-fighter-name {
    margin-top: .25rem;
    font-weight: 600;
    color: var(--bs-body-color);
}

.boss-battle-vs {
    font-size: 1.75rem;
    font-weight: 800;
    color: #dc3545;
    opacity: .8;
}

.boss-battle-feedback {
    font-size: 1.25rem;
}

.boss-battle-result-overlay {
    position: fixed;
    inset: 0;
    background: rgba(10, 5, 5, .85);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1080;
}

.boss-battle-result-box {
    color: #f1e4e4;
    padding: 2.5rem 2rem;
    border-radius: 1rem;
    border: 2px solid #7a1f1f;
    background: linear-gradient(180deg, #1a0d0d, #2b1010);
    max-width: 90vw;
}

/* Cor de destaque por resultado — dourado/vitória vs. vermelho/derrota
   (o vermelho já é a paleta padrão do card/arena; vitória sai dela de
   propósito, pra não parecer "mais um estado de perigo"). */
.boss-battle-result-box--victory {
    border-color: #c99b2e;
    background: linear-gradient(180deg, #1a1608, #2b230f);
}

.boss-battle-result-box--defeat {
    border-color: #7a1f1f;
}

.boss-battle-result-icon {
    font-size: 3.5rem;
}

.boss-battle-result-box--victory .boss-battle-result-icon {
    color: #ffd75e;
}

.boss-battle-result-box--defeat .boss-battle-result-icon {
    color: #dc3545;
}

.boss-battle-result-stats {
    color: #b8a8a8;
    font-size: .95rem;
}

/* HUD fixo no canto superior esquerdo do mapa — mesma âncora/padrão do botão de recentralizar (topo-direita)
   e do painel de nó (topo-centro): HTML sobreposto ao canvas
   (.skill-map-canvas-wrap já é position:relative), nunca se move quando o
   mundo é arrastado, ao contrário de qualquer coisa desenhada no Tiled.
   Moldura + faixa recortadas de verdade de exemplo_ui.png (pacote free, uso
   liberado pelo usuário — ver tools/slice_hud_reference.py) em vez de
   aproximação só em CSS: skill_map_hud_frame.png é um 9-slice sintético
   92×92 (canto/borda de 22px) montado só com pedaços da parte de BAIXO da
   referência (cantos/bordas inferiores, espelhados verticalmente pro topo)
   — a parte de cima da imagem original tem a faixa sobreposta, então os
   cantos superiores dela não servem pra um frame limpo; skill_map_hud_banner.png
   é a faixa recortada com o texto "TITLE" apagado (substituído por uma
   amostra da própria cor de fundo, esticada) — o nome do aluno é escrito
   por cima em HTML de verdade, não faz parte da imagem. `image-rendering:
   pixelated` em ambos evita borrão ao escalar. Corpo ainda vazio de
   propósito, pronto pra receber avatar/XP/ranking/conquistas numa próxima passada. */
.skill-map-hud {
    position: absolute;
    top: .5rem;
    left: .5rem;
    z-index: 2;
    width: 220px;
    /* Encolhe como uma unidade só (moldura + faixa + tudo dentro do corpo)
       via transform:scale, disparado por container query no wrap acima —
       mais simples/robusto que converter cada medida interna do HUD
       (avatar, barra de XP, fontes) pra unidade relativa; transform-origin
       na mesma quina do position:absolute (top/left) mantém o HUD ancorado
       no canto conforme encolhe, em vez de encolher "pro centro". */
    transform-origin: top left;
    transform: scale(1);
}

@container (max-width: 520px) {
    .skill-map-hud { transform: scale(.78); }
}

@container (max-width: 400px) {
    .skill-map-hud { transform: scale(.6); }
}

@container (max-width: 300px) {
    .skill-map-hud { transform: scale(.46); }
}

.skill-map-hud-body {
    min-height: 140px;
    background: #232228;
    /* Achado 2026-07-15: background-clip por padrão é border-box — a cor
       de fundo pinta por baixo da área INTEIRA da borda, inclusive onde
       skill_map_hud_frame.png tem cantos transparentes (arredondados),
       então o que aparecia ali não era transparência de verdade, era essa
       cor vazando por baixo do recorte. padding-box recorta o fundo só
       até onde a borda começa, deixando a área da borda (incluindo os
       cantos transparentes) mostrar o que estiver de fato atrás do
       elemento. */
    background-clip: padding-box;
    border-style: solid;
    border-width: 14px;
    border-image-source: url("../images/skill_map_hud_frame.png");
    border-image-slice: 22 fill;
    border-image-repeat: stretch;
    image-rendering: pixelated;
    padding: 1rem .6rem .6rem;
}

.skill-map-hud-banner {
    margin: 0 auto -60px;
    width: calc(100% - .9rem);
    aspect-ratio: 380 / 105;
    background-image: url("../images/skill_map_hud_banner.png");
    background-size: 100% 100%;
    image-rendering: pixelated;
    clip-path: polygon(0 0, 100% 0, 100% 68%, 85% 100%, 50% 95%, 15% 100%, 0 68%);
    display: flex;
    justify-content: center;
    position: relative;
    z-index: 1;
    padding-top: 10px;
}

/* Minimizar/restaurar o HUD — paradas do mapa perto do canto superior
   esquerdo podem ficar embaixo do HUD sem nenhum jeito de revelá-las (a
   câmera não tem mais margem de "respiro" nas bordas do conteúdo, ver
   CAMERA_MARGIN = 0 em skill_map_pixi.js — antes disso existia margem,
   removida por deixar uma tira "flutuando" sem conteúdo atrás dela, ver
   comentário lá). Minimizado esconde faixa + corpo inteiros (a moldura de
   9-slice não se adapta bem a conteúdo parcial) e mostra só o botão de
   restaurar, mesmo canto/tamanho de sempre. Mesmo estilo circular dos
   botões de trocar personagem/estatísticas extras — igual eles, ancorado
   no canto de .skill-map-hud-avatar-wrap (não centralizado sobre o avatar
   como os outros dois, pra não cobrir rosto/capuz do personagem; achado
   2026-07-17: colocado antes na faixa, mas o clip-path dela deixou o botão
   difícil de posicionar/achar de forma confiável — o wrap do avatar é uma
   área simples, sem clip, e já é onde o usuário espera achar os outros
   controles do HUD). */
.skill-map-hud-minimize-btn {
    position: absolute;
    top: 0;
    right: 0;
    width: 1.4rem;
    height: 1.4rem;
    border-radius: 50%;
    border: 1px solid #97702c;
    background: #1a1712;
    color: #e8c07d;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: .65rem;
    padding: 0;
    cursor: pointer;
    z-index: 2;
}

.skill-map-hud-minimize-btn:hover {
    background: #2a2015;
    color: #f6e2a8;
}

.skill-map-hud-restore-btn {
    display: none;
    position: absolute;
    top: 0;
    left: 0;
    width: 1.8rem;
    height: 1.8rem;
    border-radius: 50%;
    border: 1px solid #97702c;
    background: #1a1712;
    color: #e8c07d;
    align-items: center;
    justify-content: center;
    font-size: .8rem;
    padding: 0;
    cursor: pointer;
    z-index: 2;
}

.skill-map-hud-restore-btn:hover {
    background: #2a2015;
    color: #f6e2a8;
}

.skill-map-hud--minimized .skill-map-hud-banner,
.skill-map-hud--minimized .skill-map-hud-body {
    display: none;
}

.skill-map-hud--minimized .skill-map-hud-restore-btn {
    display: flex;
}

/* HUD secundário é um IRMÃO de .skill-map-hud (não filho, ver comentário
   em _skill_map_hud.html) — se estava aberto quando o HUD principal foi
   minimizado, esconde junto (o botão que o abre está dentro do corpo
   agora escondido, sem isso ficaria "preso" aberto sem jeito de fechar). */
.skill-map-hud--minimized ~ .skill-map-hud-secondary {
    display: none !important;
}

.skill-map-hud-banner-text {
    color: #fff;
    font-weight: 700;
    font-size: .72rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    text-shadow: 1px 1px 0 #000;
    padding: 0 .6rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 78%;
}

/* Conteúdo do corpo do HUD — nível/ avatar/XP/ranking/conquistas "descidos" do topo da Home pra dentro do
   mapa. Personagem grande e "limpo", sem moldura/número em volta (pedido
   do usuário); o NOME do tier (badge_tier_name, ex. "Mestre") entra como
   pill entre o personagem e a barra de XP — mesma classe .badge-tier-pill/
   .badge-tier-pill-N já usada no dashboard, não uma nova. */
.skill-map-hud-avatar-wrap {
    display: flex;
    justify-content: center;
    margin-bottom: .4rem;
    margin-top: .7rem;
    /* âncora pro botão de trocar personagem */
    position: relative;
}

.skill-map-hud-avatar {
    width: 150px;
    height: 150px;
    object-fit: contain;
}

.skill-map-hud-character-btn {
    position: absolute;
    bottom: 0;
    left: calc(50% - 60px);
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    border: 1px solid #97702c;
    background: #1a1712;
    color: #e8c07d;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: .7rem;
    padding: 0;
    cursor: pointer;
}

.skill-map-hud-character-btn:hover {
    background: #2a2015;
    color: #f6e2a8;
}

/* Botão do HUD secundário ("estatísticas extras") — mesmo estilo do botão
   de trocar personagem, espelhado pro canto superior direito do avatar
   (era bottom:0, aqui top:0) em vez do inferior. */
.skill-map-hud-stats-btn {
    position: absolute;
    bottom: 0;
    right: calc(50% - 68px);
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    border: 1px solid #97702c;
    background: #1a1712;
    color: #e8c07d;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: .7rem;
    padding: 0;
    cursor: pointer;
}

.skill-map-hud-stats-btn:hover {
    background: #2a2015;
    color: #f6e2a8;
}

.skill-map-hud-tier-pill-wrap {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: .35rem;
    margin-bottom: .5rem;
}

/* Mesmo EFEITO do pill de XP ao lado (gradiente claro->escuro, achado do
   usuário: gostou do resultado) — mas mantendo a cor própria de cada tier
   (.badge-tier-pill-N, 10 cores, bronze->prata->ouro), não sempre dourado
   (2ª rodada: primeira versão fixava dourado pra todo tier, o usuário
   corrigiu — queria só o EFEITO, não perder a distinção de cor por tier).
   Cada gradiente é a MESMA cor base de .badge-tier-pill-N (ver acima),
   misturada 45% com branco (claro) e 30% com preto (escuro) — calculado,
   não escolhido a olho, pra ficar consistente com a progressão de cor que
   já existe. Duas classes (0,2,0) > uma classe (0,1,0) — vence
   .badge-tier-pill-N sem precisar de !important. */
.skill-map-hud-tier-pill-wrap .badge-tier-pill {
    font-size: .8rem;
    color: #1a1310;
}

.skill-map-hud-tier-pill-wrap .badge-tier-pill-1  { background: linear-gradient(180deg, #e4b98e, #905923); }
.skill-map-hud-tier-pill-wrap .badge-tier-pill-2  { background: linear-gradient(180deg, #e2c1a2, #8d643c); }
.skill-map-hud-tier-pill-wrap .badge-tier-pill-3  { background: linear-gradient(180deg, #e0cbb5, #8b7055); }
.skill-map-hud-tier-pill-wrap .badge-tier-pill-4  { background: linear-gradient(180deg, #ded4c9, #897b6e); }
.skill-map-hud-tier-pill-wrap .badge-tier-pill-5  { background: linear-gradient(180deg, #dcdcdc, #868686); }
.skill-map-hud-tier-pill-wrap .badge-tier-pill-6  { background: linear-gradient(180deg, #e4dfc7, #908a6c); }
.skill-map-hud-tier-pill-wrap .badge-tier-pill-7  { background: linear-gradient(180deg, #eae1b2, #988d51); }
.skill-map-hud-tier-pill-wrap .badge-tier-pill-8  { background: linear-gradient(180deg, #f1e49d, #a19036); }
.skill-map-hud-tier-pill-wrap .badge-tier-pill-9  { background: linear-gradient(180deg, #f8e688, #a9931b); }
.skill-map-hud-tier-pill-wrap .badge-tier-pill-10 { background: linear-gradient(180deg, #ffe973, #b39700); }

/* Pill de XP total ao lado do nome do tier — mesma forma de pill (não
   .badge-tier-pill, cor fixa própria em vez de uma por tier: é uma
   contagem, não um "degrau" como o tier). */
.skill-map-hud-xp-pill {
    display: inline-block;
    padding: .2rem .65rem;
    border-radius: 50rem;
    font-size: .8rem;
    font-weight: 600;
    color: #1a1310;
    background: linear-gradient(180deg, #f6e2a8, #d8ad57);
}

.skill-map-hud-xp-pill i {
    margin-right: .25rem;
}

/* Badges de acerto (Answer.get_badge_data — "Assunto dominado"/"Sequência
   perfeita"/etc., antes soltos num bloco à parte da Home) — ícone + número
   compacto (ver comentário em _skill_map_hud.html), cor só no ícone pra
   diferenciar o tipo sem precisar de 4 cores de fundo (ficaria poluído
   nesse espaço pequeno). */
.skill-map-hud-badges {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .3rem;
    margin-top: .4rem;
}

.skill-map-hud-badge {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .15rem .5rem;
    border-radius: .25rem;
    font-size: .8rem;
    font-weight: 600;
    border: 1px solid #4a3f2e;
    background: #232228;
    color: #e9ecef;
}

.skill-map-hud-badge-fire i { color: #ff6b4a; }
/* Mesmo ícone (fa-fire) do badge de "melhor sequência perfeita" acima —
   cor própria pra "sequência ATUAL de acertos" não ler como o mesmo
   badge (achado do usuário: os dois eram visualmente idênticos). */
.skill-map-hud-badge-fire-current i { color: #ffc107; }
.skill-map-hud-badge-trophy i { color: #ffd700; }
.skill-map-hud-badge-shield i { color: #4fc3f7; }

/* Barra de XP recortada de xp_bar_example.png (pacote do usuário) em vez
   de aproximação Bootstrap — 4 pedaços: cap esquerdo (ícone "XP", tamanho
   fixo), trilho vazio (repetido horizontalmente, o "fundo" da barra),
   preenchimento (mesma textura, também repetida, recortado via `width`
   dinâmico = progress_pct — a REGRA de negócio continua sendo só um
   número, a imagem só troca a pintura), cap direito (rebite decorativo,
   tamanho fixo). Texto "X/Y" fica por cima do trilho inteiro (HTML de
   verdade, não faz parte da imagem — mesmo motivo do nome do aluno na
   faixa). */
.skill-map-hud-xp-bar {
    display: flex;
    align-items: stretch;
    height: 28px;
    margin-bottom: .5rem;
    /* Containing block pro cap direito, que agora fica sobreposto por cima
       da trilha (ver .skill-map-hud-xp-cap-right) em vez de ser um segmento
       separado à direita dela. */
    position: relative;
}

.skill-map-hud-xp-cap {
    position: relative;
    width: 28px;
    flex-shrink: 0;
    background-size: 100% 100%;
    image-rendering: pixelated;
}

/* O cap esquerdo tinha "XP" desenhado na própria imagem (apagado — ver
   comentário em _skill_map_hud.html); o nível do aluno entra aqui em HTML
   de verdade, centralizado sobre o espaço que sobrou. */
/* Gradiente dourado (tons amostrados do próprio "XP" que estava desenhado
   na imagem original, antes de apagado — não fica idêntico ao original
   renderizado em pixel art, mas aproxima o efeito metálico em vez de
   texto branco liso) — background-clip:text recorta o gradiente pela
   forma das letras; text-shadow continua funcionando por cima (desenha
   pela forma do glifo, independente do fill ser cor sólida ou
   transparente-com-gradiente-atrás). */
.skill-map-hud-xp-cap-level {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: .9rem;
    font-weight: 800;
    background: linear-gradient(180deg, #f6e2a8 0%, #d8ad57 45%, #97702c 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -moz-animation: glow 1s ease-in-out infinite alternate;
    animation: glow 1s ease-in-out infinite alternate;
}

@-webkit-keyframes glow {
    from {
        text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #e60073, 0 0 40px #e60073, 0 0 50px #e60073, 0 0 60px #e60073, 0 0 70px #e60073;
    }
    to {
        text-shadow: 0 0 20px #fff, 0 0 30px #ff4da6, 0 0 40px #ff4da6, 0 0 50px #ff4da6, 0 0 60px #ff4da6, 0 0 70px #ff4da6, 0 0 80px #ff4da6;
    }
}

.skill-map-hud-xp-cap-left {
    background-image: url("../images/skill_map_hud_xp_cap_left.png");
}

.skill-map-hud-xp-cap-right {
    background-image: url("../images/skill_map_hud_xp_cap_right.png");
    /* Sai do fluxo de flex (a trilha cresce pra ocupar o espaço que sobrar,
       ver .skill-map-hud-xp-bar) e sobrepõe o fim da trilha — a imagem
       agora tem fundo transparente, então o preenchimento colorido (mesmo
       perto de 100%) aparece por baixo dela em vez de parar antes.
       Tamanho nativo (20x32), ancorada pela direita e centralizada
       verticalmente na barra — sem isso, background-size:100% 100% herdado
       de .skill-map-hud-xp-cap esticava a imagem pra caber num quadrado
       28x28, distorcendo. .skill-map-hud-xp-track tem margin-right pra
       sobrar espaço da imagem encostar sem cobrir o próprio texto/trilha. */
    width: 20px;
    height: 32px;
    background-size: 100% 100%;
    position: absolute;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    z-index: 2;
}

.skill-map-hud-xp-track {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    /* 10px a menos que o espaço todo (a trilha cresce via flex:1 pra ocupar
       o que sobrar depois do cap esquerdo — ver .skill-map-hud-xp-cap-right,
       que saiu do fluxo) — sem essa margem o cap direito (20x32) fica bom
       demais sobreposto e cobre um pedaço maior que o previsto da trilha. */
    margin-right: 10px;
    background-image: url("../images/skill_map_hud_xp_track.png");
    background-repeat: repeat-x;
    background-size: auto 100%;
    image-rendering: pixelated;
    display: flex;
    align-items: center;
    justify-content: center;
}

.skill-map-hud-xp-fill {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    background-image: url("../images/skill_map_hud_xp_fill.png");
    background-repeat: repeat-x;
    background-size: auto 100%;
    image-rendering: pixelated;
}

.skill-map-hud-xp-text {
    position: relative;
    z-index: 1;
    color: #fff;
    font-size: .8rem;
    font-weight: 600;
    text-shadow: 1px 1px 0 #000;
}

.skill-map-hud-line {
    display: block;
    font-size: .88rem;
    color: #e9ecef;
    text-decoration: none;
    margin-bottom: .25rem;
}

.skill-map-hud-line i {
    color: #ffc107;
    margin-right: .3rem;
    width: 1rem;
    text-align: center;
}

.skill-map-hud-line:hover {
    color: #fff;
    text-decoration: underline;
}

/* Ranking geral + Conquistas lado a lado (pedido do usuário) em vez de
   empilhados — dentro da linha, cada .skill-map-hud-line vira inline,
   sem a margem inferior que fazia sentido só quando empilhado. */
.skill-map-hud-line-row {
    display: flex;
    justify-content: center;
    gap: .9rem;
    margin-bottom: .4rem;
}

.skill-map-hud-line-row .skill-map-hud-line {
    display: inline-flex;
    align-items: center;
    margin-bottom: 0;
}

/* "Continuar"/"Meu Progresso" — mapa como ponto central de interação do
   aluno: as duas ações que antes só existiam em Disciplinas
   Matriculadas/Meu Progresso, agora também aqui. Continuar
   é a ação primária (mesmo gradiente dourado de destaque de sempre —
   .skill-map-node-panel-practice/tier pills); Meu Progresso fica um degrau
   abaixo visualmente (contorno dourado sobre fundo escuro, não preenchido). */
.skill-map-hud-actions {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    margin-bottom: .5rem;
}

.skill-map-hud-btn-continue {
    background: linear-gradient(180deg, #f6e2a8, #d8ad57);
    border: 1px solid #97702c;
    color: #1a1310;
    font-weight: 600;
}

.skill-map-hud-btn-continue:hover {
    background: linear-gradient(180deg, #ffe973, #b39700);
    color: #1a1310;
}

.skill-map-hud-btn-progress {
    background: #1a1712;
    border: 1px solid #97702c;
    color: #e8c07d;
    font-weight: 600;
}

.skill-map-hud-btn-progress:hover {
    background: #2a2015;
    color: #f6e2a8;
}

/* HUD secundário ("estatísticas extras" — Professor/Respondidas/%Acerto/
   Status, os campos que sobravam só na tabela de Disciplinas Matriculadas)
   — painel IRMÃO de .skill-map-hud (não filho), ancorado ao lado dele
   dentro do mesmo .skill-map-canvas-wrap (position:relative). Escondido
   por padrão (display:none — não d-none do Bootstrap, pra não brigar com
   a regra --open abaixo) e revelado pelo toggle de classe do botão em
   .skill-map-hud-stats-btn. Mesma técnica de moldura+faixa recortadas do
   HUD principal (ver comentário lá), desta vez de example_hud2.png —
   "mesma cara" do HUD principal, pedido do usuário. */
.skill-map-hud-secondary {
    display: none;
    position: absolute;
    top: .5rem;
    left: 236px;
    z-index: 2;
    width: 176px;
    transform-origin: top left;
}

.skill-map-hud-secondary--open {
    display: block;
}

@container (max-width: 520px) {
    .skill-map-hud-secondary { transform: scale(.78); }
}

@container (max-width: 400px) {
    .skill-map-hud-secondary { transform: scale(.6); }
}

@container (max-width: 300px) {
    .skill-map-hud-secondary { transform: scale(.46); }
}

.skill-map-hud-secondary-banner {
    margin: 0 auto -46px;
    /*width: calc(100% - .3rem);*/
    aspect-ratio: 364 / 100;
    background-image: url("../images/skill_map_hud2_banner.png");
    background-size: 100% 100%;
    image-rendering: pixelated;
    clip-path: polygon(0 0, 100% 0, 100% 68%, 85% 100%, 50% 95%, 15% 100%, 0 68%);
    display: flex;
    justify-content: center;
    position: relative;
    z-index: 1;
    padding-top: 6px;
}

.skill-map-hud-secondary-banner-text {
    color: #fff;
    font-weight: 700;
    font-size: .62rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    text-shadow: 1px 1px 0 #000;
    margin-top: 5px;
}

.skill-map-hud-secondary-body {
    min-height: 90px;
    background: #232228;
    background-clip: padding-box;
    border-style: solid;
    border-width: 12px;
    border-image-source: url("../images/skill_map_hud_frame.png");
    border-image-slice: 22 fill;
    border-image-repeat: stretch;
    image-rendering: pixelated;
    padding: .9rem .5rem .5rem;
}

.skill-map-hud-secondary-stats {
    margin: 0;
    font-size: .9rem;
}

.skill-map-hud-secondary-stats dt {
    color: #c9a86a;
    font-weight: 600;
}

.skill-map-hud-secondary-stats dd {
    color: #e9ecef;
    margin: 0 0 .4rem;
}

.skill-map-hud-secondary-stats a {
    color: #fff;
    text-decoration: underline;
}

/* Container de missões do mapa — mesma técnica de moldura recortada do HUD (border-image, ver
   .skill-map-hud-body acima), com skill_map_quests_frame.png reprocessada
   a partir de assets/container_quests_exemplo.png em 3 achados sucessivos
   (nenhum verificável sem navegador — só via composição sintética com
   Pillow sobre um fundo vibrante, pra qualquer resíduo indevido ficar
   óbvio):
   1) A imagem original vinha com fundo TOTALMENTE opaco (a "moldura"
      cobria o mapa inteiro com uma caixa sólida em vez de deixar o fundo
      #232228 de sempre aparecer por trás) — resolvido tornando o
      INTERIOR transparente (só a faixa de borda fica opaca) e recortando
      a gema central pra um sprite à parte, skill_map_quests_ornament.png
      (mesmo tratamento de skill_map_hud_banner.png separado do frame do
      HUD — sem isso, esticar a imagem inteira via border-image distorcia
      a gema junto com a borda).
   2) A fonte original (725x251) é bem maior que o painel renderizado
      (320px) — border-image tinha que ENCOLHER a imagem (~0.44x), e
      image-rendering:pixelated (nearest-neighbor) fica ruim em downscale
      grande, diferente do HUD (92px de fonte, sempre um UPSCALE ~2.4x).
      Resolvido reamostrando a própria imagem (Pillow, LANCZOS, offline)
      pra ~145px de largura ANTES de aplicar qualquer transparência —
      resize de uma imagem já com alpha variável mistura (sem premultiplicar)
      o RGB "morto" da área transparente com a borda vizinha, criando uma
      franja cinza na borda mesmo com alpha baixo ali; resize sempre
      enquanto 100% opaca, a máscara de transparência entra por último, em
      cima da imagem já no tamanho final.
   3) A imagem original tinha uma margem cinza chapada (fundo do
      print/mockup) ENVOLVENDO a moldura de pedra de verdade, antes dela
      começar (medida: ~19px em cima, ~7px embaixo, ~8px/10px nas
      laterais) — essa margem sobrevivia dentro da faixa de borda
      (BORDER px de qualquer extremidade) e aparecia como um retângulo
      cinza chapado do LADO DE FORA do anel de pedra. Resolvido recortando
      essa margem fora ANTES do resize/reamostragem (não depois — cortar
      depois deixaria a proporção poste-resize inconsistente com os
      cálculos de posição da gema). Cantos arredondados (recorte circular
      raso, ~6px de raio) adicionados no mesmo passo — mesmo espírito do
      corte "staircase" à mão que skill_map_hud_frame.png já tinha
      (conferido pixel a pixel), aqui só via fórmula (distância ao centro
      do canto), já que a fonte não trouxe esse corte pronto.
   slice:10 corresponde à espessura da faixa de borda NESSA versão final
   (reamostrada + recortada).
   Expandido = TOPO CENTRAL do canvas (mesma âncora top/left:50% do
   painel de nó — os dois nunca ficam abertos ao mesmo tempo, ver
   collapseQuestPanel/showNodePanel em skill_map_pixi.js), lista direta
   das missões ativas do mundo. Colapsado (.skill-map-quest-panel--collapsed)
   esconde o corpo inteiro e mostra só a pill, canto SUPERIOR direito, à
   esquerda do botão de recentralizar (mais visível ali do que num canto
   vazio). Proporção não verificada visualmente (sem navegador disponível
   nesta sessão) — primeira passada, ajuste fino depois de rodar. */
/* inset:0 + pointer-events:none: a âncora cobre o wrap inteiro só pra dar
   um referencial de canto pro badge/corpo abaixo sem bloquear o arrastar
   do mapa por baixo — os filhos (badge/body) reativam pointer-events em
   si mesmos. */
.skill-map-quest-panel {
    position: absolute;
    inset: 0;
    z-index: 3;
    pointer-events: none;
}

/* right: soma a folga do recenter (.5rem) + a largura dele (2.25rem) +
   um respiro (.4rem) — mantém a pill colada mas sem encostar. */
.skill-map-quest-panel-badge {
    display: none;
    position: absolute;
    top: .5rem;
    right: 3.15rem;
    align-items: center;
    gap: .4rem;
    background: #232228;
    border: 2px solid #97702c;
    border-radius: 999px;
    color: #ffc107;
    font-size: .75rem;
    font-weight: 600;
    padding: .35rem .7rem;
    cursor: pointer;
    pointer-events: auto;
}

.skill-map-quest-panel--collapsed .skill-map-quest-panel-badge {
    display: inline-flex;
}

.skill-map-quest-panel-body {
    position: absolute;
    top: .5rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 3;
    width: 320px;
    max-width: calc(100% - 1rem);
    background: #232228;
    background-clip: padding-box;
    border-style: solid;
    border-width: 14px;
    border-image-source: url("../images/skill_map_hud_frame.png");
    border-image-slice: 22 fill;
    border-image-repeat: stretch;
    image-rendering: pixelated;
    padding: .8rem .8rem .6rem;
    color: #fff;
    pointer-events: auto;
}

.skill-map-quest-panel--collapsed .skill-map-quest-panel-body {
    display: none;
}

/* Gema/suporte metálico — mesmo espírito de .skill-map-hud-banner (overlay
   à parte, nunca esticado junto com a borda). Centralizada no topo,
   parcialmente sobreposta à moldura (metade pra fora, metade pra dentro). */
.skill-map-quest-panel-ornament {
    position: absolute;
    top: -1.3rem;
    left: 50%;
    transform: translateX(-50%);
    width: 3.5rem;
    aspect-ratio: 76 / 40;
    background-image: url("../images/skill_map_quests_ornament.png");
    background-size: 100% 100%;
    image-rendering: pixelated;
    pointer-events: none;
}

/* Ícone (mesmo estilo circular do botão de minimizar o HUD principal, ver
   .skill-map-hud-minimize-btn) — texto "Esconder" trocado por um chevron. */
.skill-map-quest-panel-hide {
    position: absolute;
    top: .3rem;
    right: .3rem;
    width: 1.4rem;
    height: 1.4rem;
    border-radius: 50%;
    border: 1px solid #97702c;
    background: #1a1712;
    color: #e8c07d;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: .65rem;
    padding: 0;
    cursor: pointer;
    z-index: 2;
}

.skill-map-quest-panel-hide:hover {
    background: #2a2015;
    color: #f6e2a8;
}

.skill-map-quest-panel-title {
    font-weight: 700;
    color: #e8c07d;
    margin-top: .3rem;
    margin-bottom: .5rem;
    padding-right: 1.6rem;
}

.skill-map-quest-panel-title i {
    margin-right: .3rem;
}

.skill-map-quest-panel-row {
    display: flex;
    align-items: flex-start;
    gap: .5rem;
    margin-bottom: .6rem;
}

.skill-map-quest-panel-row i {
    color: #ffc107;
    margin-top: .2rem;
}

.skill-map-quest-panel-row-info {
    flex-grow: 1;
    min-width: 0;
}

.skill-map-quest-panel-row-label {
    font-size: .78rem;
    margin-bottom: .25rem;
}

/* Accordion do mapa (Home do aluno, index/_student.html) — moldura escura +
   acento dourado, pra combinar com o mapa desenhado à mão e o HUD que ficam
   DENTRO dele (paleta "pedra escura + dourado" já estabelecida em ambos —
   ver .skill-map-hud-body/.skill-map-hud-xp-* acima), não a paleta
   cyberpunk roxo/ciano do .app-header (que é de uma camada de página
   diferente, não faz sentido misturar as duas). Escopado a #mapAccordion
   de propósito — outros accordions do app (#achievementsAccordion no
   perfil) continuam no visual padrão do Bootstrap. Aberto por padrão desde
   2026-07-15 (achado do usuário: com o HUD assumindo as informações que
   ficavam no topo da Home, o mapa passou a ser conteúdo central o
   suficiente pra não ficar escondido atrás de um clique). */
#mapAccordion .accordion-item {
    background: #1a1712;
    border: 1px solid #4a3f2e;
}

#mapAccordion .accordion-button {
    background: #1a1712;
    color: #e8c07d;
    text-transform: uppercase;
    letter-spacing: .06em;
    font-weight: 700;
    font-size: .95rem;
    box-shadow: none;
    /* Ícone padrão do Bootstrap é um SVG cinza-escuro embutido via CSS var
       — troca de cor via filter em cima de um SVG arbitrário é impreciso
       sem conferir renderizado; sobrescrever a própria var com o mesmo
       ícone em dourado é exato, não é aproximação. */
    --bs-accordion-btn-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23e8c07d'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
    --bs-accordion-btn-active-icon: var(--bs-accordion-btn-icon);
}

#mapAccordion .accordion-button:hover {
    color: #f6e2a8;
}

#mapAccordion .accordion-button:not(.collapsed) {
    background: linear-gradient(90deg, #2a2015, #1a1712);
    color: #f6e2a8;
    box-shadow: inset 0 -2px 0 #97702c;
}

#mapAccordion .accordion-button:focus {
    box-shadow: 0 0 0 .2rem rgba(216, 173, 87, .35);
}

#mapAccordion .accordion-body {
    background: #101013;
    padding: 1rem;
}

#mapAccordion .accordion-body h6 {
    color: #c9b28a;
}

/* Abas (várias disciplinas com mapa — ver index/_student.html) — mesmo
   tratamento dourado/pedra-escura do cabeçalho do accordion acima, no
   lugar do azul padrão do Bootstrap (.nav-pills). */
#mapAccordion .nav-pills .nav-link {
    color: #c9b28a;
    background: transparent;
    border: 1px solid #4a3f2e;
    border-radius: 0;
    text-transform: uppercase;
    font-size: .78rem;
    letter-spacing: .04em;
    font-weight: 600;
    margin-right: .35rem;
}

#mapAccordion .nav-pills .nav-link:hover:not(.active) {
    border-color: #97702c;
    color: #f6e2a8;
}

#mapAccordion .nav-pills .nav-link.active {
    background: linear-gradient(180deg, #d8ad57, #97702c);
    color: #1a1310;
    border-color: #97702c;
}

/* Modal de troca de personagem — mesma paleta pedra-escura + dourado do
   #mapAccordion/HUD (ver acima), pra não destoar do resto do "chrome" do
   mapa. Carousel = 1 slide por personagem (ver _character_select_modal.html). */
.character-select-modal-content {
    background: #1a1712;
    border: 1px solid #4a3f2e;
    color: #e8c07d;
}

.character-select-modal-content .modal-header {
    border-bottom-color: #4a3f2e;
}

.character-select-modal-content .btn-close {
    filter: invert(1) grayscale(1) brightness(1.5);
}

.character-preview-img {
    width: 220px;
    height: 220px;
    object-fit: contain;
}

.character-select-name {
    color: #f6e2a8;
    text-transform: uppercase;
    letter-spacing: .05em;
}

.character-style-option .form-check-input {
    background-color: #1a1712;
    border-color: #97702c;
}

.character-style-option .form-check-input:checked {
    background-color: #d8ad57;
    border-color: #d8ad57;
}

.character-style-option .form-check-label {
    color: #c9b28a;
}

.character-select-btn {
    background: linear-gradient(180deg, #d8ad57, #97702c);
    border: 1px solid #97702c;
    color: #1a1310;
    font-weight: 600;
}

.character-select-btn:hover:not(.character-select-btn-locked) {
    background: linear-gradient(180deg, #f6e2a8, #d8ad57);
    color: #1a1310;
}

/* Sem :disabled nativo de propósito (ver character_select.js) — o botão
   "Bloqueado" continua clicável/hoverável no DOM (senão o tooltip nunca
   apareceria), só visualmente travado + cursor not-allowed; o submit real
   é barrado no JS, não aqui. !important no cursor porque o :hover do
   Bootstrap (pseudo-classe, especificidade maior que uma classe simples)
   senão vencia o cursor:pointer de volta ao passar o mouse. Foco/clique
   (:focus/:active, incluindo as variantes .focus/.active que o próprio JS
   do Bootstrap adiciona) tira o box-shadow dourado padrão do Bootstrap —
   sem sentido num botão que não faz nada quando clicado. */
.character-select-btn-locked {
    background: #3a352a;
    border: 1px solid #4a3f2e;
    color: #8a7d5e;
    opacity: 1;
    cursor: not-allowed !important;
}

.character-select-btn-locked:focus,
.character-select-btn-locked:active,
.character-select-btn-locked.focus,
.character-select-btn-locked.active {
    box-shadow: none !important;
    outline: none !important;
}

.character-select-carousel .carousel-control-prev-icon,
.character-select-carousel .carousel-control-next-icon {
    background-color: rgba(216, 173, 87, .5);
    border-radius: 50%;
    padding: 1.2rem;
    background-size: 60%;
}

/* Banner "novo estilo desbloqueado" (_character_unlock_banner.html) — dentro
   de #levelUpModal (fundo claro do modal padrão, não o tema escuro acima). */
.character-unlock-banner {
    display: flex;
    align-items: center;
    gap: .75rem;
    background: #fff8e6;
    border: 1px solid #e8c07d;
    border-radius: .5rem;
    padding: .5rem .75rem;
    margin-top: .5rem;
    text-align: left;
}

.character-unlock-banner-img {
    width: 56px;
    height: 56px;
    object-fit: contain;
    flex-shrink: 0;
}

.character-unlock-banner-text {
    font-size: .85rem;
    color: #5a4426;
}

/* Indicativo de pendência na aba de disciplina (index/_student.html, mapa
   como ponto central de interação) — substitui
   a barra de "disciplinas com pendências" que ficava acima do mapa (removida:
   cada mundo já mostra sua própria pendência dentro do próprio HUD, então a
   barra à parte virou redundante), agora só um ponto discreto na aba mesma.
   Vermelho = pendência real (mesma urgência de um "responda algo"); âmbar
   = só aguardando correção (nada que o aluno precise fazer agora). */
.map-tab-pending-dot {
    display: inline-block;
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: #dc3545;
    margin-left: .35rem;
    vertical-align: middle;
}

.map-tab-pending-dot--grading {
    background: #ffc107;
}

.skill-remedial {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 8px;
    max-width: 150px;
}

.skill-remedial-chip {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    justify-content: center;
    gap: 4px;
    font-size: .72rem;
    background: #fff8e1;
    border: 1px dashed #e0c26a;
    border-radius: 10px;
    padding: 3px 8px;
    color: #7a5c00;
    text-align: center;
}

/* Conquistas — grade de medalhas
   no perfil (profile/view.html). Desbloqueada usa o mesmo dourado de
   .skill-node-mastered/.badge-tier-pill-10 (mesma linguagem visual de
   "conquista" já estabelecida no app); bloqueada em cinza, mas com
   nome+descrição SEMPRE visíveis (nunca escondidos atrás de hover/tooltip —
   celular não tem hover) como dica de como desbloquear. */
.achievements-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: .75rem;
}

.achievement-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: .75rem .5rem;
    border-radius: .5rem;
    border: 1px solid #e9ecef;
}

.achievement-card.unlocked {
    border-color: #f2d226;
    background: #fffdf5;
}

.achievement-icon {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.3rem;
    margin-bottom: .4rem;
}

.achievement-locked {
    background: #f1f1f1;
    color: #adb5bd;
}

.achievement-unlocked {
    background: #ffd700;
    color: #4a3418;
}

.achievement-name {
    font-weight: 600;
    font-size: .85rem;
}

.achievement-card.locked .achievement-name {
    color: #adb5bd;
}

.achievement-desc {
    font-size: .72rem;
    color: #6c757d;
}

/* Header (header.html) — Referência: cyberpunk.net (preto quase-total, acento neon único,
   formas angulares, tipografia caixa-alta), adaptado com acento mais suave
   (roxo/ciano em vez de amarelo saturado) e sem depender de fonte externa —
   o app não carrega nada de CDN, só pacotes npm locais, então o efeito
   "tech" vem só de caixa-alta + peso + espaçamento de letra. Nenhuma fonte
   nova, nenhum request externo. Contagem de notificações (#notificationBadge)
   não muda. */
.app-header {
    position: relative;
    background: #0a0a12;
}

.app-header::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    background: linear-gradient(90deg, #9d5cff, #22d3ee 65%, transparent);
}

.app-header .navbar-brand {
    display: flex;
    align-items: center;
    gap: .4rem;
    text-transform: uppercase;
    font-weight: 700;
    letter-spacing: .08em;
}

.app-header .navbar-brand i {
    color: #22d3ee;
    text-shadow: 0 0 8px rgba(34, 211, 238, .6);
}

.app-header .navbar-nav {
    align-items: center;
}

.app-header .navbar-nav.me-auto > .nav-item {
    display: flex;
    align-items: center;
}

.app-header .navbar-nav.me-auto > .nav-item + .nav-item::before {
    content: '/';
    color: rgba(255, 255, 255, .25);
    margin: 0 .35rem;
}

.app-header .nav-link {
    display: flex;
    align-items: center;
    gap: .4rem;
    text-transform: uppercase;
    font-size: .82rem;
    font-weight: 600;
    letter-spacing: .06em;
    transition: color .15s ease-in-out;
}

.app-header .nav-link > i {
    font-size: 1rem;
}

.app-header .nav-link:hover {
    color: #9d5cff;
}

.app-header .nav-link.active {
    color: #22d3ee;
}

.app-header .nav-link.active::before {
    content: '[ ';
}

.app-header .nav-link.active::after {
    content: ' ]';
}

.app-header #notificationBellLink:hover {
    color: #9d5cff;
}

.app-header .dropdown-toggle::after {
    width: 6px;
    height: 6px;
    margin-left: .45rem;
    vertical-align: 1px;
    border: none;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
    opacity: .75;
}

.app-header .dropdown-menu {
    border-radius: 0;
    border: 1px solid rgba(157, 92, 255, .4);
}

.navbar-avatar {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    object-fit: cover;
    clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
}

/* Nome do badge_tier — pill pequeno, uma cor por NÍVEL (1-10, badge_tier).
   Degradê bronze -> prata -> ouro conforme o tier sobe (medalha:
   tier baixo = bronze, meio = prata, tier 10 = ouro), interpolado em RGB
   entre os 3 tons clássicos (#CD7F32 / #C0C0C0 / #FFD700). Texto num marrom
   escuro único — todas as 10 cores são claras/médias o bastante pra manter
   contraste sem precisar calcular caso a caso. */
.badge-tier-pill {
    display: inline-block;
    padding: .2rem .65rem;
    border-radius: 50rem;
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .02em;
    color: #4a3418;
}
.badge-tier-pill-1  { background-color: #cd7f32; }
.badge-tier-pill-2  { background-color: #ca8f56; }
.badge-tier-pill-3  { background-color: #c7a079; }
.badge-tier-pill-4  { background-color: #c3b09d; }
.badge-tier-pill-5  { background-color: #c0c0c0; }
.badge-tier-pill-6  { background-color: #cdc59a; }
.badge-tier-pill-7  { background-color: #d9c973; }
.badge-tier-pill-8  { background-color: #e6ce4d; }
.badge-tier-pill-9  { background-color: #f2d226; }
.badge-tier-pill-10 { background-color: #ffd700; }

.level-up-banner {
    background: linear-gradient(135deg, #6f42c1, #22d3ee);
    color: #fff;
    border-radius: .5rem;
    padding: .75rem 1rem;
    animation: level-up-pop .4s ease-out;
}

@keyframes level-up-pop {
    0% { transform: scale(.85); opacity: 0; }
    100% { transform: scale(1); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .level-up-banner { animation: none; }
}

/* Modal "Meu Progresso" do HUD (index/_student.html, progress_modal.js) —
   pedido do usuário: 80-90% da tela, não o modal-xl fixo (1140px) do
   Bootstrap. Largura fixa em vw; altura fica a cargo de
   .modal-dialog-scrollable (já classe do markup) — Bootstrap já limita
   .modal-content à altura da viewport menos as margens verticais padrão
   e faz só o corpo rolar, sem precisar de max-height/overflow manual aqui. */
.modal-progress {
    max-width: 88vw;
}

/* Footer (footer.html) — espelha o header: mesma faixa gradiente roxo→ciano
   (em cima, já que o footer fica embaixo da tela) sobre o mesmo preto
   quase-total, texto branco em vez do text-muted cinza padrão do Bootstrap. */
.app-footer {
    position: relative;
    background: #0a0a12;
    color: #fff;
}

.app-footer::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: 3px;
    background: linear-gradient(90deg, #9d5cff, #22d3ee 65%, transparent);
}
/* Tela "Estudar" — arquivos visíveis do assunto (question/_study_material_content.html):
   bloco recolhível + uma aba por arquivo. */
.study-files {
    border: 1px solid #dee2e6;
    border-radius: .6rem;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
    overflow: hidden;
}
.study-files-toggle {
    display: flex;
    align-items: center;
    width: 100%;
    padding: .75rem 1rem;
    border: 0;
    background: linear-gradient(90deg, rgba(111, 66, 193, .10), rgba(13, 202, 240, .08));
    font-weight: 600;
    color: #3d2a6b;
    text-align: left;
}
.study-files-toggle:hover { background: linear-gradient(90deg, rgba(111, 66, 193, .16), rgba(13, 202, 240, .12)); }
.study-files-count { background: #6f42c1; font-size: .75rem; }
.study-files-chevron { transition: transform .2s; }
.study-files-toggle.collapsed .study-files-chevron { transform: rotate(-90deg); }

.study-files-tabs {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: .25rem;
    padding: .5rem .5rem 0;
    border-bottom: 1px solid #dee2e6;
    background: #f8f9fa;
}
.study-files-tabs .nav-link {
    display: flex;
    align-items: center;
    max-width: 16rem;
    padding: .45rem .8rem;
    border: 1px solid transparent;
    border-bottom: 0;
    border-radius: .45rem .45rem 0 0;
    color: #495057;
    font-size: .875rem;
    white-space: nowrap;
}
.study-files-tabs .nav-link:hover { background: #eef0f3; }
.study-files-tabs .nav-link.active {
    background: #fff;
    border-color: #dee2e6;
    color: #6f42c1;
    font-weight: 600;
    margin-bottom: -1px;
}
.study-files-tab-name { overflow: hidden; text-overflow: ellipsis; }

.study-file-bar {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .5rem 1rem;
    font-size: .85rem;
    border-bottom: 1px solid #f1f3f5;
}
.study-file-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.study-file-open { color: #6c757d; white-space: nowrap; text-decoration: none; }
.study-file-open:hover { color: #0d6efd; }
.study-file-viewer {
    display: block;
    width: 100%;
    height: 75vh;
    border: 0;
}
.study-file-image { display: block; padding: .75rem; text-align: center; cursor: zoom-in; }
.study-file-image img { max-width: 100%; max-height: 70vh; }

/* Loader do mapa (_skill_map_loader.html + finishMapLoading em skill_map_pixi.js):
   reserva o tamanho do mapa (--map-ratio = altura/largura) e cobre o canvas até
   as texturas terminarem de carregar. */
.skill-map-canvas-container.is-loading,
.skill-map-walk-canvas-container.is-loading {
    display: block;
    width: 100%;
    position: relative;
    aspect-ratio: 1 / var(--map-ratio, 0.65625);
    border-radius: .5rem;
    overflow: hidden;
}
.skill-map-loader {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .8rem;
    color: #e9e4ff;
    background: var(--map-bg, #0d0d1a);
    background: radial-gradient(circle at 50% 42%,
                color-mix(in srgb, var(--map-bg, #0d0d1a) 65%, #6f42c1 35%) 0%,
                var(--map-bg, #0d0d1a) 70%);
    transition: opacity .4s ease;
}
.skill-map-loader--done { opacity: 0; pointer-events: none; }
.skill-map-loader-icon {
    font-size: 2.4rem;
    color: #7fe7ff;
    filter: drop-shadow(0 0 10px rgba(13, 202, 240, .55));
    animation: skill-map-loader-float 1.6s ease-in-out infinite;
}
.skill-map-loader-bar {
    position: relative;
    width: min(220px, 60%);
    height: 4px;
    border-radius: 2px;
    background: rgba(255, 255, 255, .14);
    overflow: hidden;
}
.skill-map-loader-bar::after {
    content: "";
    position: absolute;
    top: 0;
    left: -40%;
    width: 40%;
    height: 100%;
    border-radius: 2px;
    background: linear-gradient(90deg, #6f42c1, #0dcaf0);
    animation: skill-map-loader-sweep 1.2s ease-in-out infinite;
}
.skill-map-loader-text {
    font-size: .78rem;
    font-weight: 600;
    letter-spacing: .18em;
    text-transform: uppercase;
    text-align: center;
    padding: 0 1rem;
}
.skill-map-loader-dots span { animation: skill-map-loader-dot 1.2s infinite; opacity: 0; }
.skill-map-loader-dots span:nth-child(2) { animation-delay: .2s; }
.skill-map-loader-dots span:nth-child(3) { animation-delay: .4s; }
.skill-map-loader--error .skill-map-loader-icon { animation: none; color: #ffb3b3; filter: none; }
.skill-map-loader--error .skill-map-loader-bar { display: none; }
.skill-map-loader--error .skill-map-loader-text { letter-spacing: .04em; text-transform: none; color: #ffd6d6; }

@keyframes skill-map-loader-float {
    0%, 100% { transform: translateY(0) rotate(-3deg); }
    50% { transform: translateY(-8px) rotate(3deg); }
}
@keyframes skill-map-loader-sweep {
    0% { left: -40%; }
    100% { left: 100%; }
}
@keyframes skill-map-loader-dot {
    0%, 20% { opacity: 0; }
    40%, 100% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .skill-map-loader-icon, .skill-map-loader-bar::after, .skill-map-loader-dots span { animation: none; }
    .skill-map-loader-dots span { opacity: 1; }
    .skill-map-loader-bar::after { left: 0; width: 100%; }
}
