﻿/* ==========================================================================
   Floskelbingo – mobil-först tema
   ========================================================================== */

/* Ett enda tema, det mörka. Tidigare var det ljusa förval och det mörka låg bakom
   `prefers-color-scheme: dark`, vilket betydde att den palett vi aldrig tittade på var
   den de flesta besökare fick. Den var heller aldrig finslipad, och en palett som ingen
   ser går sönder tyst – precis som Bootstrap-krocken med `.toast` gjorde.
   Hellre ett läge som håller än två där det ena är sämre.

   Följden är att vi struntar i besökarens systeminställning. Det är ett medvetet val
   för ett spel som spelas i korta stunder, inte en läsapp man sitter i en timme.
   Vill vi tillbaka till två lägen är arbetet inte att skriva om det här blocket utan
   att göra en ljus palett som är lika genomarbetad. */
/* Typsnittet ligger hos oss och inte hos Google. Ett <link> till fonts.googleapis.com
   skickar varje besökares IP till tredje part, och den här appen lagrar medvetet varken
   IP eller kaka – se ReferrerTracker. Att spara den principen på servern och sedan läcka
   samma uppgift i en typsnittstagg vore att lura oss själva.

   Variabel vikt 400–800 i en fil: ett anrop i stället för fem, och mellanvikterna finns
   om vi behöver dem. Vietnamesiska delmängden är utelämnad – 77 kB latin plus 13 kB
   latin-ext räcker för svenska och engelska.

   swap: hellre systemtypsnitt i en blink än osynlig text. Rubrikerna är stora och en
   tom sida medan filen laddas är värre än ett kort hopp. */
@font-face {
    font-family: 'Bricolage Grotesque';
    src: url('/fonts/bricolage-grotesque-latin.woff2') format('woff2');
    font-weight: 400 800;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Bricolage Grotesque';
    src: url('/fonts/bricolage-grotesque-latin-ext.woff2') format('woff2');
    font-weight: 400 800;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
    color-scheme: light;

    --bg: #fbf7f0;
    --surface: #ffffff;
    --surface-2: #fbf7f0;
    --text: #1c1917;
    --text-muted: #57534e;
    --border: #e0d8ca;
    /* Den hårda konturen är riktningens signatur. Egen token och inte --border,
       eftersom --border används på ett hundratal ställen där en svart linje vore
       skrikig – avdelare inne i paneler ska vara mjuka. */
    --border-strong: #1c1917;
    --primary: #e8590c;
    --primary-strong: #c2410c;
    --primary-contrast: #fff7ed;
    --accent: #facc15;
    --hit: #facc15;
    --hit-soft: #fef9c3;
    /* Grönt för "pågår" och giltiga fält. Träffärgen är gul och duger inte till
       statustext: gult på blekgult går inte att läsa. */
    --ok: #15803d;
    --ok-soft: #dcfce7;
    --warn: #d97706;
    --danger: #b91c1c;
    --danger-soft: #fee2e2;
    --shadow: 0 6px 20px rgba(28, 25, 23, .10);
    /* Riktningens signatur: skuggan är förskjuten och skarp, inte suddig. Egen token
       eftersom --shadow används på menyer och overlays där den vore fel. */
    --shadow-hard: 5px 5px 0 var(--border-strong);
    --radius: 16px;
    --radius-sm: 10px;
}

* { box-sizing: border-box; }

html, body {
    font-family: 'Bricolage Grotesque', 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    background: var(--bg);
    color: var(--text);
    margin: 0;
    -webkit-text-size-adjust: 100%;
}

a, .btn-link { color: var(--primary); }

h1, h2, h3 { line-height: 1.08; letter-spacing: -.025em; font-weight: 800; }

/* ---- App skal ---- */
.app-header {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    padding: .6rem .9rem;
    background: var(--surface);
    color: var(--text);
    border-bottom: 2px solid var(--border-strong);
}

/* Sidhuvudet är klistrat, så ett ankare som hoppar till ett element lägger målet UNDER
   huvudet – man landar en bit ned i panelen och missar rubriken. scroll-margin-top
   stoppar rullningen ovanför i stället.

   Satt på allt med id och inte bara på skapa-panelen: samma sak gäller varje framtida
   ankarlänk, och dessutom fält som rullas fram av validering. Ett fel som annars
   återuppstår varje gång någon lägger till ett id.

   Värdet är sidhuvudets uppmätta 56 px plus luft. Ändras huvudets höjd ska den här
   följa med – det finns ingen väg att härleda den i CSS. */
[id] { scroll-margin-top: 4.5rem; }

.app-brand {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    font-weight: 800;
    font-size: 1.15rem;
    color: var(--text);
    text-decoration: none;
    letter-spacing: -0.02em;
}

.app-brand .logo { font-size: 1.3rem; }

.app-nav {
    display: flex;
    align-items: center;
    gap: .25rem;
}

.app-nav a, .app-nav button {
    color: var(--text);
    background: transparent;
    border: 0;
    font: inherit;
    padding: .4rem .55rem;
    border-radius: var(--radius-sm);
    text-decoration: none;
    opacity: .92;
    cursor: pointer;
}

.app-nav a:hover, .app-nav button:hover { background: rgba(28,25,23,.07); opacity: 1; }
.app-nav a.active { background: rgba(28,25,23,.10); }

/* Globen och hamburgaren hålls ihop till höger; brödsmulan tar vänstersidan. */
.app-header-tools { display: flex; align-items: center; gap: .15rem; }

/* ---- Språkingång ---- */
.app-lang { position: relative; }

.app-lang > summary {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    /* Samma vertikala mått som hamburgaren, så de står i linje. */
    padding: .35rem .5rem;
    border-radius: var(--radius-sm);
    color: var(--text);
    opacity: .92;
    -webkit-user-select: none;
    user-select: none;
}
.app-lang > summary::-webkit-details-marker { display: none; }
.app-lang > summary:hover { background: rgba(28,25,23,.07); opacity: 1; }
.app-lang-code { font-weight: 800; font-size: .85rem; letter-spacing: .04em; }

/* Panelen ovanpå innehållet, som menyns – sidan ska inte hoppa när den öppnas. */
.app-lang[open] > .app-lang-panel {
    position: absolute;
    right: 0;
    top: calc(100% + .5rem);
    min-width: 9rem;
    padding: .4rem;
    background: var(--primary-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
}

/* Listan ärver avdelarlinjen från när den låg sist i hamburgarmenyn. Här är den
   panelens enda innehåll och behöver varken linje eller luft ovanför. */
.app-lang .lang-list { margin-top: 0; padding-top: 0; border-top: 0; }

/* ---- Meny ---- */
.app-menu { position: relative; }

.app-menu > summary {
    list-style: none;
    cursor: pointer;
    font-size: 1.3rem;
    line-height: 1;
    padding: .35rem .55rem;
    border-radius: var(--radius-sm);
    color: var(--text);
    opacity: .92;
    -webkit-user-select: none;
    user-select: none;
}

.app-menu > summary::-webkit-details-marker { display: none; }
.app-menu > summary:hover { background: rgba(28,25,23,.07); opacity: 1; }

/* Panelen läggs ovanpå innehållet i stället för att knuffa ned det, så att brickan
   inte hoppar när menyn öppnas mitt i ett spel. */
.app-menu[open] > .app-nav {
    position: absolute;
    right: 0;
    top: calc(100% + .5rem);
    flex-direction: column;
    align-items: stretch;
    gap: .1rem;
    min-width: 12rem;
    padding: .4rem;
    background: var(--primary-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
}

.app-menu[open] > .app-nav a,
.app-menu[open] > .app-nav button,
.app-menu[open] > .app-nav form { text-align: left; width: 100%; }

/* ---- Språk ---- */
.lang-list {
    display: flex;
    flex-direction: column;
    gap: .1rem;
    margin-top: .25rem;
    padding-top: .35rem;
    border-top: 1px solid var(--border);
}

.lang-list a {
    color: var(--text);
    text-decoration: none;
    padding: .4rem .55rem;
    border-radius: var(--radius-sm);
    font-size: .9rem;
    opacity: .85;
}

.lang-list a:hover { background: rgba(28,25,23,.07); opacity: 1; }
.lang-list a.active { opacity: 1; font-weight: 700; background: rgba(28,25,23,.06); }

.app-main {
    max-width: 640px;
    margin: 0 auto;
    padding: 1rem .9rem 4rem;
}

/* Adminsidan är den enda som är en datavy och inte en spelvy. 640 px är rätt för en
   bingobricka och för brödtext, men tvingar besökstabellernas sju kolumner till
   sidledsrullning på en skärm som har gott om plats.

   Känns igen på .admin-head i stället för på en markör-div: elementet finns bara där,
   och bara i den inloggade grenen - den som inte är admin får "Sidan finns inte" i
   vanlig bredd, vilket är precis vad en okänd adress ska se ut som. Byter klassen namn
   ska den här raden med. */
.app-main:has(.admin-head) { max-width: 1200px; }

/* Tabellerna vill ha bredden, löptexten vill inte ha den: en förklarande fotnot över
   hela 1200 px blir en rad man tappar bort sig på. Måttet gäller bara adminsidan,
   eftersom det är där de två sorternas innehåll står i samma spalt. */
.app-main:has(.admin-head) .card-panel > p { max-width: 68ch; }

/* Tabellerna är width:100% i grunden, vilket är rätt på 640 px men fel här: sju korta
   sifferkolumner utspridda över 1200 px lägger en decimeter mellan etiketten och sin
   egen siffra. max-content låter dem hugga innehållet och lämna luften till höger,
   där den inte är i vägen. max-width behålls så att overflow-rullningen ovan finns
   kvar som fallback när innehållet ändå blir för brett. */
.app-main:has(.admin-head) .card-panel > .admin-table { width: max-content; max-width: 100%; }

/* ---- Hjältesektion på startsidan ---- */

/* Telefon: brickan i full bredd *under* brödtexten. Den ligger före texten i DOM
   eftersom desktopvarianten är en float, och en float kan bara omflyta det som
   kommer efter den – därför flyttas den nedåt med order i stället. */
.hero { display: flex; flex-direction: column; }

.hero-board {
    position: relative;
    order: 1;
    width: 100%;
    margin: .9rem 0 .2rem;
}

.hero-board .board { margin: 0; }
/* Rutorna är knappar sedan brickan blev spelbar. En knapp ärver varken typsnitt eller
   textjustering, och webbläsaren lägger dessutom på egen padding. */
.hero-board .cell { font: inherit; font-weight: 600; text-align: center; width: 100%; }
/* Den fria rutan går inte att trycka på, men ska inte se avstängd ut - den är ju redan
   vunnen. Bara pekaren avslöjar skillnaden. */
.hero-board .cell:disabled { opacity: 1; cursor: default; }

.hero-bingo {
    position: absolute;
    right: -0.3rem;
    bottom: -0.7rem;
    transform: rotate(-8deg);
    /* Primärfärgen och inte accenten: accenten ÄR träffärgen, så märket låg gult mot
       gula rutor och försvann i dem. Orange är dessutom samma färg som uppmaningen
       under brickan, vilket binder ihop ögonblicket med vad man ska göra åt det. */
    background: var(--primary);
    color: var(--primary-contrast);
    border: 2px solid var(--border-strong);
    font-weight: 900;
    font-size: .85rem;
    letter-spacing: .04em;
    padding: .25rem .65rem;
    border-radius: 999px;
    box-shadow: var(--shadow-hard);
}

/* Från 40rem finns plats att låta texten löpa runt en mindre bricka till höger. */
@media (min-width: 40rem) {
    /* flow-root så att kortet växer runt floaten. Inte overflow:hidden – det hade
       klippt BINGO-märket, som sticker ut utanför brickans hörn. */
    .hero { display: flow-root; }

    .hero-board {
        float: right;
        width: 15rem;
        margin: .2rem .5rem .6rem 1.4rem;
    }

    /* Lätt lutning så att den läses som en illustration och inte som något att trycka
       på. Bara här – i full bredd på telefon skulle rotationen ge vågrät spillning. */
    .hero-board .board { transform: rotate(-3deg); }

    /* Mindre rutor kräver mindre text. Teckenräkningen i DemoCard är ett trubbigt
       mått – "Bandbredd" och "Synergier" är lika långa men olika breda – så
       marginalen tas här. */
    .hero-board .cell { font-size: .78rem; }
}

/* ---- Kort ---- */
.card-panel {
    background: var(--surface);
    border: 2px solid var(--border-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-hard);
    padding: 1.1rem;
    margin-bottom: 1rem;
}

.card-panel h2 { margin: 0 0 .75rem; font-size: 1.2rem; }

/* Rubriker och stycken inne i en panel bär sitt eget utseende. Måtten satt tidigare
   som inline-style på varje enskilt element – 59 stycken – vilket betydde att en
   omtemning krävde att man redigerade markup. Nu säger razor VAD något är, och den
   här filen hur det ser ut.

   Måtten är de som faktiskt användes; skillnader på en tiondels rem mellan annars
   identiska rubriker är utjämnade. */
.card-panel h1 { margin: .2rem 0 .55rem; font-size: 1.5rem; }
.card-panel h3 { margin: 0 0 .5rem; font-size: 1.05rem; }

/* Rubrik som står i en flexrad bredvid något annat – knapp eller statusetikett –
   och därför inte ska bära egen luft. */
.card-panel h1.flush, .card-panel h2.flush { margin: 0; }

/* Mindre rubrik: felsidor och tomma tillstånd, där rubriken inte är sidans ärende. */
.card-panel h1.sm { font-size: 1.3rem; }

/* Underrubrik som inleder ett nytt stycke i en panel som redan har innehåll. */
.card-panel h3.spaced { margin-top: 1.2rem; }

/* Stycken i en panel: luft mellan, ingen luft mot rubriken ovanför eller kanten under.
   Det här ensamt ersätter ett tjugotal inline-marginaler. */
.card-panel > p { margin: 0 0 .75rem; }
.card-panel > p:last-child { margin-bottom: 0; }

/* Fotnot under panelens innehåll: mindre och dämpad. Flera i rad står tätare. */
.panel-note { margin: .6rem 0 0; font-size: .85rem; }
.panel-note + .panel-note { margin-top: .4rem; }

/* Stycke som behöver luft ovanför trots att det följer på något annat. */
.panel-spaced { margin-top: .6rem; }

/* Felmeddelande som står direkt under sitt fält och inte ska knuffas ned. */
.validation-message.flush { margin-top: 0; }

/* Sifferraden i admin när en tabell följer under. */
.admin-figures.spaced { margin-bottom: .8rem; }
.admin-figures.spaced-top { margin-top: .8rem; }

/* Rad med rubrik plus något till höger, när nästa block följer tätt. */
.row-split.spaced { margin-bottom: .6rem; }

/* Växel med etikett bredvid: kryssrutan och texten på samma rad. */
.menu-toggle.inline { display: flex; gap: .5rem; align-items: center; }

/* Formulär som ska stå på raden i stället för att bryta den. */
.form-inline { display: inline-block; }

/* Blockknapp med luft under, när nästa block följer direkt. De två förekomsterna
   hade 1rem respektive .6rem; skillnaden var inte avsedd och är utjämnad. */
.btn-block.spaced { margin-bottom: 1rem; }
.muted { color: var(--text-muted); }
.text-center { text-align: center; }

/* ---- Knappar ---- */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    font: inherit;
    font-weight: 700;
    border: 2px solid transparent;
    border-radius: var(--radius-sm);
    padding: .65rem 1.05rem;
    cursor: pointer;
    min-height: 46px;
    transition: transform .05s ease, background .15s ease;
}
.btn:active { transform: translateY(1px); }

/* Fokusringen är vår, inte Bootstraps. Deras är en blå box-shadow som är mer specifik
   än vår ram, och den lyste blå mitt i en orange knapp – samma sorts krock som
   .btn-primary:disabled hade. Ringen ritas utanför elementet så att den syns även mot
   den hårda konturen.

   :focus-visible och inte :focus – en ring efter ett musklick är brus, en ring efter
   Tab är navigation. */
:focus-visible {
    outline: 3px solid var(--primary);
    outline-offset: 2px;
}
.btn:focus, .btn:focus-visible, .form-control:focus, .form-select:focus { box-shadow: none; }
.btn-block { display: flex; width: 100%; }
.btn-lg { min-height: 54px; font-size: 1.05rem; }

.btn-primary {
    background: var(--primary);
    color: var(--primary-contrast);
    border-color: var(--border-strong);
}
.btn-primary:hover { background: var(--primary-strong); }
.btn-secondary { background: var(--surface-2); color: var(--text); border-color: var(--border-strong); }
.btn-secondary:hover { background: var(--border); }
.btn-danger { background: var(--danger); color: #fff; }
.btn-ghost { background: transparent; color: var(--primary); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
/* Bootstrap har egna färger för `.btn-primary:disabled` och de är mer specifika än vår
   `.btn-primary`. Utan raderna här blev en inaktiv knapp Bootstrap-blå mitt i ett lila
   gränssnitt. Dämpningen ska ligga i opaciteten ovanför, inte i en annan färg. */
.btn-primary:disabled { background: var(--primary); color: var(--primary-contrast); border-color: var(--border-strong); }
.btn-secondary:disabled { background: var(--surface-2); color: var(--text); border-color: var(--border-strong); }
.btn-danger:disabled { background: var(--danger); color: #fff; }

/* ---- Formulär ---- */
.field { margin-bottom: .9rem; }
.field label { display: block; font-weight: 600; margin-bottom: .35rem; font-size: .95rem; }
.input, input.form-control, select.form-select {
    border-width: 2px;
    width: 100%;
    font: inherit;
    padding: .7rem .8rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text);
    min-height: 46px;
}
/* FocusOnNavigate i Routes.razor flyttar fokus till sidans h1 efter navigering, så
   att skärmläsare landar överst i det nya innehållet i stället för kvar i menyn.
   Bieffekten är en synlig fokusring runt rubriken vid varje sidladdning – tydligast
   i mörkt tema, där webbläsarens ring blir nästan vit.

   Att dölja just den ringen kostar ingenting: elementet får tabindex="-1" och går
   därför inte att nå med tangentbord, så ingen kan fokusera det avsiktligt.
   Uppläsningen för skärmläsare påverkas inte. Fokusringar i övrigt lämnas orörda. */
[tabindex="-1"]:focus,
[tabindex="-1"]:focus-visible {
    outline: none;
}

.input:focus, .form-control:focus, .form-select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 25%, transparent);
}

.code-input {
    text-align: center;
    letter-spacing: .4em;
    font-size: 1.6rem;
    font-weight: 800;
}

/* ---- Kod & delning ---- */
.share-code {
    font-size: 2.6rem;
    font-weight: 900;
    letter-spacing: .18em;
    color: var(--primary);
    text-align: center;
    margin: .2rem 0 .4rem;
}
.qr { display: block; margin: .5rem auto; width: 190px; max-width: 60%; height: auto; background:#fff; padding:.4rem; border-radius: var(--radius-sm); }

/* ---- Chips (floskelval) ---- */
.chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.chip {
    border-width: 2px;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .5rem .75rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text);
    font-size: .9rem;
    cursor: pointer;
    min-height: 40px;
}
.chip:hover { border-color: var(--primary); }
.chip.selected { background: var(--primary); color: var(--primary-contrast); border-color: var(--primary-strong); }
.chip .x { font-weight: 800; opacity: .8; }

/* ---- Bingobricka ---- */
.board {
    display: grid;
    gap: .5rem;
    margin: .3rem 0;
}
.cell {
    aspect-ratio: 1 / 1;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: .3rem;
    font-size: .72rem;
    font-weight: 600;
    line-height: 1.1;
    border-radius: var(--radius-sm);
    border: 2px solid var(--border-strong);
    background: var(--surface-2);
    color: var(--text);
    cursor: pointer;
    overflow: hidden;
    /* Avstavning efter språkets regler i stället för brytning var som helst.
       word-break: break-word gav "efterhandsko|nstruktion"; hyphens: auto ger
       "efterhands-|konstruktion". Kräver ett korrekt lang på elementet – brickorna
       sätter spelets språk, inte gränssnittets, eftersom flosklerna hör till spelet.
       overflow-wrap står kvar som sista utväg för ord som är längre än rutan. */
    hyphens: auto;
    /* Fördela raderna jämnt i stället för att fylla dem girigt. Utan detta tas
       "Var ska peng-arna tas" eftersom "peng-" råkar rymmas på första raden; med
       balans föredras den rena brytningen vid mellanslag. Gjord för korta stycken,
       vilket en brickruta är. */
    text-wrap: balance;
    /* Minst 6 tecken i ordet, minst 3 före och 3 efter brytningen. Stoppar fragment
       som "En efter-" som säger mindre än de kostar i läsbarhet. */
    hyphenate-limit-chars: 6 3 3;
    overflow-wrap: break-word;
    user-select: none;
    transition: background .12s ease, transform .06s ease;
}
.cell:active { transform: scale(.97); }
.cell.hit {
    background: var(--hit);
    color: var(--text);
    border-color: var(--border-strong);
}
.cell.free {
    background: repeating-linear-gradient(45deg, var(--hit), var(--hit) 6px, #d9a406 6px, #d9a406 12px);
    color: var(--text);
    cursor: default;
}
.cell.protested { box-shadow: inset 0 0 0 2px var(--warn); }
.cell .badge-protest {
    position: absolute;
    margin: -2.2rem 0 0 2.4rem;
    background: var(--warn);
    color: #3a2a00;
    border-radius: 999px;
    font-size: .62rem;
    padding: 0 .35rem;
    font-weight: 800;
}
.board.size-3 { grid-template-columns: repeat(3, 1fr); }
.board.size-3 .cell { font-size: .85rem; }
.board.size-4 { grid-template-columns: repeat(4, 1fr); }
.board.size-5 { grid-template-columns: repeat(5, 1fr); }
.board.size-5 .cell { font-size: .62rem; }

/* ---- Spelarlista ---- */
.player-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: .55rem .2rem;
    border-bottom: 1px solid var(--border);
}
.player-row:last-child { border-bottom: 0; }
.player-name { font-weight: 600; }
/* Mellanrummen läggs här, inte i markup: Razor trimmar blanksteg runt kodblock, så
   "@p.Name @if (...)" renderas utan mellanslag. */
.player-you { margin-left: .35rem; }
.host-mark { font-size: .85em; opacity: .85; margin-left: .35rem; }
.pill {
    font-size: .72rem;
    font-weight: 800;
    padding: .15rem .5rem;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-muted);
    border: 2px solid var(--border);
}
.pill.bingo { background: var(--accent); color: var(--text); border: 2px solid var(--border-strong); }
.pill.ready { background: var(--ok-soft); color: var(--ok); border-color: var(--ok); }

/* Förhandsvisning i brickbyggaren. Tomma rutor markeras streckade så att det syns hur
   många som återstår, utan att de ser ut att gå att trycka på. */
.builder-preview { margin: .1rem 0 1rem; }
.builder-preview .cell { cursor: default; }
.builder-preview .cell:active { transform: none; }
.builder-preview .cell { position: relative; }

.cell-remove {
    position: absolute;
    top: 0;
    right: 0;
    /* 1.75rem ≈ 28 px. Fortfarande under de 44 px som brukar rekommenderas för
       tumnavigering, men rutan är bara ~55 px bred på en 5×5-bricka och ett kryss
       som täcker halva rutan gör flosklen oläslig. Kompromissen är medveten. */
    width: 1.75rem;
    height: 1.75rem;
    line-height: 1;
    padding: 0;
    border: 0;
    border-radius: 0 var(--radius-sm) 0 var(--radius-sm);
    background: var(--surface);
    color: var(--text-muted);
    font-size: 1.1rem;
    font-weight: 700;
    cursor: pointer;
}

/* Hovern gäller bara pekdon som faktiskt kan hovra. Utan mediefrågan blir regeln
   klibbig på touch: webbläsaren låter :hover sitta kvar efter ett tryck, så krysset
   stannar rött tills man rör något annat. */
@media (hover: hover) {
    .cell-remove:hover { background: var(--danger); color: #fff; }
}

/* Återkoppling som fungerar på touch, där hover inte finns. */
.cell-remove:active { background: var(--danger); color: #fff; transform: scale(.92); }

/* På 5×5 är rutorna för små för det stora krysset. */
.board.size-5 .cell-remove { width: 1.4rem; height: 1.4rem; font-size: .95rem; }

/* Tom ruta ska synas. Tidigare var bakgrunden transparent och hela rutan låg på
   opacity .55 — mot panelens färg blev resultatet osynligt i mörkt tema, och brickan
   såg ut som en tom yta i stället för ett rutnät man ska fylla.

   Nu: egen bakgrund, streckad kant i en dämpad variant av accentfärgen, och full
   opacitet. Dämpningen ligger i färgvalen i stället för i opacity, som annars drar
   ned kanten och texten lika mycket som fyllningen. */
.builder-preview .cell.empty {
    border-style: dashed;
    border-color: color-mix(in srgb, var(--primary) 40%, var(--border));
    background: color-mix(in srgb, var(--surface-2) 60%, transparent);
    color: var(--text-muted);
    font-weight: 500;
    /* Mindre än flosklerna: platshållaren ska läsas som frånvaro, inte som innehåll. */
    font-size: .62rem;
}

/* ---- Flöde ---- */
.feed-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .6rem;
    padding: .6rem 0;
    border-bottom: 1px solid var(--border);
}
.feed-item:last-child { border-bottom: 0; }
.feed-text { font-weight: 600; }
.feed-meta { font-size: .8rem; color: var(--text-muted); }
.feed-item.disputed .feed-text { text-decoration: line-through solid var(--warn); }
.protest-count { color: var(--warn); font-weight: 700; }

.btn-protest {
    padding: .4rem .7rem;
    min-height: 38px;
    border-radius: 999px;
    font-size: .85rem;
    background: var(--danger-soft);
    color: var(--danger);
    border: 1px solid var(--danger);
    white-space: nowrap;
}
.btn-protest.active { background: var(--danger); color: #fff; }

/* ---- Status-taggar ---- */
.status-tag { font-size: .72rem; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; padding: .2rem .55rem; border-radius: 999px; border: 2px solid var(--border-strong); }
.status-tag.lobby { background: #fef3c7; color: #92400e; }
.status-tag.active { background: var(--ok-soft); color: var(--ok); }
.status-tag.finished { background: var(--surface-2); color: var(--text-muted); }

/* ---- Bingo-fest ---- */
.bingo-banner {
    text-align: center;
    background: linear-gradient(135deg, var(--accent), var(--primary));
    color: var(--text);
    border-radius: var(--radius);
    padding: 1.2rem;
    margin-bottom: 1rem;
    animation: pop .4s ease;
}
.bingo-banner .big { font-size: 2rem; font-weight: 900; letter-spacing: .1em; }
@keyframes pop { from { transform: scale(.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }

.toolbar { display: flex; gap: .5rem; flex-wrap: wrap; }
.toolbar .btn { flex: 1; }
.row-split { display: flex; gap: .6rem; align-items: center; }
.grow { flex: 1; }

/* ==========================================================================
   Bevarade fel- och valideringsstilar
   ========================================================================== */
.valid.modified:not([type=checkbox]) { outline: 1px solid var(--ok); }
.invalid { outline: 1px solid var(--danger); }
.validation-message { color: var(--danger); font-size: .85rem; }

#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    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;
    border-radius: var(--radius-sm);
}
.blazor-error-boundary::after { content: "Ett fel har inträffat."; }

/* Om-sidan och FAQ ------------------------------------------------------- */

.steps { margin: 0; padding-left: 1.2rem; color: var(--text-muted); }
.steps li { margin: .35rem 0; }

/* Frågorna är <details>. Markören döljs och ersätts med ett eget tecken som
   roterar, eftersom webbläsarnas egna trianglar skiljer sig åt i både storlek
   och placering. */
.faq-item { border-bottom: 1px solid var(--border); }
.faq-item:last-child { border-bottom: none; }

.faq-item > summary {
    cursor: pointer;
    list-style: none;
    padding: .7rem 1.6rem .7rem 0;
    font-weight: 600;
    position: relative;
}
.faq-item > summary::-webkit-details-marker { display: none; }

.faq-item > summary::after {
    content: "▸";
    position: absolute;
    right: .25rem;
    color: var(--primary);
    transition: transform .15s ease;
}
.faq-item[open] > summary::after { transform: rotate(90deg); }

.faq-item > p {
    margin: 0 0 .9rem;
    color: var(--text-muted);
}

/* Sidfot ----------------------------------------------------------------- */

/* Om- och FAQ-sidorna måste nås från varje sida, inte bara från startsidan:
   en sida ingen länkar till hittas varken av besökare eller sökmotorer. */
.app-footer {
    margin: 1.5rem 0 .5rem;
    text-align: center;
    font-size: .85rem;
    color: var(--text-muted);
}
.app-footer a {
    color: var(--text-muted);
    text-decoration: none;
    padding: 0 .5rem;
}
.app-footer a:hover { color: var(--primary); text-decoration: underline; }

/* Admin ------------------------------------------------------------------ */

.admin-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }

.admin-figures { display: flex; gap: 2rem; flex-wrap: wrap; }
.admin-figures .figure {
    display: block;
    font-size: 2rem;
    font-weight: 700;
    line-height: 1.1;
    color: var(--primary);
}
.admin-figures .label { font-size: .8rem; color: var(--text-muted); }

.admin-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.admin-table th, .admin-table td { padding: .35rem .5rem; text-align: left; }
.admin-table thead th { border-bottom: 1px solid var(--border); font-size: .8rem; color: var(--text-muted); }
.admin-table tbody tr + tr th, .admin-table tbody tr + tr td { border-top: 1px solid var(--border); }
.admin-table tbody th { font-weight: 600; }

/* Sifferkolumner högerställs så att storleksordningen syns utan att läsas: i en
   kolumn med 4 och 1207 hamnar annars tusentalet och ensiffran under varandra. */
.admin-table th.num, .admin-table td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Tabellerna är bredare än en telefon. Låt dem rulla i sig själva i stället för att
   knuffa hela sidan i sidled. */
.card-panel > .admin-table { display: block; overflow-x: auto; white-space: nowrap; }

/* Undantaget från nowrap ovan. En User-Agent är upp till 400 tecken, och på en rad blir
   tabellen bred i tusental oavsett hur bred sidan är - rullningen skulle bli värre av
   att sidan växte, inte bättre. Enda kolumnen på hela sidan som inte är ett kort värde. */
.admin-agent { white-space: normal; overflow-wrap: anywhere; max-width: 62ch; }

/* Datumspannet. Snabbvalen är länkar och inte knappar - de leder till en annan
   adress - men ska läsas som en knapprad, därav utseendet. */
.admin-period { display: flex; flex-wrap: wrap; gap: 1rem 1.5rem; align-items: center; margin-top: .8rem; }
.admin-period-presets { display: flex; flex-wrap: wrap; gap: .4rem; }
.admin-period-presets a {
    padding: .3rem .7rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: .85rem;
    text-decoration: none;
}
.admin-period-presets a:hover { border-color: var(--text-muted); }
.admin-period-presets a.vald { border-color: currentColor; font-weight: 700; }
.admin-period-custom { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; font-size: .85rem; }
.admin-period-custom label { display: flex; gap: .35rem; align-items: center; }
.admin-period-custom input { padding: .25rem .4rem; font: inherit; }

/* Blänkare till vänsajt. Avskild med en linje i stället för bara luft – den hör inte
   till säljtexten ovanför, och utan markering läses den som en fortsättning på den. */
.partner-note {
    margin: .9rem 0 0;
    padding-top: .7rem;
    border-top: 1px solid var(--border);
    font-size: .88rem;
}
.partner-note a { color: var(--primary); text-decoration: none; white-space: nowrap; }
.partner-note a:hover { text-decoration: underline; }

/* Korsreklam efter avslutad omgång. Får ta plats – till skillnad från blänkaren på
   startsidan visas den bara när spelet är slut och besökaren inte har något annat
   för sig. Accentfärgen skiljer den från spelets egna paneler så att den läses som
   ett erbjudande och inte som en del av resultatet. */
.cross-promo {
    border: 1px solid var(--accent);
    background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 8%, transparent), transparent);
}
.cross-promo h2 { margin-top: 0; }
.cross-promo p { color: var(--text-muted); }

.btn-accent {
    background: var(--accent);
    color: var(--text);
    border-color: var(--border-strong);
}
.btn-accent:hover { filter: brightness(1.04); color: var(--text); }

/* Ändrar bricka: varken klar eller obörjad. Varnande gult snarare än grönt, eftersom
   det är ett läge värden behöver notera innan hen startar spelet. */
.pill.editing {
    background: color-mix(in srgb, var(--warn) 18%, transparent);
    color: var(--warn);
    border-color: color-mix(in srgb, var(--warn) 40%, transparent);
}

/* Introrad för den som redan är inloggad. Kompakt med flit – formuläret under ska
   fortfarande synas utan att man scrollar på telefon. */
.welcome-strip { padding: .8rem 1.1rem; }
.welcome-strip h1 { margin: 0 0 .3rem; font-size: 1.25rem; }
.welcome-strip p { margin: 0; font-size: .9rem; }
.welcome-strip a { color: var(--primary); text-decoration: none; white-space: nowrap; }
.welcome-strip a:hover { text-decoration: underline; }

/* Pågående spel på startsidan. Hela raden är länken – ett litet mål bredvid en stor
   yta som ser klickbar ut är den sortens sak man missar på telefon. */
.ongoing h2 { margin-top: 0; }
.ongoing-row {
    display: flex;
    align-items: center;
    gap: .8rem;
    padding: .6rem 0;
    text-decoration: none;
    color: inherit;
}
.ongoing-row + .ongoing-row { border-top: 1px solid var(--border); }
.ongoing-row strong { display: block; }
.ongoing-row .muted { font-size: .85rem; }
.ongoing-row:hover strong { color: var(--primary); }
/* Knappen är en span inuti länken – den ska se ut som en knapp men inte fånga klicket
   för sig, eftersom hela raden redan leder rätt. */
.ongoing-row .btn { pointer-events: none; white-space: nowrap; }

/* Reglage i menyn. Hela raden är klickbar via label, så träffytan blir densamma
   som för menyns länkar i stället för en liten kryssruta. */
.menu-toggle {
    display: flex;
    align-items: center;
    gap: .55rem;
    padding: .55rem .2rem;
    cursor: pointer;
    user-select: none;
}
.menu-toggle input { width: 1rem; height: 1rem; accent-color: var(--primary); cursor: pointer; }

/* Aviseringar --------------------------------------------------------------
   Ligger fast längst ner så att blicken kan stanna på brickan. Kolumnen är
   omvänd: nyaste hamnar närmast underkanten, där tummen är.

   HETER INTE .toast MED FLIT. Bootstrap har en egen toast-komponent och döljer
   den med `.toast:not(.show){display:none}`. Den regeln har högre specificitet
   än en enkel klassväljare, så den vinner även om app.css laddas efteråt —
   aviseringen fanns i DOM:en, hade rätt färger, och var ändå osynlig. */
.hit-toasts {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: max(.8rem, env(safe-area-inset-bottom));
    z-index: 50;
    display: flex;
    flex-direction: column-reverse;
    gap: .5rem;
    width: min(28rem, calc(100vw - 1.6rem));
    pointer-events: none;
}

.hit-toast {
    pointer-events: auto;
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .6rem .7rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--primary);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
    animation: hit-toast-in .22s ease-out;
}
.hit-toast.leaving { animation: hit-toast-out .25s ease-in forwards; }

.hit-toast-text { font-weight: 600; font-size: .92rem; }
.hit-toast-who { font-size: .8rem; color: var(--text-muted); }

.hit-toast-close {
    background: none;
    border: 0;
    color: var(--text-muted);
    font-size: 1.1rem;
    line-height: 1;
    padding: .2rem .3rem;
    cursor: pointer;
}
.hit-toast-close:hover { color: var(--text); }

@keyframes hit-toast-in  { from { opacity: 0; transform: translateY(1.2rem); } }
@keyframes hit-toast-out { to   { opacity: 0; transform: translateY(.6rem); } }

/* Den som bett om mindre rörelse får uttoning utan förflyttning. */
@media (prefers-reduced-motion: reduce) {
    @keyframes hit-toast-in  { from { opacity: 0; } }
    @keyframes hit-toast-out { to   { opacity: 0; } }
}

.hit-toast-icon { font-size: 1rem; line-height: 1; opacity: .8; }

/* Bingofirande ------------------------------------------------------------
   Tar hela skärmen. En liten ruta i hörnet säger inte vad ögonblicket är värt,
   och det är enda gången i spelet något faktiskt avgörs. */
.bingo-overlay {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    cursor: pointer;
    background: rgba(10, 8, 16, .82);
    backdrop-filter: blur(3px);
    animation: bingo-in .25s ease-out;
}
.bingo-overlay.leaving { animation: bingo-out .3s ease-in forwards; }

.bingo-overlay-icon { font-size: clamp(3rem, 18vw, 6rem); line-height: 1; }

.bingo-overlay-word {
    font-size: clamp(2.5rem, 15vw, 5rem);
    font-weight: 900;
    letter-spacing: .04em;
    color: var(--accent);
    margin: .3rem 0 .2rem;
}
.bingo-overlay-who {
    font-size: clamp(1.1rem, 5vw, 1.6rem);
    font-weight: 600;
    color: #fff;
}

@keyframes bingo-in  { from { opacity: 0; transform: scale(.94); } }
@keyframes bingo-out { to   { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
    @keyframes bingo-in { from { opacity: 0; } }
}

.builder-saved { color: var(--hit); font-weight: 600; }

/* Startknappen i deltagarpanelen. Avskild med en linje så den inte läses som
   ännu en spelarrad. */
.lobby-start {
    margin-top: .8rem;
    padding-top: .8rem;
    border-top: 1px solid var(--border);
}
.lobby-ready { margin: 0 0 .6rem; font-size: .9rem; }

/* Bjud in: rader i stället för kolumner ------------------------------------
   "Etikett: värde" läser sig i samma riktning som texten, och slipper klämma
   ihop en adress i en tredjedels bredd. */
/* Rutnät i stället för fristående flexrader, så att etiketterna får en gemensam
   vänsterkant och värdena en annan. Med var sin flexrad hamnade de på olika ställen
   beroende på hur lång etiketten råkade vara. */
/* Bilden ligger *bakom* raderna i stället för i en egen kolumn. Den tar därmed ingen
   bredd, så QR-koden behåller sina 230 px oavsett hur stor bilden är – med en egen
   kolumn krympte koden så fort bilden växte.

   Överlapp gör ingenting: QR-koden har en ogenomskinlig vit platta och ligger överst,
   så den förblir skannbar även om en figur hamnar under den.

   Bilden visas först över 40rem. Mobilvyn är den som finslipades fram och ska vara
   orörd. */
.share-layout { position: relative; }

/* Lagret är exakt lika högt som raderna: det spänner över hela .share-layout, som inte
   har någon egen höjd utan får radernas. Bilden maximeras till den höjden. */
.share-art-cell { display: none; }

@media (min-width: 40rem) {
    .share-art-cell {
        display: block;
        position: absolute;
        inset: 0;
        /* Lagret får sträcka sig upp i luften ovanför raderna, ända tills håret nästan
           nuddar ingressen. Utrymmet står ändå tomt, och bilden vinner höjd utan att
           panelen växer. Nedre kanten ligger kvar mot QR-kodens. */
        top: -1.5rem;
        z-index: 0;
        /* Dekor ska inte fånga klick från kopieringsknappen bakom. */
        pointer-events: none;
    }

    /* Innehållet måste vara positionerat för att hamna över lagret. Ett opositionerat
       element förlorar mot ett absolut placerat oavsett z-index. */
    .share-main, .share-qr { position: relative; z-index: 1; }
}

/* Två spalter från 40rem: koden och länken till vänster, QR-koden till höger. Under
   det staplas de, för på telefon finns ingen bredd att dela och QR-koden är dessutom
   mindre användbar där – den som håller telefonen kan inte scanna den. */
.share-main { display: flex; flex-direction: column; gap: .3rem; min-width: 0; }

.share-label { font-size: .82rem; font-weight: 600; color: var(--text-muted); }
.share-label + .btn { margin-top: .1rem; }

/* Koden läses upp högt för ett rum. Den är den enda siffran i appen som ska gå att
   se på avstånd, och därför får den ta plats i stället för att sitta i en rad.
   Spärrad med luft mellan tecknen: fyra siffror i klump läses fel när någon
   dikterar dem. */
.share-code-big {
    font-size: 2.6rem;
    font-weight: 800;
    letter-spacing: .18em;
    /* Spärrningen lägger luft även efter sista tecknet och skjuter texten åt vänster.
       Lika stor indragning till höger tar tillbaka mitten. */
    text-indent: .18em;
    text-align: center;
    line-height: 1.15;
    background: var(--surface-2);
    border: 2px solid var(--border-strong);
    border-radius: var(--radius-sm);
    padding: .35rem .5rem;
    margin-bottom: .7rem;
}

.share-qr {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: .3rem;
    flex-shrink: 0;
    margin-top: 1rem;
}

/* Ingen padding: den tysta zonen är inbakad i bilden sedan den blev PNG, så att en
   kopierad kod går att skanna även när CSS:en inte följer med. Ramen är bara kontur. */
.share-qr .qr {
    width: 100%;
    max-width: 210px;
    margin: 0;
    padding: 0;
    border: 2px solid var(--border-strong);
}

@media (min-width: 40rem) {
    .share-layout { display: flex; gap: 1.4rem; align-items: flex-start; }
    .share-main { flex-grow: 1; }
    .share-qr { width: 210px; margin-top: 0; }
}

/* Nedröstad ruta: gul och överstruken. Den ligger kvar och kan bli giltig igen om
   någon drar tillbaka sin protest, så den ska läsas som ifrågasatt – inte som tom. */
.cell.disputed {
    background: color-mix(in srgb, var(--warn) 22%, var(--surface-2));
    border-color: var(--warn);
    color: var(--text);
    text-decoration: line-through;
    text-decoration-thickness: 2px;
}
.cell.disputed.hit { background: color-mix(in srgb, var(--warn) 30%, var(--surface-2)); }

/* Bekräftelse innan en träff ångras. */
.undo-confirm {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
    margin-bottom: .6rem;
    padding: .55rem .7rem;
    border: 1px solid var(--warn);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--warn) 12%, transparent);
    font-size: .9rem;
}

/* ---- Gratulationer ---- */
.congrats-block + .congrats-block {
    margin-top: .9rem;
    padding-top: .9rem;
    border-top: 1px solid var(--border);
}
.congrats-winner { font-weight: 700; margin-bottom: .4rem; }
.congrats-item {
    display: flex;
    gap: .5rem;
    align-items: baseline;
    padding: .35rem 0;
}
.congrats-from {
    font-weight: 600;
    font-size: .85rem;
    color: var(--text-muted);
    /* Namn får inte krympa bort; det är meddelandet som ska brytas. */
    flex: 0 0 auto;
}
.congrats-message { overflow-wrap: anywhere; }
.congrats-empty { margin: 0; font-size: .9rem; }
.congrats-compose {
    display: flex;
    gap: .5rem;
    margin-top: .5rem;
}
.congrats-compose .form-control { flex: 1 1 auto; min-width: 0; }
.congrats-compose .btn { flex: 0 0 auto; white-space: nowrap; }
@media (max-width: 26rem) {
    /* Fältet blir obrukbart smalt bredvid knappen på en liten telefon. */
    .congrats-compose { flex-direction: column; }
}

/* ---- Så går det till ---- */
/* Kompakt: fem steg som ska gå att svepa över, inte läsas som brödtext. Marginalen
   på ol nollställs eftersom card-panel redan ger luften. */
.how-to ol {
    margin: 0;
    padding-left: 1.4rem;
    display: grid;
    gap: .45rem;
}
.how-to li { font-size: .92rem; line-height: 1.4; }
.how-to li::marker { color: var(--primary); font-weight: 800; }

/* Markerad ruta i väntan på en partner att byta med. Tydligt men inte skrikigt:
   spelaren ska se vad som är valt utan att tro att något gått sönder. */
.builder-preview .cell.picked {
    border-style: solid;
    border-color: var(--primary);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 45%, transparent);
}
.builder-preview .cell[role="button"] { cursor: pointer; }
/* Krysset ligger ovanpå rutan och ska ta bort, inte markera för byte. Det renderas
   bara på den markerade rutan – se CardBuilder.razor för skälet. */
.builder-preview .cell-remove { z-index: 1; }

/* ---- Bytesläge i brickbyggaren ---- */
/* Allt utom brickan dämpas medan en ruta väntar på att bytas. Blicken ska ligga på
   rutnätet, och inget annat på sidan är meningsfullt att trycka på just då.

   Dämpningen utanför panelen görs med :has() i stället för en klass på sidans rot.
   Skälet är konkret: else-grenen i Game.razor deklarerar `RenderFragment deltagarpanel`
   mitt i markupen, och ett omslutande element gör resten av blocket till markupkontext
   där det C#-uttrycket inte får stå. :has() slipper det, och en webbläsare utan stöd
   faller tillbaka på att bara panelen dämpas — vilket är det mesta av nyttan. */
.app-main:has(.card-panel.swapping) > .card-panel:not(.swapping) {
    opacity: .3;
    transition: opacity .15s ease;
}

/* Innehållet i själva byggaren dämpas också – utom brickan och raden som förklarar
   vad som händer. pointer-events stängs av så att ett bomskott inte lägger till en
   floskel i stället för att byta plats. */
.card-panel.swapping > *:not(.board):not(.builder-hintline):not(h2) {
    opacity: .3;
    pointer-events: none;
    transition: opacity .15s ease;
}

/* Raden byter innehåll mellan "2 av 16 valda" och bytesledtexten. Höjden reserveras
   för två rader, så att brickan står stilla oavsett vilken text som visas.

   Att bara korta texten räcker inte: nästa språk kan vara längre, och då knuffas
   brickan nedåt igen precis när man siktar på nästa ruta. Reserverad höjd är
   språksäker på ett sätt som en kort formulering aldrig blir. */
.builder-hintline {
    min-height: 2.4rem;
    display: flex;
    align-items: center;
    margin-bottom: .3rem;
    font-size: .85rem;
    line-height: 1.35;
}
.builder-swap-hint {
    font-weight: 600;
    color: var(--primary);
}
/* Varning och inte bara dämpad text: det är ett tillstånd med en följd. */
.builder-warn {
    font-weight: 600;
    color: var(--warn);
}

/* ---- Återkoppling ---- */
.feedback-label { display: block; font-weight: 600; margin-bottom: .35rem; }
.feedback-text { width: 100%; resize: vertical; font: inherit; }

/* Honungsfällan ska vara osynlig för människor men finnas i markupen för bottar.
   display:none hoppas över av en del enklare bottar, medan en ruta utanför skärmen
   ser ut som ett vanligt fält. */
.feedback-trap {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    opacity: 0;
}

.feedback-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .6rem;
    margin-top: .6rem;
}
.feedback-count { font-size: .8rem; }
.feedback-thanks { margin: 0; font-weight: 600; color: var(--hit); }

.admin-feedback { padding: .6rem 0; border-bottom: 1px solid var(--border); }
.admin-feedback:last-child { border-bottom: 0; }
.admin-feedback-meta { font-size: .78rem; color: var(--text-muted); margin-bottom: .2rem; }
/* Fritext från främlingar: bryt var som helst hellre än att spränga layouten. */
.admin-feedback-text { white-space: pre-wrap; overflow-wrap: anywhere; }

/* ---- Historik ---- */

/* Hela panelen är klickbar. Länkens egna färger och understrykning bort – kortet ska
   se ut som ett kort, inte som en länk mitt i löptext. */
.card-panel-link { display: block; text-decoration: none; color: inherit; }
.history-title { font-weight: 700; }
.history-meta { text-align: right; }
.history-pills { margin-top: .35rem; }

/* ---- Brickbyggaren: mått som satt i markup ---- */
.builder-label { font-weight: 600; }
.builder-label + .chips { margin-top: .35rem; }

/* ---- Demobrickan på startsidan ---- */

/* Etiketten som säger att brickan går att röra. Fet och i textfärg, inte dämpad:
   den ska läsas som en skylt, inte som en fotnot. Pilen ligger i CSS eftersom den
   hör till layouten – etiketten står alltid ovanför brickan, så nedåt är rätt
   oavsett skärmbredd. */
.demo-flag {
    /* Visas i alla bredder. Först bara på desktop, eftersom problemet var
       desktopspecifikt – men etiketten visade sig vara bättre än den dämpade fotnot
       den ersatte, och då finns ingen anledning att undanhålla den på telefon. */
    margin: 0 0 .4rem;
    font-size: .82rem;
    font-weight: 800;
    letter-spacing: .02em;
    color: var(--text);
}
/* Tecknet rakt av och inte som CSS-escape: filen är UTF-8, escapen tillför
   ingenting och gick dessutom sönder på vägen genom ett skript. */
.demo-flag::after { content: " ↓"; color: var(--primary); font-weight: 900; }

/* Hovring säger "tryck här" snabbare än någon text. Finns bara på pekdon som kan
   hovra, alltså inte på telefon - där gör etiketten hela jobbet. */
@media (hover: hover) {
    .hero-board .cell:hover:not(:disabled) {
        transform: translateY(-2px);
        box-shadow: 0 3px 0 var(--border-strong);
    }
}

/* Löftet ovanför rubriken. Inline-block så att plattan tar bredden av sin text och
   inte av spalten – en etikett som sträcker sig över hela raden läser sig som en
   rubrikbakgrund i stället för som en märkning. */
.hero-promise {
    display: inline-block;
    align-self: flex-start;
    margin: 0 0 .55rem;
    padding: .28rem .7rem;
    background: var(--border-strong);
    color: var(--bg);
    border-radius: 999px;
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
}

/* Vinsten. Ligger under brickan och inte över: den som just fått bingo ska se sin
   egen rad, inte få den övertäckt av ett grattis. */
.hero-win {
    /* Brickan flyter till höger från 40rem. Utan clear lägger sig vinstrutan i spalten
       BREDVID den i stället för under, och knuffar ingressen framför sig. */
    clear: both;
    margin-top: .7rem;
    padding: .8rem 1rem;
    background: var(--hit-soft);
    border: 2px solid var(--border-strong);
    border-radius: var(--radius-sm);
}
.hero-win-title {
    margin: 0;
    font-size: 1.3rem;
    font-weight: 800;
    letter-spacing: -.02em;
}
.hero-win-text { margin: .1rem 0 .4rem; font-size: .95rem; line-height: 1.4; }
.hero-win-actions { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }

.player-marks { display: flex; align-items: center; gap: .35rem; }

/* Först i mål när flera fått bingo. Guld mot bingopillets vitt, så att ordningen syns
   utan att läsas – och så att den inte konkurrerar med träffgult i brickan. */
.pill.first-bingo {
    background: #fff8e1;
    border-color: #b8860b;
    color: #7a5a00;
}
