/* ===========================================================================
   Togglepad - het apparaat
   ---------------------------------------------------------------------------
   Dit bestand wordt letterlijk gedeeld door de publieke homepage en /app. Beide
   laden het van dezelfde URL, dus er is één cache-entry en één bron van waarheid.
   Zet hier niets in dat alleen in de app bestaat.

   Twee principes:

   1. Alle visuele staat hangt aan attributen (data-tp-state, data-tp-hit,
      aria-pressed). pad.js schrijft één attribuut en nooit een style-property,
      behalve de per-knop kleuren die als custom property meekomen uit de
      database. Zo blijft stijlwerk van het audiopad af.

   2. Responsive is puur CSS. Geen ResizeObserver, geen JS-layout. Het formaat
      volgt uit aspect-ratio op de knop plus een hoogtebudget in dvh, en onder een
      minimale knopmaat scrollt het apparaat in plaats van verder te krimpen.
   =========================================================================== */

.tp-device {
    /* Gridvorm - pad.js zet deze twee op basis van de pad-configuratie. */
    --tp-cols: 4;
    --tp-rows: 4;

    /* Ruimte tussen de knoppen. Schaalt mee, maar nooit onder 6 of boven 14. */
    --tp-gap: clamp(6px, 1.2vw, 14px);

    /* Hoogtebudget voor het grid. Bewust dvh en niet vh: met vh valt de onderste
       knoprij op mobiel achter de adresbalk, precies waar een duim landt.
       De rem-cap voorkomt een absurd groot apparaat op een 4K-scherm. */
    --tp-grid-max-h: min(62dvh, 30rem);

    /* Ondergrens per knop. Onder 44px is een raakdoel niet meer betrouwbaar te
       treffen; daaronder gaat het apparaat scrollen (zie .tp-gridwrap). */
    --tp-cell-min: 44px;

    /* Binnenmarge van het chassis. Staat als variabele zodat de breedte hieronder
       hem kan meerekenen; de media-queries passen alleen deze waarde aan. */
    --tp-pad-x: clamp(12px, 2vw, 22px);

    /* Afgeleiden - niet zelf zetten. */
    --tp-cell-max: calc((var(--tp-grid-max-h) - (var(--tp-rows) - 1) * var(--tp-gap)) / var(--tp-rows));
    --tp-grid-w:   calc(var(--tp-cell-max) * var(--tp-cols) + (var(--tp-cols) - 1) * var(--tp-gap));
    --tp-grid-min: calc(var(--tp-cell-min) * var(--tp-cols) + (var(--tp-cols) - 1) * var(--tp-gap));

    /* Selectiering (editor). Staat hier en niet in app.css, omdat .tp-gridwrap er
       ruimte voor moet vrijhouden - en die staat in dit bestand. Twee plekken die
       hetzelfde getal moeten kennen, dus één bron. */
    --tp-ring-w: 3px;
    --tp-ring-gap: 2px;
    --tp-ring-room: calc(var(--tp-ring-w) + var(--tp-ring-gap));

    /* Chassis */
    --tp-chassis-1: #23262c;
    --tp-chassis-2: #15171b;
    --tp-chassis-edge: rgba(255, 255, 255, .07);
    --tp-accent: #2fd3b5;
    --tp-radius: 18px;

    display: flex;
    flex-direction: column;
    gap: clamp(10px, 1.6vw, 18px);

    /* Expliciet uit het grid afgeleid, niet via fit-content.
       fit-content kan de breedte hier niet uit het grid halen: .tp-gridwrap is
       een scroll-container, en die geeft de intrinsieke breedte van zijn inhoud
       niet door aan de ouder. Het gevolg was een chassis over de volle
       schermbreedte met een smal grid in het midden - alleen zichtbaar in
       landschap, waar het hoogtebudget het grid smal maakt. */
    width: min(100%, calc(var(--tp-grid-w) + var(--tp-pad-x) * 2));
    margin-inline: auto;

    padding: var(--tp-pad-x);
    padding-bottom: max(var(--tp-pad-x), env(safe-area-inset-bottom));

    border-radius: var(--tp-radius);
    color: #e8ecf2;

    /* Geborsteld metaal uit twee gradients: een verloop voor de vorm en een
       fijne herhalende streep voor de textuur. Geen afbeelding, dus geen extra
       request en scherp op elke pixeldichtheid. */
    background:
        repeating-linear-gradient(
            90deg,
            rgba(255, 255, 255, .022) 0 1px,
            rgba(0, 0, 0, 0) 1px 3px
        ),
        linear-gradient(160deg, var(--tp-chassis-1), var(--tp-chassis-2));

    box-shadow:
        inset 0 1px 0 var(--tp-chassis-edge),
        inset 0 0 0 1px rgba(0, 0, 0, .5),
        0 30px 60px -20px rgba(0, 0, 0, .6);
}

/* --------------------------------------------------------------------------
   Kop: merk, statuslampje, mastervolume
   -------------------------------------------------------------------------- */

.tp-device__head {
    display: flex;
    align-items: center;
    gap: clamp(8px, 1.6vw, 16px);
    padding-inline: 2px;

    /* Wikkelen in plaats van uitsteken.
       Bij een smal chassis - 4 kolommen bij 8 rijen maakt het apparaat hoog en
       smal - past de kop niet meer op één regel. Zonder dit steekt de volumebalk
       buiten het apparaat uit; nu zakt hij naar een tweede regel. */
    flex-wrap: wrap;
    row-gap: 8px;
}

.tp-brand {
    font: 600 clamp(10px, 1.4vw, 12px)/1 ui-sans-serif, system-ui, sans-serif;
    letter-spacing: .22em;
    text-transform: uppercase;
    color: #7d8794;
    white-space: nowrap;
}

/* Statuslampje: dof tijdens laden, oplichtend zodra alles speelklaar is. */
.tp-led {
    width: 8px;
    height: 8px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: #3a3f47;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .6);
    transition: background 200ms ease-out, box-shadow 200ms ease-out;
}

.tp-device[data-tp-ready="1"] .tp-led {
    background: var(--tp-accent);
    box-shadow:
        inset 0 0 0 1px rgba(0, 0, 0, .4),
        0 0 8px rgba(47, 211, 181, .8);
}

/* --------------------------------------------------------------------------
   Stopknop
   --------------------------------------------------------------------------
   Rood en als enige gekleurde bedieningsknop op het apparaat: dit is de knop die
   je in het donker moet kunnen vinden terwijl er iets speelt dat weg moet.

   Blijft ook onder 480px staan, in tegenstelling tot het merklabel. Ruimte
   besparen op de knop die je onder tijdsdruk zoekt, is de verkeerde ruil.
   -------------------------------------------------------------------------- */
.tp-stop {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: clamp(26px, 4.4vw, 32px);
    aspect-ratio: 1;
    padding: 0;
    border: 0;
    border-radius: 7px;
    color: #ffe3e3;
    /*
       De glanslijn langs de bovenrand als gradient, niet als inset-schaduw.

       `inset 0 1px 0` tekent een rechte lijn van 1px en wordt door de
       border-radius aan weerszijden afgekapt. Op een pad van 100px valt dat niet
       op, maar deze knop is 28px met een radius van 7px: dan is minder dan de
       helft van de bovenrand recht, en de lijn houdt zichtbaar op vóór de hoeken.
       Een gradient wordt door dezelfde radius netjes meegebogen en loopt dus wél
       door tot in de rondingen.
    */
    background:
        linear-gradient(180deg, rgba(255, 255, 255, .30), rgba(255, 255, 255, 0) 3px),
        linear-gradient(180deg, #e2564f, #c33b34);
    box-shadow:
        0 2px 0 #7f2621,
        0 5px 10px rgba(0, 0, 0, .38);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    /* Dezelfde asymmetrie als de pads: direct omlaag, verend omhoog. Zonder dat
       voelt hij als een webformulier tussen hardware. */
    transition: transform 40ms ease-out, box-shadow 40ms ease-out, filter 160ms ease-out;
}

.tp-stop svg {
    width: 58%;
    height: 58%;
    display: block;
}

.tp-stop:hover { filter: brightness(1.08); }

.tp-stop:focus-visible {
    outline: 2px solid var(--tp-accent);
    outline-offset: 2px;
}

.tp-stop:active {
    transform: translateY(2px);
    box-shadow:
        0 0 0 #7f2621,
        0 2px 4px rgba(0, 0, 0, .4);
}

/* Niets te stoppen: zichtbaar, maar duidelijk buiten werking. Verbergen zou de
   knoppenbalk laten verspringen op het moment dat er geluid begint - precies
   wanneer je hem wilt aanwijzen. */
.tp-stop:disabled {
    filter: grayscale(.65) brightness(.62);
    cursor: default;
}

.tp-volume {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
    min-width: 0;
    /* Mag krimpen én groeien binnen de grenzen van de schuif hieronder. */
    flex: 1 1 auto;
}

/* Zichtbaar voor screenreaders, onzichtbaar op het scherm. Bewust hier en niet
   als utility in default.css: pad.css wordt ook door /app geladen, waar
   default.css niet bestaat, en de component moet zelfvoorzienend zijn.
   Niet display:none - dat haalt het label ook uit de a11y-boom, en dan is het
   weg voor precies de gebruiker die het nodig heeft. */
.tp-volume__label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.tp-volume__icon {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    color: #7d8794;
}

.tp-volume input[type="range"] {
    /**
     * min-width: 0 is hier de hele fix.
     *
     * Een flex-item heeft standaard min-width: auto, en dat lost op naar de
     * intrinsieke minimumbreedte van de inhoud. Voor een input[type=range] is dat
     * in Chromium ruim honderd pixels - die weigert dus te krimpen, ook al staat
     * er flex-shrink op. Het gevolg was een volumebalk die bij een smal apparaat
     * buiten het chassis uitstak.
     *
     * De breedte is nu een bovengrens in plaats van een vaste maat: groeien tot
     * 132px, en daaronder meekrimpen met wat er over is.
     */
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
    max-width: clamp(64px, 14vw, 132px);

    accent-color: var(--tp-accent);
    background: transparent;
    cursor: pointer;
}

/* --------------------------------------------------------------------------
   Bankkiezer
   -------------------------------------------------------------------------- */

.tp-banks {
    display: flex;
    gap: 6px;
}

.tp-bank {
    flex: 1 1 0;
    min-width: 44px;
    min-height: 32px;
    padding: 6px 10px;

    font: 600 12px/1 ui-sans-serif, system-ui, sans-serif;
    letter-spacing: .08em;
    color: #99a2ad;

    background: linear-gradient(180deg, #2b2f36, #22262c);
    border: 1px solid rgba(0, 0, 0, .5);
    border-radius: 8px;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
    cursor: pointer;

    transition: color 120ms ease-out, background 120ms ease-out, box-shadow 120ms ease-out;
}

.tp-bank:hover { color: #cdd4dd; }

.tp-bank[aria-pressed="true"] {
    color: #0d1013;
    background: linear-gradient(180deg, var(--tp-bank-color, var(--tp-accent)), color-mix(in srgb, var(--tp-bank-color, var(--tp-accent)) 78%, #000));
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .3),
        0 0 12px -2px var(--tp-bank-color, var(--tp-accent));
}

/* Een bank die nog laadt is zichtbaar bezig maar wel al aanklikbaar - de actieve
   bank wordt als eerste geladen, dus wisselen tijdens het laden is normaal. */
.tp-bank[data-tp-state="loading"]::after {
    content: "";
    display: inline-block;
    width: 5px;
    height: 5px;
    margin-left: 6px;
    vertical-align: middle;
    border-radius: 50%;
    background: currentColor;
    opacity: .5;
    animation: tp-pulse 900ms ease-in-out infinite;
}

/* --------------------------------------------------------------------------
   Het grid
   -------------------------------------------------------------------------- */

/* Zodra het grid smaller zou worden dan --tp-grid-min, scrollt het hier
   horizontaal in plaats van de knoppen onder de 44px te duwen. Dat is de reden
   dat een 8x8 op een telefoon bruikbaar blijft. */
/**
 * Scrollcontainer voor het grid.
 *
 * De marge-plus-padding is geen opmaak maar ruimte voor de selectiering van de
 * editor. overflow-y: hidden klipt alles wat buiten deze doos valt, en de ring
 * ligt --tp-ring-room buiten de knop - zonder deze ruimte werd hij op de
 * bovenste en onderste rij afgesneden, precies waar hij het meest opvalt.
 *
 * De negatieve marge heft de padding weer op, zodat het grid op dezelfde plek
 * blijft staan en het chassis niet groeit.
 *
 * overflow-y kan niet op visible: zodra één as scrollt, dwingt CSS de andere naar
 * auto. Weghalen zou dus een verticale scrollbalk opleveren in plaats van zichtbare
 * ringen.
 */
.tp-gridwrap {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    margin: calc(var(--tp-ring-room) * -1);
    padding: var(--tp-ring-room);
}

.tp-grid {
    display: grid;
    grid-template-columns: repeat(var(--tp-cols), 1fr);
    gap: var(--tp-gap);

    width: min(100%, var(--tp-grid-w));
    min-width: var(--tp-grid-min);
    margin-inline: auto;
    scroll-snap-align: start;

    /* Zonder touch-action:none scrollt of zoomt de pagina mee terwijl je speelt,
       en annuleert de browser de pointer-sequentie halverwege een aanslag. */
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
}

/* --------------------------------------------------------------------------
   De knop
   -------------------------------------------------------------------------- */

.tp-pad {
    /* Vierkant per knop, niet via aspect-ratio op het grid: met gaps klopt de
       verhouding op gridniveau alleen bij een vierkant grid, en 4x8 moet ook. */
    aspect-ratio: 1;
    position: relative;
    display: grid;
    place-items: center;
    padding: 0;

    color: var(--tp-ink, #111);
    border: 0;
    border-radius: clamp(6px, 1.1vw, 12px);
    cursor: pointer;
    overflow: hidden;

    /* --- Het reliëf komt uit de knopkleur, niet uit vaste grijstinten ------

       Hier stonden drie hard-gecodeerde waarden: een grijze zijkant (#b9bfc9),
       een witte glanslijn (#fff) en een verloop naar 88% van de kleur. Alle drie
       waren op wit afgestemd, en op een donkere knop deed geen van drieën nog
       wat hij moest doen:

         - de grijze zijkant was lichter dan de knop zelf, dus die las als een
           gloed eronder in plaats van als een schaduwrand;
         - de witte glanslijn werd een hard streepje in plaats van een lichtval;
         - 88% van bijna-zwart is nog steeds bijna-zwart, dus het verloop was
           er domweg niet.

       Vandaar dat alles nu van --tp-face wordt afgeleid. De percentages zijn zo
       gekozen dat wit blijft zoals het was - 72% wit is precies de oude
       #b9bfc9 - terwijl een donkere kleur wél een lichtere bovenkant en een
       donkere zijkant krijgt. Bijmengen met wit doet op wit niets en op zwart
       veel, en dat is precies de asymmetrie die je hier wilt. */
    --tp-face: #ffffff;
    --tp-face-top:  color-mix(in srgb, var(--tp-face) 90%, #fff);
    --tp-face-bot:  color-mix(in srgb, var(--tp-face) 92%, #000);
    --tp-face-side: color-mix(in srgb, var(--tp-face) 72%, #000);
    --tp-face-lip:  color-mix(in srgb, var(--tp-face) 70%, #fff);

    background: linear-gradient(180deg, var(--tp-face-top), var(--tp-face-bot));

    /* De 0 3px 0 is de zijkant van de knop; die klapt in bij het indrukken en
       daar komt het gevoel van indrukken vandaan. */
    box-shadow:
        0 3px 0 var(--tp-face-side),
        0 8px 16px rgba(0, 0, 0, .35),
        inset 0 1px 0 var(--tp-face-lip);

    /* Asymmetrisch: hard omlaag, verend omhoog. Dit ene detail bepaalt of het
       als hardware voelt of als een webknop. */
    transition:
        transform 40ms ease-out,
        box-shadow 40ms ease-out,
        background 160ms ease-out;
}

.tp-pad:focus-visible {
    outline: 2px solid var(--tp-accent);
    outline-offset: 2px;
}

/* Losgelaten: langzamer en met een lichte overshoot. */
.tp-pad:not([data-tp-hit]) {
    transition:
        transform 160ms cubic-bezier(.2, .8, .3, 1),
        box-shadow 160ms cubic-bezier(.2, .8, .3, 1),
        background 160ms ease-out;
}

/* Ingedrukt. data-tp-hit wordt door pad.js gezet en dekt muis, aanraking,
   toetsenbord én MIDI - er is maar één codepad dat dit attribuut schrijft. */
.tp-pad[data-tp-hit],
.tp-pad:active {
    transform: translateY(3px);
    box-shadow:
        0 1px 0 var(--tp-face-side),
        0 3px 6px rgba(0, 0, 0, .3),
        inset 0 1px 0 var(--tp-face-lip),
        0 0 0 2px color-mix(in srgb, var(--tp-accent) 70%, transparent);
}

/* De per-knop kleur komt binnen als inline custom property op het element zelf,
   zodat er geen stylesheet per knop nodig is.

   Er stond hier een eigen achtergrondregel voor knoppen mét --tp-face. Die is
   overbodig geworden: .tp-pad zet nu zelf een standaardwaarde en leidt het hele
   reliëf daaruit af, dus een inline kleur overschrijft simpelweg de basis en de
   afgeleiden volgen vanzelf. Twee plekken die allebei de achtergrond bepaalden,
   was precies waarom de donkere variant er anders uitzag dan de lichte. */

/* --- Inhoud van de knop --------------------------------------------------- */

.tp-pad__icon {
    width: 42%;
    height: 42%;
    color: var(--tp-ink, #111);
    pointer-events: none;
    opacity: 0;
    transform: scale(.86);
    transition: opacity 180ms ease-out, transform 180ms cubic-bezier(.2, .8, .3, 1);
}

/* --------------------------------------------------------------------------
   De achtergrondafbeelding
   --------------------------------------------------------------------------
   Onderste laag van de knop: icoon en label liggen erbovenop. De z-index staat
   er expliciet bij en niet impliciet via de documentvolgorde, omdat de drie
   lagen in verschillende takken van inc_pad.php gerenderd worden en de volgorde
   daar dus niet gegarandeerd is.

   object-fit: cover in plaats van contain - de gebruiker heeft al vierkant
   bijgesneden, dus er valt niets meer weg te snijden, en contain zou bij een
   afwijkende verhouding balken geven.
   -------------------------------------------------------------------------- */
.tp-pad__img {
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    pointer-events: none;
    opacity: 0;
    transition: opacity 180ms ease-out;
    /* De knopkleur blijft de ondergrond: een PNG met een uitgeknipt logo laat
       hem doorschijnen, en dat is precies waarom de kleurkeuze blijft bestaan. */
    background: transparent;
}

.tp-pad__label {
    position: absolute;
    z-index: 2;
    left: 0;
    right: 0;
    bottom: 6%;
    padding-inline: 6%;

    font: 600 clamp(8px, .9vw, 11px)/1.2 ui-sans-serif, system-ui, sans-serif;
    letter-spacing: .02em;
    text-align: center;
    color: var(--tp-ink, #111);
    opacity: 0;

    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    pointer-events: none;
    transition: opacity 180ms ease-out;
}

/* --- Spinner -------------------------------------------------------------- */

/* Een conic-gradient-ring met een uitgestanste kern. Geen SVG, geen extra
   request, en hij erft de kleur van de knop. */
.tp-pad__spinner {
    position: absolute;
    width: 26%;
    height: 26%;
    border-radius: 50%;
    pointer-events: none;
    opacity: 0;

    background: conic-gradient(
        from 0deg,
        transparent 0deg,
        color-mix(in srgb, var(--tp-ink, #111) 55%, transparent) 300deg,
        transparent 360deg
    );
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
            mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));

    animation: tp-spin 700ms linear infinite;
}

/* --- Toestanden ----------------------------------------------------------- */

/* Nog niets gestart (bijvoorbeeld: het demo-pad wacht tot het in beeld komt). */
.tp-pad[data-tp-state="idle"] {
    background: linear-gradient(180deg, #f4f6f8, #e3e7ec);
}

.tp-pad[data-tp-state="loading"] .tp-pad__spinner { opacity: 1; }

.tp-pad[data-tp-state="ready"] .tp-pad__icon {
    opacity: 1;
    transform: scale(1);
}
.tp-pad[data-tp-state="ready"] .tp-pad__img,
.tp-pad[data-tp-state="ready"] .tp-pad__label { opacity: 1; }

/**
 * Een knop zonder geluid toont zijn inhoud meteen.
 *
 * De opacity hierboven laat icoon, afbeelding en label pas verschijnen zodra het
 * geluid geladen is - dat voorkomt dat een half geladen pad er af uitziet. Maar
 * een knop die alleen een achtergrond of een label heeft, wacht op niets, en zou
 * daardoor voor altijd onzichtbaar blijven.
 */
.tp-pad[data-tp-state="empty"] .tp-pad__img,
.tp-pad[data-tp-state="empty"] .tp-pad__icon,
.tp-pad[data-tp-state="empty"] .tp-pad__label {
    opacity: 1;
    transform: none;
}

/* --------------------------------------------------------------------------
   Leesbaarheid boven een foto
   --------------------------------------------------------------------------
   Zodra er een achtergrond ligt, is de knopkleur niet meer wat je ziet en klopt
   de gekozen inktkleur niet langer. Label en icoon gaan dan naar wit met een
   donkere waas eronder.

   De waas is een gradient op de knop zelf en niet op het label: een schaduw
   alleen houdt geen stand op een lichte foto met veel detail, en een blok achter
   de tekst ziet eruit als een fout in de opmaak. Een verloop van onderaf is
   precies genoeg om tekst te dragen zonder dat je het als element opmerkt.
   -------------------------------------------------------------------------- */
.tp-pad:has(.tp-pad__img[src])::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, .55) 0%,
        rgba(0, 0, 0, .18) 38%,
        rgba(0, 0, 0, 0) 62%
    );
    border-radius: inherit;
    opacity: 0;
    transition: opacity 180ms ease-out;
}
.tp-pad[data-tp-state="ready"]:has(.tp-pad__img[src])::before { opacity: 1; }

/* --------------------------------------------------------------------------
   Het chassis van een knop mét achtergrond
   --------------------------------------------------------------------------
   Een gewone knop is wit plastic: witte gradient, lichte grijze zijkant, witte
   glanslijn bovenaan. Met een foto erop lijkt dat een witte lijst om het beeld -
   en dat is precies wat een achtergrondafbeelding níét moet doen.

   Dus: donkere ondergrond (zodat de rondingen geen lichte rafel geven waar de
   anti-aliasing net niet dekt), een donkere zijkant in plaats van een lichte, en
   de glanslijn naar een fijne binnenrand die de foto begrenst zonder hem te
   omlijsten. Het indrukgevoel blijft hetzelfde - alleen de kleur van de rand
   verandert mee.
   -------------------------------------------------------------------------- */
.tp-pad:has(.tp-pad__img[src]) {
    background: #14161a;
    box-shadow:
        0 3px 0 rgba(0, 0, 0, .55),
        0 8px 16px rgba(0, 0, 0, .35),
        inset 0 0 0 1px rgba(255, 255, 255, .10);
}

.tp-pad:has(.tp-pad__img[src]):is([data-tp-hit], :active) {
    box-shadow:
        0 1px 0 rgba(0, 0, 0, .55),
        0 3px 6px rgba(0, 0, 0, .3),
        inset 0 0 0 1px rgba(255, 255, 255, .10),
        0 0 0 2px color-mix(in srgb, var(--tp-accent) 70%, transparent);
}

.tp-pad:has(.tp-pad__img[src]) .tp-pad__label {
    color: #fff;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .8);
}

/* Het icoon volgt mee. Zonder dit blijft een donker icoon staan dat de gebruiker
   koos voor een lichte knop, en dat verdwijnt in een donkere foto. */
.tp-pad:has(.tp-pad__img[src]) .tp-pad__icon {
    color: #fff;
    filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .75));
}

/* Fout: rood randje, geen spinner, wel klikbaar zodat een retry mogelijk is. */
.tp-pad[data-tp-state="error"] {
    background: linear-gradient(180deg, #fff1f1, #f6dcdc);
    box-shadow:
        0 3px 0 #cfa8a8,
        0 8px 16px rgba(0, 0, 0, .3),
        inset 0 1px 0 #fff;
}
.tp-pad[data-tp-state="error"]::after {
    content: "!";
    font: 700 clamp(12px, 1.6vw, 18px)/1 ui-sans-serif, system-ui, sans-serif;
    color: #a33;
}

/* Lege knop: zichtbaar aanwezig, maar niet uitnodigend. */
.tp-pad[data-tp-state="empty"] {
    background: linear-gradient(180deg, #2a2e34, #212429);
    box-shadow:
        inset 0 0 0 1px rgba(255, 255, 255, .04),
        inset 0 2px 8px rgba(0, 0, 0, .5);
    cursor: default;
}
.tp-pad[data-tp-state="empty"]:active { transform: none; }

/* Een knop die op dit moment klinkt (gate/toggle, of een lange stream). */
.tp-pad[data-tp-playing] {
    box-shadow:
        0 3px 0 #b9bfc9,
        0 8px 16px rgba(0, 0, 0, .35),
        inset 0 1px 0 #fff,
        0 0 0 2px var(--tp-accent),
        0 0 16px -2px var(--tp-accent);
}

/* --------------------------------------------------------------------------
   Voetnoot onder het apparaat (hint, iOS-melding)
   -------------------------------------------------------------------------- */

.tp-note {
    margin: 0;
    font: 400 clamp(11px, 1.3vw, 13px)/1.5 ui-sans-serif, system-ui, sans-serif;
    color: #7d8794;
    text-align: center;
}

.tp-note--warn { color: #d8b46a; }

/* --------------------------------------------------------------------------
   Animaties
   -------------------------------------------------------------------------- */

@keyframes tp-spin  { to { transform: rotate(360deg); } }
@keyframes tp-pulse { 0%, 100% { opacity: .25; } 50% { opacity: .9; } }

/* --------------------------------------------------------------------------
   Aanpassingen per viewport
   -------------------------------------------------------------------------- */

/* Telefoon in landschap: het scherm is dan laag, dus wordt de hoogte leidend en
   krimpt de chrome. Zonder deze regel valt de onderste rij buiten beeld. */
@media (orientation: landscape) and (max-height: 640px) {
    .tp-device {
        --tp-grid-max-h: calc(100dvh - 7.5rem);
        --tp-gap: clamp(4px, .8vw, 9px);
        --tp-pad-x: 10px;
        gap: 8px;
    }
    .tp-brand { display: none; }
    .tp-bank { min-height: 28px; }
}

/* Smalle telefoon in portret: merk weg, volledige breedte voor het apparaat. */
@media (max-width: 480px) {
    .tp-device {
        --tp-grid-max-h: min(58dvh, 26rem);
        --tp-pad-x: 10px;
        border-radius: 14px;
    }
    .tp-brand { display: none; }
    .tp-volume input[type="range"] { width: 100%; }
}

/* Ruim scherm: iets meer lucht en een subtiele reflectie op het chassis. */
@media (min-width: 1024px) {
    .tp-device {
        --tp-grid-max-h: min(66dvh, 34rem);
        --tp-pad-x: 24px;
    }
    .tp-device::before {
        content: "";
        position: absolute;
        inset: 0 0 auto 0;
        height: 40%;
        border-radius: var(--tp-radius) var(--tp-radius) 0 0;
        background: linear-gradient(180deg, rgba(255, 255, 255, .045), transparent);
        pointer-events: none;
    }
    .tp-device { position: relative; }
}

/* --------------------------------------------------------------------------
   Toegankelijkheid
   -------------------------------------------------------------------------- */

/* Bewegingsreductie haalt de animatie eruit maar niet de informatie: de spinner
   wordt een statische ring die nog steeds "bezig" betekent. */
@media (prefers-reduced-motion: reduce) {
    .tp-pad,
    .tp-pad__icon,
    .tp-pad__img,
    .tp-pad__label,
    .tp-led,
    .tp-stop,
    .tp-bank {
        transition-duration: 1ms !important;
    }
    .tp-pad__spinner {
        animation: none;
        background: none;
        border: 3px dashed color-mix(in srgb, var(--tp-ink, #111) 45%, transparent);
        -webkit-mask: none;
                mask: none;
    }
    .tp-bank[data-tp-state="loading"]::after { animation: none; opacity: .6; }
}

/* Hoog contrast: de gradients dragen dan geen informatie meer, dus terug naar
   platte vlakken met een harde rand. */
@media (prefers-contrast: more) {
    .tp-pad {
        background: #fff;
        box-shadow: 0 0 0 2px #000, 0 3px 0 #666;
    }
    .tp-pad[data-tp-hit] { box-shadow: 0 0 0 3px #000; }
    .tp-device { background: #101215; }
}
