html, body {
    font-family: 'Global Monospace', Helvetica, Arial, sans-serif;
    height: 100%;
    margin: 0;
    /* Keep the whole app confined to one screen; only .main-page-content scrolls. */
    overflow: hidden;
}

/* main layout */
.main-page {
    display: flex;
    flex-direction: column;
    max-width: 100%;
    min-width: 100%;
    height: 100vh;
    height: 100dvh;
}

.main-page-content {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    display: flex;
    flex-direction: column;
}

.ktf-game {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

.ktf-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.6em;
    padding: 2em;
    text-align: center;
}

.ktf-loading-icon {
    position: static;
    width: 56px;
    height: auto;
}

/*scoreboard*/
.score-tray {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: center;
    gap: 0.9em;
    max-width: 480px;
    width: max-content;
    margin: 0.3em auto;
    padding: 0.35em 0.8em;
    background: #fff7ec;
    border: 1px solid #e0c9a6;
    border-radius: 999px;
    box-shadow: 0 1px 3px rgba(0,0,0,.15);
    font-weight: bold;
    font-size: 0.95em;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.score-icon {
    width: auto;
    text-align: center;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.3em;
    flex-shrink: 0;
    white-space: nowrap;
}

/* On narrow screens, shrink gaps/font/icon size instead of wrapping to a second line */
@media (max-width: 480px) {
    .score-tray {
        gap: 0.45em;
        padding: 0.3em 0.5em;
        font-size: 0.8em;
    }

    .score-icon img {
        width: 20px !important;
        height: 20px !important;
    }
}

.score-coords {
    color: #4a4033;
    border-left: 1px solid #e0c9a6;
    padding-left: 0.9em;
}

/*banner*/
.banner-photo {
    max-width: 100%;
    min-width: 100%;
    max-height: 12vh;
    object-fit: cover;
    flex: 0 0 auto;
}

/*photo manager (admin tool to upload/slice map photos)*/
.photo-manager {
    max-width: 500px;
    width: 100%;
    box-sizing: border-box;
    margin: 1em auto;
    padding: 0 0.6em 1em;
    /* Let the single .main-page-content scroll container handle scrolling (same
       pattern as .weather-forecast) instead of nesting a second overflow:auto
       box in here - having two independent scroll containers made scrolling feel
       broken/janky, with content getting clipped between the two. */
}

.photo-manager-section {
    background: rgba(0, 0, 0, 0.035);
    border: 1px solid rgba(0, 0, 0, 0.08);
    border-radius: 10px;
    padding: 0.8em;
    margin-bottom: 1em;
}

.photo-manager-section-title {
    margin: 0 0 0.6em;
    font-size: 1em;
}

.photo-manager-toolbar {
    margin-top: 0.6em;
}

.photo-manager-banner-preview {
    display: block;
    max-width: 100%;
    max-height: 120px;
    object-fit: cover;
    border-radius: 6px;
    margin-bottom: 0.6em;
}

.photo-manager-toolbar-group {
    display: flex;
    flex-direction: column;
    gap: 0.35em;
}

.photo-manager-select-row {
    display: flex;
    align-items: center;
    gap: 0.4em;
}

.photo-manager-select {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.4em 0.5em;
    border-radius: 6px;
    border: 1px solid rgba(0, 0, 0, 0.2);
    background: #fff;
    font-size: 0.95em;
}

.photo-manager-divider {
    height: 1px;
    background: rgba(0, 0, 0, 0.1);
    margin: 0.8em 0;
}

/* Small, round icon-only action buttons (used next to selects/inputs where a
   full text .photo-button would be too heavy). */
.icon-button {
    flex: 0 0 auto;
    cursor: pointer;
    border: none;
    border-radius: 6px;
    background: #e0e0e0;
    width: 2.2em;
    height: 2.2em;
    font-size: 1em;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.icon-button:hover {
    background: #cfcfcf;
}

.icon-button:disabled {
    opacity: 0.5;
    cursor: default;
}

.icon-button-primary {
    background: #dd8787;
}

.icon-button-primary:hover {
    background: #f47c20;
}

.icon-button-danger {
    background: #c65b5b;
    color: #fff;
}

.icon-button-danger:hover {
    background: #a83f3f;
}

/* File input disguised as a button + filename label, replacing the raw/ugly
   native file picker (which renders very differently and clunkily across browsers). */
.icon-file-button {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.4em;
    cursor: pointer;
    background: #eee;
    border: 1px dashed rgba(0, 0, 0, 0.3);
    border-radius: 6px;
    padding: 0.4em 0.6em;
    font-size: 0.9em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1 1 auto;
    min-width: 0;
}

.icon-file-button:hover {
    background: #e2e2e2;
}

.icon-file-button-wide {
    width: 100%;
    box-sizing: border-box;
}

.photo-manager-file-input-hidden {
    position: absolute;
    inset: 0;
    opacity: 0;
    cursor: pointer;
    width: 100%;
}

.photo-manager-upload-grid {
    display: flex;
    gap: 0.8em;
    align-items: flex-start;
    flex-wrap: wrap;
}

.photo-manager-upload-controls {
    flex: 1 1 160px;
    min-width: 160px;
    display: flex;
    flex-direction: column;
    gap: 0.5em;
}

.photo-manager-form {
    display: flex;
    flex-direction: column;
    gap: 0.7em;
}

.photo-manager-field {
    display: flex;
    flex-direction: column;
    gap: 0.3em;
}

.photo-manager-label {
    font-size: 0.85em;
    font-weight: bold;
    opacity: 0.85;
}

.photo-manager-slider-label {
    display: flex;
    align-items: center;
    gap: 0.5em;
    font-size: 0.85em;
}

.photo-manager-slider-label input[type="range"] {
    flex: 1 1 auto;
    min-width: 0;
}

.photo-manager-error {
    color: #b32121;
}

.photo-manager-hint {
    font-size: 0.85em;
    opacity: 0.8;
    margin: 0.2em 0 0;
}

.photo-crop-preview {
    width: 100%;
    max-width: 140px;
    aspect-ratio: 1/1;
    overflow: hidden;
    margin: 0;
    border-radius: 6px;
    flex: 0 0 auto;
}

.photo-crop-preview img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.photo-manager-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5em;
}

.photo-manager-name-input {
    width: 100%;
    box-sizing: border-box;
    min-width: 0;
    padding: 0.4em 0.5em;
    border-radius: 6px;
    border: 1px solid rgba(0, 0, 0, 0.2);
}

/* Ad-tile admin grid: 22x22 = the 20x20 playable map (shown as a plain visual
   reference) surrounded by a 1-tile ring of clickable "wall" squares, matching
   the exact layout the game renders (see MapService.MAP_SIZE + emptyChar). */
.ad-grid-wrapper {
    width: 100%;
    max-width: 420px;
    margin: 0.5em auto 0;
    box-sizing: border-box;
}

.ad-grid {
    display: grid;
    grid-template-columns: repeat(22, 1fr);
    grid-gap: 1px;
    width: 100%;
    aspect-ratio: 1/1;
}

.ad-grid-map-tile {
    width: 100%;
    height: 100%;
    aspect-ratio: 1/1;
    object-fit: cover;
    display: block;
}

.ad-grid-map-tile-empty {
    background-color: #d8e8c8;
}

.ad-grid-map-tile-wrapper {
    position: relative;
    aspect-ratio: 1/1;
    width: 100%;
    height: 100%;
}

.ad-grid-player-marker {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.9em;
    line-height: 1;
    text-shadow: 0 0 2px #000, 0 0 2px #000;
    pointer-events: none;
}

.ad-grid-wall {
    aspect-ratio: 1/1;
    background-color: brown;
    cursor: pointer;
    display: block;
}

.ad-grid-wall img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.ad-grid-wall-has-ad {
    outline: 2px solid gold;
    outline-offset: -2px;
}

.ad-grid-wall-selected {
    outline: 3px solid dodgerblue;
    outline-offset: -3px;
}

@media (max-width: 420px) {
    .ad-grid-wrapper {
        max-width: 100%;
    }
}

/* photos button */

.photo-button {
    cursor: pointer;
    background: #dd8787;
    text-align: center;
    padding: .5em 2em .55em;
    font-size: large;
    text-shadow: 0 1px 1px rgba(0,0,0,.3);
    border-radius: 0;
}

.photo-button:hover {
    background: #f47c20;
    background: -webkit-gradient(linear, left top, left bottom, from(#f88e11), to(#f06015));
    background: -moz-linear-gradient(top,  #f88e11,  #f06015);
    filter:  progid:DXImageTransform.Microsoft.gradient(startColorstr='#f88e11', endColorstr='#f06015');
}

.photo-button:active {
    position: relative;
    top: 1px;
}

.photo-button:disabled {
    opacity: 0.55;
    cursor: default;
    filter: none;
}

/* Online/Local mode toggle: a fixed "Online" label + an actual on/off switch knob, so
   it's clear that "on" = Online and "off" = Local, instead of the label text itself
   flipping between "Modo: Online"/"Modo: Local" (which was less immediately readable). */
.game-mode-toggle {
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.35em;
    background: transparent;
    padding: 0;
    box-shadow: none;
}

.game-mode-label {
    font-size: 0.85em;
    font-weight: bold;
}

.game-mode-switch {
    position: relative;
    display: inline-block;
    width: 2.2em;
    height: 1.2em;
    border-radius: 999px;
    background: #9aa5b1;
    transition: background-color 0.2s ease;
    flex: 0 0 auto;
}

.game-mode-switch.on {
    background: #2ecc71;
}

.game-mode-switch-knob {
    position: absolute;
    top: 0.12em;
    left: 0.12em;
    width: 0.96em;
    height: 0.96em;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgba(0,0,0,.3);
    transition: left 0.2s ease;
}

.game-mode-switch.on .game-mode-switch-knob {
    left: 1.12em;
}

.logout-button {
    flex: 0 0 auto;
    font-size: 1.3em;
    padding: 0.3em 0.6em;
    background: #8a8a8a;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: white;
}

.logout-icon {
    width: 1em;
    height: 1em;
    display: block;
}

.logout-button:hover {
    background: #6b6b6b;
}

.menu-bar {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    align-items: stretch;
    /* Setting overflow-x:auto implicitly makes overflow-y auto too, which removes the
       flex item's automatic minimum size protection and lets tall sibling content (e.g. the
       Weather tab) shrink this bar to 0 height in the vertical .main-page-content flex column.
       Prevent that by disallowing shrink and forcing a sane minimum height. */
    flex-shrink: 0;
    min-height: 2.6em;
}

.menu-bar .photo-button {
    flex: 1 1 0;
    min-width: 0;
    padding: .5em 1em .55em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.menu-icon {
    width: 1.4em;
    height: 1.4em;
    vertical-align: middle;
    image-rendering: pixelated;
}

/* Forces the top emoji/icon menu bar to always stay on one line, even on narrow phones with
   several tabs enabled (Jugar/Pronóstico/Mis olas/Tablero/Admin) — shrinks padding/font instead
   of wrapping to a second row, with horizontal scroll as a last-resort fallback if it still can't
   all fit. */
@media (max-width: 480px) {
    .menu-bar .photo-button {
        padding: .35em .5em .4em;
        font-size: 0.85em;
    }

    .menu-bar .menu-icon {
        width: 1.15em;
        height: 1.15em;
    }
}

.google-signin-area {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
}

.photo-button.active {
    background: #b85c5c;
    box-shadow: inset 0 0 0 3px rgba(0,0,0,.25);
}

.player-list {
    width: 100%;
    border-collapse: collapse;
}

.player-list th,
.player-list td {
    border: 1px solid #ccc;
    padding: .4em .6em;
    text-align: left;
}

.player-list td {
    overflow-wrap: break-word;
}

/* An email has no spaces to break on, so it is the one thing allowed to split mid-word;
   without it a single long address stretches its column and pushes the table sideways.
   The guid and the status labels must not break, or they turn into ragged stacks. */
.player-google small {
    overflow-wrap: anywhere;
}

.player-list td[data-label="Registrado"],
.player-list td[data-label="Estado"] {
    white-space: nowrap;
}

.player-no-google {
    color: #999;
}

.player-identity {
    display: flex;
    align-items: center;
    gap: .5em;
}

.player-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 auto;
    background: #ddd;
}

.player-avatar-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: .8em;
    font-weight: 700;
    color: #777;
}

.player-guid {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.player-google {
    display: flex;
    flex-direction: column;
}

/*leaderboard (own layout: a three-field ranking fits on one line at any width, so unlike
  the admin .player-list it never needs to collapse into cards)*/
.leaderboard-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.leaderboard-row {
    display: flex;
    align-items: center;
    gap: .7em;
    padding: .55em .7em;
    border: 1px solid #e0c9a6;
    border-radius: 8px;
    margin-bottom: .4em;
    background: #fff7ec;
}

.leaderboard-rank {
    flex: 0 0 auto;
    min-width: 1.9em;
    height: 1.9em;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    background: #ece2d3;
    color: #4a4033;
    font-weight: 700;
    font-size: .9em;
}

.leaderboard-rank-1 { background: #f4c542; color: #4a3a00; }
.leaderboard-rank-2 { background: #d8d8d8; color: #3d3d3d; }
.leaderboard-rank-3 { background: #d9a06a; color: #40260f; }

/* The name is the only elastic part, so it absorbs every width change and truncates
   rather than pushing the score off the row. */
.leaderboard-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.leaderboard-score {
    flex: 0 0 auto;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* Below this width the five columns cannot coexist, so each row becomes its own card and
   the per-cell data-label stands in for the (now hidden) shared header row. */
@media (max-width: 640px) {
    .player-list,
    .player-list tbody,
    .player-list tr,
    .player-list td {
        display: block;
        width: 100%;
        /* The card's own padding and border must fit inside that 100%, otherwise each row
           ends up wider than the table and spills past the right edge of the screen. */
        box-sizing: border-box;
    }

    .player-list thead {
        display: none;
    }

    .player-list tr {
        border: 1px solid #ccc;
        border-radius: 8px;
        margin-bottom: .6em;
        padding: .1em .7em;
    }

    .player-list td {
        border: none;
        border-bottom: 1px solid #eee;
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: .8em;
        padding: .5em 0;
        text-align: right;
    }

    /* The label keeps its intrinsic width; the value takes the rest and is allowed to
       shrink below its content size, which is what lets long emails wrap instead of
       widening the row. */
    .player-list td > * {
        min-width: 0;
    }

    .player-list td[data-label="Jugador"] .player-identity {
        justify-content: flex-end;
        flex: 1 1 auto;
    }

    .player-list td:last-child {
        border-bottom: none;
    }

    .player-list td::before {
        content: attr(data-label);
        font-weight: 600;
        color: #666;
        text-align: left;
        flex: 0 0 auto;
    }
}

/*blazor errors*/
#blazor-error-ui {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

/*grid*/

.grid-square-wrapper {
    display: grid;
    place-items: center;
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
    overflow: hidden;
}

.grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 1fr);
    grid-gap: 0;
    overflow: hidden;
}

.grid-item {
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
    position: relative;
    /*padding: 10px;*/
    text-align: center;
    display: flex;
    align-items: center;
    justify-content: center;
}
.map-wall {
    background-color: brown;
    width: 100%;
    height: 100%;
}

/* Sólo para jugadores verificados: muestra el nombre elegido debajo del sprite,
   visible para todos en el mundo compartido (ver GameEntity.DisplayName). */
.player-name-tag {
    position: absolute;
    bottom: 2px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 0.55em;
    line-height: 1;
    color: #fff;
    background: rgba(0, 0, 0, 0.55);
    padding: 1px 4px;
    border-radius: 3px;
    white-space: nowrap;
    max-width: 95%;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
}

/*mosca spinner*/

.spinner {
    margin: auto;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100px;
    height: auto;
}

/* Fly "prey" idle animation for matalamosca: a subtle side-to-side buzz/wiggle
   (like a fly hovering in place) instead of a full dizzying spin. */
.spinner-icon {
    animation: fly-buzz 0.6s ease-in-out infinite;
}

/* Wave "prey" idle animation for the puntagruesa scenario: the swell grows and
   shrinks in place (rising/falling) instead of spinning like a fly. */
.wave-pulse-icon {
    animation: wave-pulse 1.4s ease-in-out infinite;
    transform-origin: center bottom;
}

@keyframes wave-pulse {
    0%, 100% {
        transform: scale(0.85);
    }
    50% {
        transform: scale(1.15);
    }
}

/* A goal does not spin or swell - it stands there. What moves is the net, so rock it
   gently as if a breeze were catching it. */
.goal-sway-icon {
    animation: goal-sway 2.6s ease-in-out infinite;
    transform-origin: center bottom;
}

@keyframes goal-sway {
    0%, 100% {
        transform: rotate(-2.5deg) scale(0.97);
    }
    50% {
        transform: rotate(2.5deg) scale(1.03);
    }
}

@keyframes fly-buzz {
    0%, 100% {
        transform: translate(0, 0) rotate(-3deg);
    }
    25% {
        transform: translate(2px, -2px) rotate(3deg);
    }
    50% {
        transform: translate(-2px, 1px) rotate(-2deg);
    }
    75% {
        transform: translate(1px, 2px) rotate(2deg);
    }
}

/* swatter animation */
.something {
    z-index: 1;
    position: absolute;
    top: 15%;
    left: 22%;
    width: 60%;
    height: auto;
    transition: transform 120ms ease-out;
}

/* Dead fly peeking out from behind the swatter when the player stands on top of it,
   so kills stay visible instead of being fully hidden by the player's own tile. */
.captured-quarry-under-player {
    z-index: 0;
    width: 40%;
    top: 40%;
    left: 45%;
}

.animate-UpLeft {
    animation-name: slide-UpLeft;
    animation-duration: 180ms;
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
    animation-fill-mode: forwards;
}

@keyframes slide-UpLeft {
    0% {
        transform: translate(0, 0);
    }
    100% {
        transform: translate(-150%, -150%);
    }
  }
  
.animate-Up {
    animation-name: slide-Up;
    animation-duration: 180ms;
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
    animation-fill-mode: forwards;
}

@keyframes slide-Up {
    0% {
        transform: translateY(0);
    }
    100% {
        transform: translateY(-150%);
    }
}

.animate-UpRight {
    animation-name: slide-UpRight;
    animation-duration: 180ms;
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
    animation-fill-mode: forwards;
}

@keyframes slide-UpRight {
    0% {
        transform: translate(0, 0);
    }
    100% {
        transform: translate(150%, -150%);
    }
}
  
.animate-Left {
    animation-name: slide-Left;
    animation-duration: 180ms;
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
    animation-fill-mode: forwards;
}

@keyframes slide-Left {
    0% {
        transform: translateX(0);
    }
    100% {
        transform: translateX(-150%);
    }
}
.animate-Right {
    animation-name: slide-Right;
    animation-duration: 180ms;
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
    animation-fill-mode: forwards;
}

@keyframes slide-Right {
    0% {
        transform: translateX(0);
    }
    100% {
        transform: translateX(150%);
    }
}
.animate-DownLeft {
    animation-name: slide-DownLeft;
    animation-duration: 180ms;
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
    animation-fill-mode: forwards;
}

@keyframes slide-DownLeft {
    0% {
        transform: translate(0, 0);
    }
    100% {
        transform: translate(-150%, 150%);
    }
}
.animate-Down {
    animation-name: slide-Down;
    animation-duration: 180ms;
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
    animation-fill-mode: forwards;
}

@keyframes slide-Down {
    0% {
        transform: translateY(0);
    }
    100% {
        transform: translateY(150%);
    }
}

.animate-DownRight {
    animation-name: slide-DownRight;
    animation-duration: 180ms;
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
    animation-fill-mode: forwards;
}

@keyframes slide-DownRight {
    0% {
        transform: translate(0, 0);
    }
    100% {
        transform: translate(150%, 150%);
    }
}

/* Tutorial arrows */

.tutorial-overlay {
    z-index: 2;
    position: absolute;
    display: block;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.tutorial-opacity-5 {
    opacity: 0.7;
}

.tutorial-opacity-4 {
    opacity: 0.6;
}

.tutorial-opacity-3 {
    opacity: 0.5;
}

.tutorial-opacity-2 {
    opacity: 0.4;
}

.tutorial-opacity-1 {
    opacity: 0.3;
}

.tutorial-opacity-0 {
    opacity: 0.2;
}

/* Surf videos tab */

.surf-videos-subtabs {
    margin-bottom: 0.6em;
}

.surf-videos-hint {
    margin-bottom: 0.6em;
    opacity: 0.8;
}

/* Storage gauge. Deliberately above the clip grid rather than tucked under it: it is a
   warning that clips get deleted, and a warning below the thing it warns about is not read. */
.surf-videos-storage {
    display: flex;
    flex-direction: column;
    gap: 0.3em;
    margin-bottom: 0.8em;
}

.surf-videos-storage-bar {
    height: 8px;
    border-radius: 999px;
    background: #e8ddcb;
    overflow: hidden;
}

.surf-videos-storage-fill {
    height: 100%;
    background: #5b9c5b;
    transition: width 0.3s ease;
}

.surf-videos-storage-fill-mid {
    background: #d8a33a;
}

.surf-videos-storage-fill-high {
    background: #c1523f;
}

.surf-videos-storage-label {
    font-size: 0.85em;
    opacity: 0.8;
}

.surf-videos-storage-warning {
    font-size: 0.85em;
    color: #a33a2a;
}

.surf-videos-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 1em;
}

.surf-video-card {
    display: flex;
    flex-direction: column;
    gap: 0.4em;
    background: #f4f4f4;
    border-radius: 4px;
    padding: 0.5em;
}

.surf-video-card video {
    width: 100%;
    border-radius: 3px;
    background: #000;
}

.surf-video-meta {
    display: flex;
    justify-content: space-between;
    font-size: 0.85em;
    opacity: 0.75;
}

/* ---------------------------------------------------------------------------
   Admin sprite grant grid: players down the side, sprites across the top.
   Unlike the player list this one deliberately does NOT restack into cards on
   narrow screens - a matrix loses its meaning once the axes are gone. It scrolls
   sideways instead, with both axes pinned so the cell you are checking always
   has a visible row and column header.
   --------------------------------------------------------------------------- */
.player-manager-section {
    margin: 1.4em 0 .6em;
    font-size: 1.05em;
}

.sprite-grid-scroll {
    overflow: auto;
    /* Cap the height so the sticky header has something to stick against; without a
       scroll container of its own, position:sticky on <th> resolves against the page
       and the header scrolls away with it. */
    max-height: 70vh;
    /* Shrink to the table on wide screens so the border traces the grid instead of
       trailing off into empty space, but still cap at the viewport so it scrolls on a phone. */
    width: fit-content;
    max-width: 100%;
    border: 1px solid #ccc;
    border-radius: 8px;
}

.sprite-grid {
    border-collapse: separate;
    border-spacing: 0;
}

.sprite-grid th,
.sprite-grid td {
    border-right: 1px solid #ddd;
    border-bottom: 1px solid #ddd;
    padding: .4em .6em;
    background: #fff;
}

/* The first column pins horizontally, the header row vertically, and the corner cell
   does both - so it needs the higher stacking order of the two to stay on top of each. */
.sprite-grid-player {
    position: sticky;
    left: 0;
    z-index: 2;
    text-align: left;
    font-weight: 500;
    white-space: nowrap;
    box-shadow: 1px 0 0 #ccc;
}

.sprite-grid-head {
    position: sticky;
    top: 0;
    z-index: 2;
    min-width: 72px;
    box-shadow: 0 1px 0 #ccc;
}

.sprite-grid-corner {
    position: sticky;
    left: 0;
    top: 0;
    z-index: 3;
    text-align: left;
    box-shadow: 1px 0 0 #ccc, 0 1px 0 #ccc;
}

.sprite-grid-head img {
    display: block;
    width: 34px;
    height: 34px;
    object-fit: contain;
    margin: 0 auto .2em;
}

.sprite-grid-head span {
    display: block;
    font-size: .72em;
    font-weight: 500;
    line-height: 1.1;
    max-width: 76px;
}

.sprite-grid-player {
    /* The avatar and the name sit side by side; the cell is sticky, so its contents
       cannot use flex on the <th> itself without breaking the pin in some browsers. */
    vertical-align: middle;
}

.sprite-grid-player-name {
    display: inline-block;
    vertical-align: middle;
    margin-left: .4em;
    max-width: 9em;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* .player-avatar-empty is display:flex so its letter centres; inside the sticky first
   column that makes it block-level and drops the name onto its own line. */
.sprite-grid-player .player-avatar {
    display: inline-flex;
    vertical-align: middle;
}

.sprite-grid-cell {
    text-align: center;
}

.sprite-grid-cell input {
    width: 20px;
    height: 20px;
}

/* Scenario defaults sit in the same grid as the grantable extras, but read as state rather than
   as a control: the star is always-on, the dash is not-applicable. Both are non-interactive. */
/* The one "Por defecto" column: tinted so it reads as a fixed fact about the player rather than
   another thing to click, and it sits right next to the sticky name column. */
.sprite-grid-cell-default,
.sprite-grid-head-default {
    background: #fdf6e6;
}

.sprite-grid-cell-default-img {
    width: 28px;
    height: 28px;
    object-fit: contain;
    vertical-align: middle;
}

.sprite-grid-error,
.locker-room-error {
    color: #b03030;
    margin-top: .6em;
}

/* ---------------------------------------------------------------------------
   Locker room: the player's own rack of sprites.
   --------------------------------------------------------------------------- */
.locker-room {
    padding: .8em;
}

.locker-room-rack {
    display: grid;
    /* auto-fill rather than a fixed count: the rack holds anywhere from two sprites to
       the whole catalogue, and should fill the width either way. */
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: .8em;
}

.locker-room-slot {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .3em;
    padding: .7em .4em;
    border: 2px solid #ccc;
    border-radius: 10px;
    background: #fff;
    cursor: pointer;
    font: inherit;
}

.locker-room-slot:disabled {
    opacity: .6;
    cursor: progress;
}

.locker-room-slot.is-selected {
    border-color: #b85c5c;
    box-shadow: 0 0 0 3px rgba(184, 92, 92, .2);
}

.locker-room-slot img {
    width: 64px;
    height: 78px;
    object-fit: contain;
}

.locker-room-name {
    font-size: .85em;
    text-align: center;
    line-height: 1.15;
}

.locker-room-tag {
    font-size: .68em;
    color: #777;
}

.locker-room-empty {
    color: #777;
}

/* The Google avatar is a photo among sprites, so round it off to read as a person
   rather than as another game icon. */
.score-avatar {
    border-radius: 50%;
    object-fit: cover;
}

/* Capture history. Matches the leaderboard's card look, since both are lists a player
   scans top-down; the details block is indented so a capture with extras still reads as
   one row and not as two entries. */
.capture-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.capture-row {
    border: 1px solid #e0c9a6;
    border-radius: 8px;
    margin-bottom: .5em;
    background: #fff7ec;
    padding: .55em .7em;
}

.capture-when {
    display: flex;
    align-items: center;
    gap: .6em;
}

.capture-sprite {
    width: 1.6em;
    height: 1.6em;
    object-fit: contain;
    flex: 0 0 auto;
}

.capture-date {
    font-weight: 600;
    color: #4a4033;
}

.capture-details {
    margin-top: .45em;
    padding-top: .45em;
    padding-left: 2.2em;
    border-top: 1px dashed #e0c9a6;
}

.capture-details-group {
    font-size: .8em;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #8a7a63;
    margin-bottom: .2em;
}

.capture-detail {
    display: flex;
    align-items: baseline;
    gap: .4em;
    font-size: .92em;
}

.capture-detail-icon {
    flex: 0 0 auto;
}

.capture-detail-label {
    color: #6b5d4a;
}

.capture-detail-value {
    font-weight: 600;
    color: #3a3228;
    margin-left: auto;
}

.capture-history-error {
    color: #a33;
}

/* ---- Racha (Components/StreakBadge.razor) --------------------------------- */

.streak {
    display: flex;
    align-items: center;
    gap: 10px;
    max-width: 420px;
    margin: 6px auto;
    padding: 7px 12px;
    border: 1px solid #e2d4a8;
    border-radius: 999px;
    background: #fdf7e3;
    font-size: 0.85rem;
}

/* The one state where saying something changes what the player does, so it is the only
   one that gets a colour. A streak that is already safe needs no urgency. */
.streak.is-at-risk {
    border-color: #e0b78a;
    background: #fdeee0;
}

.streak-flame {
    font-size: 1.3rem;
    line-height: 1;
}

.streak-body {
    display: flex;
    flex-direction: column;
    line-height: 1.25;
    /* min-width:0 lets the flex child shrink instead of forcing the pill wider than the
       screen when the note is long. */
    min-width: 0;
    flex: 1 1 auto;
}

.streak-count {
    font-weight: 600;
}

.streak-best {
    margin-left: 5px;
    padding: 1px 6px;
    border-radius: 999px;
    background: #2f7d32;
    color: #fff;
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    vertical-align: 1px;
}

.streak-note {
    color: #7a6a45;
    font-size: 0.75rem;
}

.streak-dots {
    display: flex;
    gap: 3px;
    flex: 0 0 auto;
}

.streak-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #e4dcc4;
}

.streak-dot.is-on {
    background: #2f7d32;
}

/* Today gets a ring so the strip has a "you are here" - without it a run ending yesterday
   and a run ending today look identical. */
.streak-dot.is-today {
    box-shadow: 0 0 0 2px #fdf7e3, 0 0 0 3px #b9a97e;
}

/* Below ~360px the dots are the first thing worth dropping: the number and the call to
   action carry the whole message on their own. */
@media (max-width: 359px) {
    .streak-dots {
        display: none;
    }
}

/* ---- Diario (Components/Diary.razor) ------------------------------------ */

.diary {
    padding: 0 10px 20px;
}

.diary-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin: 10px 0 18px;
}

.diary-stat {
    flex: 1 1 110px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 10px 8px;
    border: 1px solid #ddd;
    border-radius: 10px;
    background: #fafafa;
    text-align: center;
}

.diary-stat-value {
    font-size: 1.7rem;
    font-weight: 700;
    line-height: 1.1;
}

/* A date is far longer than a count; shrink it so the tiles stay the same height. */
.diary-stat-value-small {
    font-size: 1.1rem;
}

.diary-stat-label {
    font-size: 0.78rem;
    color: #666;
}

.diary-heading {
    margin: 18px 0 6px;
    font-size: 1rem;
}

.diary-days,
.diary-hours {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 110px;
}

.diary-day,
.diary-hour {
    /* Equal flex basis of 0 makes every column the same width regardless of how many
       there are, so the 30-day and 24-hour charts line up with each other. */
    flex: 1 1 0;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
}

.diary-day-bar {
    background: #2f7d32;
    border-radius: 2px 2px 0 0;
    min-height: 0;
}

/* A day with no captures still gets a hairline, so the chart reads as a timeline with
   gaps rather than as a chart that starts wherever the player happened to begin. */
.diary-day-bar.is-empty {
    height: 2px !important;
    background: #e0e0e0;
}

.diary-axis {
    display: flex;
    justify-content: space-between;
    font-size: 0.75rem;
    color: #777;
    margin-top: 3px;
}

.diary-hour-stack {
    position: relative;
    height: 100%;
    display: flex;
    align-items: flex-end;
}

/* The map's shape sits behind the player's as a pale backdrop: same baseline, so the two
   silhouettes can be compared at a glance without a second axis. */
.diary-hour-all {
    position: absolute;
    inset: auto 0 0 0;
    background: #cfe3d0;
    border-radius: 2px 2px 0 0;
}

.diary-hour-mine {
    position: relative;
    width: 100%;
    background: #2f7d32;
    border-radius: 2px 2px 0 0;
}

.diary-hour-label {
    font-size: 0.7rem;
    color: #777;
    text-align: center;
}

/* The streak line at the top of the diary tab: the same fact the badge shows above the
   board, restated where the player came looking for it. */
.diary-streak {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 10px 0 4px;
    padding: 9px 12px;
    border: 1px solid #e2d4a8;
    border-radius: 10px;
    background: #fdf7e3;
}

.diary-streak-at-risk {
    border-color: #e0b78a;
    background: #fdeee0;
}

.diary-streak-none {
    border-color: #ddd;
    background: #fafafa;
}

.diary-streak-icon {
    font-size: 1.5rem;
    line-height: 1;
}

.diary-streak-note {
    display: block;
    color: #7a6a45;
    font-size: 0.78rem;
}

.diary-streak-none .diary-streak-note {
    color: #666;
}

/* ---- Historia (Components/Historia.razor) ------------------------------- */

/* The one screen in the app that is meant to be read rather than played, so it breaks the
   app-wide layout rules on purpose: a measured column instead of full width, and normal
   text sizing instead of the compact game chrome. Everything here is styled by element
   rather than by class, because the markup comes from Markdown (see ScenarioStory) and the
   person writing a story cannot add classes to it. */
.historia {
    /* A flex child's default min-width is its content's intrinsic width, so the widest thing
       inside (the conditions table, which does not wrap) would push the whole app sideways -
       and .main-page-content hides the overflow, so it would be cut off rather than scrollable.
       min-width: 0 is what lets this column actually obey the viewport. */
    width: 100%;
    max-width: 44rem;
    min-width: 0;
    box-sizing: border-box;
    align-self: center;
    margin: 0 auto;
    padding: 6px 16px 48px;
    /* The game font is a monospace; a page of prose in it is punishing to read. */
    font-family: Helvetica, Arial, sans-serif;
    line-height: 1.65;
    color: #2c2c2c;
}

.historia-status {
    padding: 24px 0;
    text-align: center;
    color: #666;
}

.historia h1 {
    margin: 18px 0 4px;
    font-size: 2rem;
    line-height: 1.15;
    letter-spacing: -0.01em;
}

.historia h2 {
    margin: 34px 0 8px;
    font-size: 1.35rem;
    line-height: 1.25;
    padding-top: 14px;
    border-top: 1px solid #e6ded0;
}

.historia h3 {
    margin: 24px 0 6px;
    font-size: 1.1rem;
}

.historia p {
    margin: 0 0 14px;
}

/* The opening paragraph carries the page: slightly larger, so the top of the story reads
   as an introduction without the writer having to mark it up as one. */
.historia h1 + p {
    font-size: 1.1rem;
    color: #3c3c3c;
}

.historia a {
    color: #0e6ba8;
}

.historia ul,
.historia ol {
    margin: 0 0 16px;
    padding-left: 1.4em;
}

.historia li {
    margin-bottom: 6px;
}

.historia blockquote {
    margin: 18px 0;
    padding: 10px 16px;
    border-left: 4px solid #c9a227;
    background: #fdf8ec;
    color: #55492f;
    font-size: 0.95rem;
}

/* Figures go full-bleed inside the column and carry their caption underneath. The image is
   width-constrained rather than height-constrained because the illustrations are all the
   same aspect ratio and a phone in portrait is the narrow case that decides the layout. */
.historia-figure {
    margin: 22px 0;
}

.historia-figure img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 12px;
    background: #f2ede2;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.14);
}

.historia-figure figcaption {
    margin-top: 8px;
    color: #6b6355;
    font-size: 0.85rem;
    line-height: 1.45;
    text-align: center;
}

/* An image that is part of a sentence rather than a figure of its own still must not
   overflow the column. */
.historia p img {
    max-width: 100%;
    height: auto;
}

.historia table {
    width: 100%;
    margin: 8px 0 20px;
    border-collapse: collapse;
    font-size: 0.92rem;
}

.historia th,
.historia td {
    padding: 8px 10px;
    border-bottom: 1px solid #e6ded0;
    text-align: left;
    vertical-align: top;
}

.historia th {
    background: #fbf6ea;
    font-weight: 700;
}

.historia hr {
    margin: 30px 0 18px;
    border: 0;
    border-top: 1px solid #e6ded0;
}

/* The credits/footnote line at the end of every story. */
.historia hr + p {
    color: #857b6a;
    font-size: 0.82rem;
}

.historia code {
    padding: 1px 5px;
    border-radius: 4px;
    background: #f1ece1;
    font-family: 'Global Monospace', monospace;
    font-size: 0.88em;
}

@media (max-width: 480px) {
    .historia {
        padding: 4px 12px 40px;
        line-height: 1.6;
    }

    .historia h1 {
        font-size: 1.6rem;
    }

    .historia h2 {
        font-size: 1.2rem;
        margin-top: 26px;
    }

    /* Wrapping beats sideways scrolling here: the overflow is clipped by an ancestor, so a
       table that is too wide is silently cut off rather than swipeable. A fixed layout with
       a narrow label column keeps the long prose cells readable on a phone. */
    .historia table {
        table-layout: fixed;
        font-size: 0.86rem;
    }

    .historia th:first-child,
    .historia td:first-child {
        width: 34%;
    }

    .historia th,
    .historia td {
        padding: 7px 8px;
        word-break: break-word;
    }
}

/* A cogollo does not move: it ripens on the branch. What you are actually hunting is the frost
   of trichomes on it, so what animates is the light rather than the position - a slow glint
   with the faintest breathing, which at tile size reads as "this one is ready". */
.trichome-shimmer-icon {
    animation: trichome-shimmer 2.2s ease-in-out infinite;
    transform-origin: center bottom;
}

@keyframes trichome-shimmer {
    0%, 100% {
        transform: scale(0.97);
        filter: brightness(1) saturate(1);
    }
    50% {
        transform: scale(1.04);
        filter: brightness(1.2) saturate(1.12);
    }
}
