/* A deliberately single-world design: the board is a reading table under
     lamplight, and it stays that way on any host theme. Every colour is
     therefore declared explicitly, including the body ground. Palette is
     MeowTarot's own brand system (plum #3d1a5c, gold #d49a2c), darkened. */
  :root {
    --ground:     #17102a;
    --surface:    #1f1638;
    --surface-2:  #2a1f49;
    --ink:        #f2ecfb;
    --muted:      #a695c4;
    --faint:      #6d5f8d;
    --gold:       #d49a2c;
    --gold-soft:  #e8c77a;
    --edge:       rgba(212, 154, 44, 0.22);
    --edge-soft:  rgba(212, 154, 44, 0.10);
    --plum-lift:  #3d1a5c;

    --slot-w: 96px;
    --radius: 10px;
    /* Thai comes from the platform: LINE's webview has a Thai UI face on both
       iOS and Android, so no 143 KB inlined font. Swap in a subset woff2 here if
       the brand ever needs one specific face. */
    --font-thai: 'Noto Sans Thai', 'IBM Plex Sans Thai', -apple-system, 'Helvetica Neue', system-ui, sans-serif;
    --font-mark: Georgia, 'Times New Roman', serif;
  }

  * { box-sizing: border-box; }

  body {
    margin: 0;
    background: var(--ground);
    color: var(--ink);
    font-family: var(--font-thai);
    -webkit-font-smoothing: antialiased;
    line-height: 1.5;
  }

  .board {
    max-width: 430px;
    margin: 0 auto;
    min-height: 100vh;
    /* iOS reports 100vh as the viewport with the browser toolbar HIDDEN, so the
       board was always taller than the glass and the bottom row sat under the
       toolbar. svh is the toolbar-visible height. */
    min-height: 100svh;
    /* Was a grid whose second row carried the 1fr, which only worked while the
       mat happened to BE the second child. P1 inserted the room bar above it,
       so the room bar quietly took the flexible row and stretched into a void
       with สร้างห้อง floating in the middle of it. Flex column instead: the row
       that grows is named, not counted, so inserting another row cannot move
       it again. */
    display: flex;
    flex-direction: column;
    background: linear-gradient(180deg, var(--surface) 0%, var(--ground) 42%);
  }

  /* ---------------------------------------------------------- session bar */
  .bar {
    display: grid;
    grid-template-columns: 44px 1fr 44px;
    align-items: center;
    gap: 8px;
    padding: 14px 12px 12px;
    border-bottom: 1px solid var(--edge-soft);
  }

  /* Deck plate + bottom sheet mirror the site's deck switcher (js/deck-switcher.js,
     css/deck-switcher.css) down to the mt-ds-* class names, so the real build can
     lift that CSS instead of growing a second pattern. Colours are re-tuned for
     the dark board. */
  .back-btn {
    display: inline-grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex: 0 0 auto;
    background: transparent;
    border: 1px solid var(--edge);
    border-radius: 50%;
    cursor: pointer;
    padding: 0;
  }
  .back-btn[hidden] { display: none; }
  .back-btn svg { width: 15px; height: 15px; stroke: var(--gold); fill: none; stroke-width: 2.2; }
  .back-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

  .unlock-hint { margin: 0; font-size: 11px; text-align: center; }
  .unlock-hint[hidden] { display: none; }
  .unlock-hint a { color: var(--gold-soft); text-decoration: underline; text-underline-offset: 2px; }
  .unlock-hint a:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

  .mt-ds-plate {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: 40px;
    padding: 5px 9px 5px 5px;
    background: var(--surface-2);
    border: 1px solid var(--edge);
    border-radius: 14px;
    cursor: pointer;
    text-align: left;
    transition: transform .16s ease;
  }
  .mt-ds-plate:active { transform: scale(.97); }
  /* the deck is locked in for the round once cards are on the table -- swapping
     art mid-reading would change the cards the ลูกดวง already touched */
  .mt-ds-plate[disabled] { opacity: .55; cursor: default; }
  .mt-ds-plate[disabled]:active { transform: none; }
  .mt-ds-plate[disabled] .mt-ds-plate__caret { visibility: hidden; }
  .mt-ds-plate:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  .mt-ds-plate__preview { width: 24px; flex: 0 0 auto; border-radius: 5px; overflow: hidden; line-height: 0; }
  .mt-ds-plate__preview img { width: 100%; aspect-ratio: var(--back-ratio, .6); object-fit: cover; display: block; }
  .mt-ds-plate__text { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
  .mt-ds-plate__label { font-size: 9px; letter-spacing: .18em; text-transform: uppercase; color: var(--faint); }
  .mt-ds-plate__name { font-size: 13px; font-weight: 600; color: var(--gold-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .mt-ds-plate__caret svg { width: 12px; height: 12px; stroke: var(--gold); fill: none; stroke-width: 3; }

  .mt-ds-overlay { position: fixed; inset: 0; z-index: 18; }
  .mt-ds-overlay[hidden] { display: none; }
  .mt-ds-scrim { position: absolute; inset: 0; background: rgba(10, 6, 18, .72); }
  .mt-ds-sheet {
    position: absolute;
    left: 50%;
    bottom: 0;
    transform: translateX(-50%);
    width: min(430px, 100%);
    max-height: 78vh;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 8px 16px 22px;
    background: var(--surface);
    border: 1px solid var(--edge);
    border-bottom: 0;
    border-radius: 18px 18px 0 0;
  }
  .mt-ds-grabber { width: 38px; height: 4px; border-radius: 99px; background: var(--edge); margin: 2px auto 4px; }
  .mt-ds-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
  .mt-ds-title { margin: 0; font-size: 15px; font-weight: 600; color: var(--ink); }
  .mt-ds-close { background: transparent; border: 0; cursor: pointer; padding: 4px; line-height: 0; }
  .mt-ds-close svg { width: 14px; height: 14px; stroke: var(--muted); stroke-width: 2.6; stroke-linecap: round; }
  .mt-ds-close:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  .mt-ds-hint { margin: 0; font-size: 11px; color: var(--faint); }
  .mt-ds-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px 10px; overflow-y: auto; padding-bottom: 4px; }

  .mt-ds-cell { background: transparent; border: 0; padding: 0; cursor: pointer; display: flex; flex-direction: column; gap: 6px; text-align: center; }
  .mt-ds-cell:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; border-radius: 12px; }
  .mt-ds-cell__art { position: relative; width: 100%; aspect-ratio: 5 / 7; border-radius: 10px; overflow: hidden; box-shadow: 0 9px 18px -8px rgba(0,0,0,.7); }
  .mt-ds-cell__art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
  .mt-ds-cell.is-locked .mt-ds-cell__art img { filter: grayscale(1) brightness(.55); }
  .mt-ds-cell__badge {
    position: absolute; left: 4px; top: 4px;
    font-size: 9px; letter-spacing: .08em; text-transform: uppercase;
    padding: 2px 6px; border-radius: 99px;
    background: var(--gold); color: #2a1a05;
  }
  .mt-ds-cell__lock { position: absolute; inset: 0; display: grid; place-items: center; }
  .mt-ds-cell__lock svg { width: 20px; height: 20px; stroke: var(--gold-soft); fill: none; stroke-width: 2; }
  .mt-ds-cell__name { font-size: 11px; color: var(--ink); line-height: 1.25; }
  .mt-ds-cell__note { font-size: 10px; color: var(--faint); }

  /* toolbar toggle */
  .tool-row { display: flex; justify-content: flex-end; padding: 0 16px 6px; }
  .tool-toggle {
    display: inline-flex; align-items: center; gap: 5px;
    font-family: var(--font-thai); font-size: 11px; color: var(--muted);
    background: transparent; border: 1px solid var(--edge-soft);
    border-radius: 999px; padding: 4px 10px; cursor: pointer;
  }
  .tool-toggle svg { width: 12px; height: 12px; stroke: var(--gold); fill: none; stroke-width: 2.4; transition: transform .2s ease; }
  .tool-toggle[aria-expanded="false"] svg { transform: rotate(180deg); }
  .tool-toggle:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  .reader[hidden] { display: none; }

  .bar .back-btn { grid-column: 1; }
  .bar .mt-ds-plate { grid-column: 2; justify-self: center; max-width: 100%; }
  .bar .state { grid-column: 3; justify-self: end; }

  .state {
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--faint);
    text-align: right;
    /* Thai stacks a vowel above and a tone above that, so 1.2 sets the next
       line into the marks of the one before it — two wrapped lines were
       overlapping by 3px and spilling out of the cell. */
    line-height: 1.55;
  }

  /* --------------------------------------------------- card-name drawer */
  /* Names only. Meanings stay off this board on purpose -- interpreting the
     card is แม่หมอ's entire value, and a board that explains cards replaces
     her. Every request to add meanings here gets measured against that. */
  .board { position: relative; overflow-x: clip; }

  /* A thin edge handle, not a labelled button -- on a phone the board is the
     whole screen and a pill in the header ate space the cards need. */
  .names-handle {
    position: absolute;
    left: -27px;
    top: 50%;
    transform: translateY(-50%);
    width: 27px;
    height: 62px;
    display: grid;
    place-items: center;
    padding: 0;
    background: #150f26;
    border: 1px solid var(--edge);
    border-right: 0;
    border-radius: 9px 0 0 9px;
    cursor: pointer;
  }
  .names-handle svg { width: 15px; height: 15px; stroke: var(--gold); fill: none; stroke-width: 2; transition: transform 220ms ease; }
  .names-handle:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  .names:not([hidden]) .names-handle svg { transform: rotate(180deg); }

  .names {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 5;
    width: min(206px, 58%);
    height: 100%;
    background: #150f26;
    border-left: 1px solid var(--edge);
    box-shadow: -12px 0 28px rgba(0, 0, 0, 0.45);
    transform: translateX(0);
    transition: transform 260ms cubic-bezier(.2, .7, .2, 1);
  }
  /* stays in the DOM so the handle rides along with it */
  .names[hidden] { display: block; transform: translateX(100%); }
  .names[hidden] .names-inner { visibility: hidden; }

  .names-inner { height: 100%; overflow-y: auto; padding: 14px 14px 24px; }

  .names-head {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 11px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--faint);
    padding-bottom: 10px;
    border-bottom: 1px solid var(--edge-soft);
  }

  .names-list { list-style: none; margin: 0; padding: 12px 0 0; display: flex; flex-direction: column; gap: 12px; }
  .names-list li { display: flex; gap: 9px; align-items: baseline; }
  .names-list .n {
    font-size: 11px;
    color: var(--faint);
    font-variant-numeric: tabular-nums;
    min-width: 12px;
  }
  .names-list .en { font-family: var(--font-mark); font-style: italic; font-size: 14px; color: var(--ink); }
  .names-list .th { display: block; font-size: 12px; color: var(--muted); }
  .names-list .rev { display: block; font-size: 11px; color: var(--gold); }
  .names-empty { font-size: 12px; color: var(--faint); padding-top: 12px; }

  /* On a phone the drawer has to cover the board. On แม่หมอ's second screen
     there is room beside it, so dock it outside and let her read the names and
     watch the picks at the same time. */
  @media (min-width: 940px) {
    .board { overflow-x: visible; }
    .names {
      left: 100%;
      right: auto;
      box-shadow: none;
      border-left: 1px solid var(--edge);
      background: var(--surface);
    }
    .names[hidden] { transform: translateX(0); }
    .names[hidden] .names-inner { visibility: hidden; }
  }

  @media (prefers-reduced-motion: reduce) { .names { transition: none; } }

  /* -------------------------------------------------------------- the mat */
  .mat {
    /* the one row that absorbs the leftover height */
    flex: 1 1 auto;
    min-height: 0;
    padding: 22px 16px 8px;
    display: flex;
    flex-direction: column;
    gap: 22px;
    align-items: center;
    justify-content: center;
    position: relative;
    isolation: isolate;
  }

  /* The cloth. A reading happens on a surface, and a surface is what the board
     was missing: light falling from above, the weave catching it, and the cloth
     going dark where it leaves the table. Painted on a pseudo-element so the
     cards keep their own stacking context above it. */
  .mat::before {
    content: '';
    position: absolute;
    inset: 6px 8px 4px;
    z-index: -1;
    border-radius: 20px;
    background:
      radial-gradient(120% 70% at 50% -10%, rgba(255, 244, 214, .07), transparent 62%),
      radial-gradient(130% 90% at 50% 118%, rgba(0, 0, 0, .5), transparent 64%),
      repeating-linear-gradient(52deg, rgba(255,255,255,.014) 0 2px, transparent 2px 4px),
      repeating-linear-gradient(-38deg, rgba(0,0,0,.05) 0 2px, transparent 2px 4px),
      linear-gradient(178deg, #2c1d43 0%, #241736 55%, #1b1129 100%);
    box-shadow:
      inset 0 0 0 1px rgba(201, 162, 74, .13),
      inset 0 14px 34px rgba(0, 0, 0, .42),
      inset 0 -10px 26px rgba(0, 0, 0, .3);
  }

  .prompt {
    font-size: 15px;
    color: var(--muted);
    text-align: center;
    text-wrap: balance;
    min-height: 1.5em;
  }

  .prompt b { color: var(--gold-soft); font-weight: 600; }

  .slots {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
    width: 100%;
  }

  .slot {
    width: var(--slot-w);
    aspect-ratio: var(--card-ratio, 0.67);
    border-radius: var(--radius);
    position: relative;
    perspective: 900px;
  }

  /* Where a card goes: a shallow depression pressed into the cloth, the way a
     dealt spread leaves its outline. The dashed rectangle read as a form field. */
  .slot.is-empty {
    background:
      linear-gradient(180deg, rgba(0, 0, 0, .3), rgba(0, 0, 0, .16));
    box-shadow:
      inset 0 2px 7px rgba(0, 0, 0, .55),
      inset 0 0 0 1px rgba(255, 255, 255, .045),
      0 1px 0 rgba(255, 255, 255, .04);
  }

  .slot-num {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    font-size: 19px;
    font-family: var(--serif, 'Cormorant Garamond', Georgia, serif);
    color: rgba(201, 162, 74, .3);
    font-variant-numeric: tabular-nums;
  }

  /* card flip */
  .card {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
    transition: transform 620ms cubic-bezier(.2, .7, .2, 1);
    border-radius: var(--radius);
  }

  .card.is-face-up { transform: rotateY(180deg); }

  /* A card turned over on a table comes up off the cloth to do it, and the
     shadow underneath opens and closes with it. */
  .slot.is-turning { animation: table-turn 640ms cubic-bezier(.3, .7, .25, 1); }

  /* Keeps the card's own resting angle through the turn: an animation's
     transform replaces the element's, so dropping the rotate here would
     straighten the card for 640ms and snap it back crooked at the end. */
  @keyframes table-turn {
    0%   { transform: translateY(0) scale(1) rotate(var(--land-rot, 0deg)); filter: drop-shadow(0 6px 10px rgba(0,0,0,.45)); }
    45%  { transform: translateY(-9px) scale(1.045) rotate(var(--land-rot, 0deg)); filter: drop-shadow(0 18px 20px rgba(0,0,0,.5)); }
    100% { transform: translateY(0) scale(1) rotate(var(--land-rot, 0deg)); filter: drop-shadow(0 6px 10px rgba(0,0,0,.45)); }
  }

  /* A card that has landed sits on the cloth, not in a frame. */
  .slot .card-side img { box-shadow: 0 8px 16px rgba(0,0,0,.46), 0 2px 3px rgba(0,0,0,.45); border-radius: var(--radius); }

  .card-side {
    position: absolute;
    inset: 0;
    backface-visibility: hidden;
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
  }

  /* contain, never cover: face and back ratios differ per deck and from
     each other, so cover silently crops the art */
  .card-side img { width: 100%; height: 100%; display: block; object-fit: contain; }

  .card-front { transform: rotateY(180deg); }
  .card-front.is-reversed img { transform: rotate(180deg); }

  .card-name {
    margin-top: 6px;
    text-align: center;
    font-size: 11px;
    line-height: 1.3;
    color: var(--muted);
    font-family: var(--font-mark);
    font-style: italic;
  }

  .card-name .rev {
    display: block;
    font-family: var(--font-thai);
    font-style: normal;
    font-size: 10px;
    letter-spacing: 0.06em;
    color: var(--gold);
  }

  .slot-wrap { display: flex; flex-direction: column; align-items: center; width: var(--slot-w); }

  /* ------------------------------------------------------------ the fan */
  /* The spread is placed in pixels along an arc, so the shell no longer scrolls
     sideways — there is nothing hanging off the edge to scroll to. */
  .fan-shell { width: 100%; overflow: visible; padding: 6px 0 14px; }
  .fan-shell[hidden] { display: none; }
  .fan-shell::-webkit-scrollbar { height: 0; }

  /* Every card is placed absolutely: --dx / --dy / --rot come from layoutFan(),
     which walks a shallow arc and adds each card's own seeded crookedness. */
  .fan {
    position: relative;
    width: 100%;
    height: var(--fan-h, 130px);
    margin: 0 auto;
  }

  .fan-card {
    --lift: 0px;
    --scale: 1;
    position: absolute;
    left: 50%;
    top: 0;
    width: var(--fan-w, 60px);
    border: 0;
    padding: 0;
    background: none;
    cursor: pointer;
    border-radius: 8px;
    transform:
      translate(calc(-50% + var(--dx, 0px)), calc(var(--dy, 0px) - var(--lift)))
      rotate(var(--rot, 0deg))
      scale(var(--scale));
    transition: transform 200ms cubic-bezier(.2, .7, .2, 1), filter 200ms ease;
    will-change: transform;
  }

  .fan-card { touch-action: none; }          /* let the card own the gesture */

  /* Lit from the same direction as the cloth, and casting onto it. The second
     shadow is the contact shadow — without it a card floats instead of lying. */
  .fan-card img {
    width: 100%;
    display: block;
    aspect-ratio: var(--back-ratio, 0.6);
    object-fit: contain;
    border-radius: 8px;
    box-shadow:
      0 10px 18px rgba(0, 0, 0, .48),
      0 2px 3px rgba(0, 0, 0, .5),
      0 0 0 1px rgba(0, 0, 0, .35);
  }

  /* Picking a card up: it rises off the cloth, straightens in the hand, and its
     shadow spreads the way a lifted thing's does. */
  /* Guarded: a touch screen has no hover to leave, so the state sticks after
     the finger lifts and strands a card above everything — including cards
     that were actually chosen. */
  @media (hover: hover) and (pointer: fine) {
    .fan-card:hover { --lift: 18px; --scale: 1.05; z-index: 60; }
    .fan-card:hover img { box-shadow: 0 18px 26px rgba(0, 0, 0, .55), 0 3px 5px rgba(0, 0, 0, .4); }
  }
  .fan-card:focus-visible { --lift: 18px; --scale: 1.05; z-index: 60; }
  .fan-card:focus-visible img { box-shadow: 0 18px 26px rgba(0, 0, 0, .55), 0 3px 5px rgba(0, 0, 0, .4); }
  .fan-card:active { --lift: 22px; --scale: 1.07; }
  .fan-card:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
  .fan-card[disabled] { opacity: .28; cursor: default; }
  /* keeps its space in the row so nothing re-flows when a card is taken */
  .fan-card.is-taken { visibility: hidden; pointer-events: none; }

  /* A chosen card LEAVES the spread — it is over in ไพ่ที่เลือก, and the space
     it held stays open so nothing reflows under a finger. It used to stay put
     and lift instead, and because a card in this spread shows only the sliver
     its neighbour does not cover, lifting it above the stack swallowed that
     neighbour whole: the next tap kept landing on the card just chosen. */
  .fan-card.is-selected { visibility: hidden; pointer-events: none; }

  /* ------------------------------------------------------ ไพ่ที่เลือก */
  /* Cards set aside on the table edge, in the order she picked them. */
  .chosen { display: grid; gap: 6px; justify-items: center; width: 100%; }
  .chosen[hidden] { display: none; }
  .chosen-label { margin: 0; font-size: 11px; color: var(--faint); }
  .chosen-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    row-gap: 8px;
    max-width: 100%;
    /* Fixed, not minimum: the row is sized for the whole spread before the
       first card lands in it, so nothing below it ever moves while she picks. */
    height: calc(var(--chosen-rows, 1) * 90px + (var(--chosen-rows, 1) - 1) * 8px);
    min-width: 120px;
    border-radius: 10px;
  }
  /* Empty, it reads as the part of the cloth these cards will come to rest on,
     not as a gap where something failed to load. */
  .chosen.is-empty .chosen-row {
    box-shadow:
      inset 0 2px 6px rgba(0, 0, 0, .4),
      inset 0 0 0 1px rgba(255, 255, 255, .035);
  }
  .chosen.is-empty .chosen-label { color: rgba(109, 95, 141, .7); }
  .chosen-card {
    --chosen-w: 54px;
    flex: 0 0 auto;
    width: var(--chosen-w);
    /* they overlap the way a small held stack does, and the first one keeps
       its whole face so the row reads as cards rather than a bar */
    margin-left: calc(var(--chosen-w) * -0.36);
    border: 0;
    padding: 0;
    background: none;
    cursor: pointer;
    border-radius: 7px;
    transform: rotate(var(--rot, 0deg));
    transition: transform 180ms cubic-bezier(.2, .7, .2, 1);
  }
  .chosen-card:first-child { margin-left: 0; }
  .chosen-card img {
    width: 100%;
    display: block;
    aspect-ratio: var(--back-ratio, 0.6);
    object-fit: contain;
    border-radius: 7px;
    box-shadow:
      0 0 0 1.5px var(--gold),
      0 6px 12px rgba(0, 0, 0, .5),
      0 1px 2px rgba(0, 0, 0, .5);
  }
  @media (hover: hover) and (pointer: fine) {
    .chosen-card:hover { transform: rotate(var(--rot, 0deg)) translateY(-4px); }
  }
  .chosen-card:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
  /* while a fan card is being dragged toward them, the row says so */
  .chosen.is-drop-target .chosen-row {
    box-shadow: inset 0 0 0 1.5px var(--gold);
    border-radius: 10px;
  }

  /* The deck is the button. Tapping the thing about to be dealt reads as the
     act itself, where a เริ่มต้น button beside it was a second object to find —
     and สับไพ่ sits with it, because shuffling is what she does just before
     she lays, not something to hunt for in the toolbar. */
  .stack.is-startable { cursor: pointer; }
  .stack.is-startable img { transition: transform 220ms cubic-bezier(.2,.7,.2,1); }
  @media (hover: hover) and (pointer: fine) {
    .stack.is-startable:hover img { transform: translateY(-6px) scale(1.02); }
  }
  .stack.is-startable:active img { transform: translateY(-2px) scale(.99); }
  .stack.is-startable:focus-visible { outline: 2px solid var(--gold); outline-offset: 6px; border-radius: 12px; }

  .deck-actions { display: flex; justify-content: center; margin-top: 10px; }
  .deck-actions[hidden] { display: none; }
  .deck-shuffle {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-family: var(--font-thai);
    font-size: 13px;
    font-weight: 600;
    color: var(--muted);
    background: var(--surface-2);
    border: 1px solid var(--edge-soft);
    border-radius: 999px;
    padding: 9px 16px;
    cursor: pointer;
  }
  .deck-shuffle svg { width: 15px; height: 15px; fill: none; stroke: var(--gold); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
  .deck-shuffle:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  @media (hover: hover) and (pointer: fine) {
    .deck-shuffle:hover { border-color: var(--edge); color: var(--ink); }
  }
  /* the icon turns with the cards it is turning */
  .deck-shuffle.is-spun svg { animation: shuffle-spin 620ms cubic-bezier(.3,.7,.3,1); }
  @keyframes shuffle-spin { to { transform: rotate(180deg); } }
  @media (prefers-reduced-motion: reduce) {
    .deck-shuffle.is-spun svg { animation: none; }
    .stack.is-startable img { transition: none; }
  }

  /* ------------------------------------------------------------ controls */
  .reveal-row { display: flex; justify-content: center; padding: 4px 16px 18px; }

  .btn {
    font-family: var(--font-thai);
    font-size: 15px;
    font-weight: 600;
    color: #2a1a05;
    background: linear-gradient(180deg, var(--gold-soft), var(--gold));
    border: 0;
    border-radius: 999px;
    padding: 12px 30px;
    cursor: pointer;
  }

  .btn:focus-visible { outline: 2px solid var(--gold-soft); outline-offset: 3px; }
  .btn[hidden] { display: none; }
  .btn[disabled] { opacity: .55; cursor: default; }

  /* เลือกแล้ว X/N — sits beside ถัดไป while the ลูกดวง is choosing */
  .pick-count { font-size: 13px; color: var(--muted); align-self: center; }

  /* จัดเรียง/เปิดไพ่ beside the main action; disabled is the querent's
     read-only view of the same state */
  .seg--mode button[disabled] { opacity: .5; cursor: default; }

  .reader {
    border-top: 1px solid var(--edge-soft);
    padding: 12px 16px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    background: rgba(0, 0, 0, 0.18);
  }

  .reader-label {
    font-size: 10px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--faint);
    width: 100%;
  }

  .seg { display: flex; border: 1px solid var(--edge); border-radius: 999px; overflow: hidden; }

  .seg button {
    font-family: var(--font-thai);
    font-size: 13px;
    color: var(--muted);
    background: transparent;
    border: 0;
    padding: 7px 14px;
    cursor: pointer;
  }

  .seg button[aria-pressed="true"] { background: var(--plum-lift); color: var(--gold-soft); }
  .seg button:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }

  /* Two counts, one pattern: type an exact number, or take a common one off
     the chips beside it. They sit together so the pair reads as one decision —
     how many cards go down, and how many she picks out of them. */
  .count { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .count-label { font-size: 12px; color: var(--muted); white-space: nowrap; }
  .count-n {
    width: 58px;
    font-family: var(--font-thai);
    /* 16px is the floor for any input on iOS: below it Safari zooms the page
       on focus and every fixed layer misplaces against the visual viewport. */
    font-size: 16px;
    font-weight: 600;
    text-align: center;
    color: var(--gold-soft);
    font-variant-numeric: tabular-nums;
    background: var(--surface-2);
    border: 1px solid var(--edge);
    border-radius: 9px;
    padding: 7px 6px;
  }
  .count-n:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }

  .chips { display: flex; gap: 6px; flex-wrap: wrap; }
  .chips button {
    font-family: var(--font-thai);
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    color: var(--muted);
    background: var(--surface-2);
    border: 1px solid var(--edge-soft);
    border-radius: 999px;
    padding: 8px 13px;
    cursor: pointer;
    font-variant-numeric: tabular-nums;
    position: relative;
  }
  /* the pill stays this size; the finger gets more */
  .chips button::after { content: ''; position: absolute; inset: -5px; border-radius: 999px; }
  .chips button[aria-pressed="true"] {
    background: linear-gradient(180deg, rgba(212, 154, 44, .18), rgba(212, 154, 44, .06));
    border-color: var(--gold);
    color: var(--gold-soft);
  }
  .chips button[disabled] { opacity: .35; cursor: default; }
  .chips button:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

  .ghost {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-thai);
    font-size: 13px;
    color: var(--muted);
    background: transparent;
    border: 1px solid var(--edge);
    border-radius: 999px;
    padding: 7px 14px;
    cursor: pointer;
  }

  /* display:inline-flex above beats the UA's [hidden] { display: none }, so every
     ghost button rendered no matter what the room state said -- the claim button
     showed inside a code room and the bar overflowed into stacked Thai. */
  /* Kill the whole bug class rather than one selector at a time: any rule that
     sets display on a class silently beats the UA's [hidden] { display: none },
     which is how the invite row and every room button ended up on screen while
     the code believed they were hidden. .names is the one deliberate exception —
     it stays displayed and slides out of frame. */
  [hidden]:not(.names) { display: none !important; }

  .ghost:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  .ghost svg { width: 14px; height: 14px; stroke: var(--gold); fill: none; stroke-width: 1.6; }


  /* the closed deck, shown while แม่หมอ sets up the round */
  .stack { position: relative; width: 150px; }
  .stack > img { aspect-ratio: var(--stack-ratio, 0.6); object-fit: contain; }
  .stack[hidden] { display: none; }
  .stack > img { width: 100%; display: block; border-radius: var(--radius); box-shadow: 0 10px 26px rgba(0,0,0,.5); position: relative; z-index: 2; }
  .stack-shadow {
    position: absolute; inset: 0; border-radius: var(--radius);
    background: var(--surface-2); border: 1px solid var(--edge-soft);
    transform: translate(5px, 5px); z-index: 1;
  }
  .stack-shadow--2 { transform: translate(10px, 10px); z-index: 0; }

  /* สับไพ่ throws a spray of cards out of the deck in an arc and pulls them
     back in. The old version only wobbled the three stack layers, which read as
     a shake rather than a shuffle. */
  .shuffle-fx { position: absolute; inset: 0; z-index: 5; pointer-events: none; }
  .shuffle-fx img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    border-radius: var(--radius);
    box-shadow: 0 8px 20px rgba(0, 0, 0, .5);
    animation: card-throw var(--dur, 720ms) cubic-bezier(.34, .12, .2, 1) var(--delay, 0ms) both;
  }
  @keyframes card-throw {
    0%   { transform: translate(0, 0) rotate(0deg) scale(1); }
    38%  { transform: translate(var(--tx), var(--ty)) rotate(var(--rot)) scale(.94); }
    72%  { transform: translate(calc(var(--tx) * .18), calc(var(--ty) * .18)) rotate(calc(var(--rot) * .2)) scale(1.02); }
    100% { transform: translate(0, 0) rotate(0deg) scale(1); }
  }
  .stack.is-shuffling > img { animation: deck-settle 720ms ease-in-out; }
  @keyframes deck-settle {
    0%, 100% { transform: translateY(0) scale(1); }
    30%      { transform: translateY(4px) scale(.985); }
    70%      { transform: translateY(-2px) scale(1.01); }
  }

  @media (prefers-reduced-motion: reduce) {
    .shuffle-fx { display: none; }
    .stack.is-shuffling > img { animation: none; }
  }

  /* a picked card flies from the pile into its slot, so the pick reads as
     moving a card rather than one card dimming and another appearing */
  .fly {
    position: fixed;
    z-index: 15;
    transform-origin: top left;
    pointer-events: none;
    transition: transform 340ms cubic-bezier(.2, .7, .2, 1);
  }
  .fly img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    border-radius: 8px;
    box-shadow: 0 10px 24px rgba(0, 0, 0, .55);
  }
  .card.is-landing { opacity: 0; }
  .fly.is-dragging { transition: none; cursor: grabbing; }

  /* The empty places light up while a card is over them. Drawn as an inset ring
     now, not a border — the dashed frame these rules used to colour is gone. */
  .slots.is-drop-target .slot.is-empty {
    background: linear-gradient(180deg, rgba(212, 154, 44, .1), rgba(212, 154, 44, .04));
    box-shadow:
      inset 0 2px 7px rgba(0, 0, 0, .45),
      inset 0 0 0 1px rgba(212, 154, 44, .42);
  }
  .slot.is-drop-hover {
    background: rgba(212, 154, 44, .16) !important;
    box-shadow:
      inset 0 2px 8px rgba(0, 0, 0, .4),
      inset 0 0 0 2px var(--gold),
      0 0 16px rgba(212, 154, 44, .3) !important;
  }

  /* A card put down on a table does not stop dead — it rocks once and settles. */
  .slot.is-settling { animation: card-settle 380ms cubic-bezier(.25, .8, .3, 1); }

  @keyframes card-settle {
    0%   { transform: translateY(-4px) scale(1.045) rotate(var(--land-rot, 0deg)); }
    55%  { transform: translateY(1px) scale(.995) rotate(calc(var(--land-rot, 0deg) * .4)); }
    100% { transform: translateY(0) scale(1) rotate(var(--land-rot, 0deg)); }
  }

  /* Nothing lands perfectly square. The angle is seeded off the card, so both
     screens show the same crooked card in the same place. */
  .slot { transform: rotate(var(--land-rot, 0deg)); }

  /* Dealing after เริ่มต้น: each card comes off the pile, drops onto the cloth and
     settles into the crooked place layoutFan() gave it. The keyframe has to end
     on that same transform or the card would snap when the animation lets go. */
  @keyframes fan-deal {
    0% {
      transform: translate(-50%, -34px) rotate(-9deg) scale(.9);
      opacity: 0;
    }
    62% {
      transform:
        translate(calc(-50% + var(--dx, 0px)), calc(var(--dy, 0px) - 5px))
        rotate(var(--rot, 0deg)) scale(1.015);
      opacity: 1;
    }
    100% {
      transform:
        translate(calc(-50% + var(--dx, 0px)), var(--dy, 0px))
        rotate(var(--rot, 0deg)) scale(1);
      opacity: 1;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .fly { transition: none; }
    .slot.is-turning { animation: none; }
    .fan-card { transition: none; }
  }

  .field { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; color: var(--muted); }
  .field select,
  .field input {
    font-family: var(--font-thai);
    font-size: 13px;
    color: var(--ink);
    background: var(--surface-2);
    border: 1px solid var(--edge);
    border-radius: 8px;
    padding: 6px 8px;
  }
  .field input { width: 62px; font-variant-numeric: tabular-nums; }
  .field select:focus-visible,
  .field input:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }

  .stack-credit { margin: 0; font-size: 11px; color: var(--faint); text-align: center; }
  .stack-credit:empty { display: none; }

  /* locked decks: the deck itself shows the lock, so the reason is visible
     where the choice is made rather than in a toast that disappears */
  .stack.is-locked > img { filter: grayscale(1) brightness(.55); }
  .lock {
    position: absolute; inset: 0; z-index: 3;
    display: grid; place-items: center;
  }
  .preview-btn {
    position: absolute;
    inset: 0;
    z-index: 4;
    background: transparent;
    border: 0;
    border-radius: var(--radius);
    cursor: pointer;
  }
  .preview-btn[hidden] { display: none; }
  .preview-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }

  .lock svg { width: 30px; height: 30px; stroke: var(--gold-soft); fill: none; stroke-width: 1.6; }
  .lock[hidden] { display: none; }
  .ghost.is-on { border-color: var(--gold); color: var(--gold-soft); }

  .score {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 9px;
    flex-wrap: wrap;
    font-size: 12px;
    color: var(--muted);
    padding: 8px 10px;
    border: 1px solid var(--edge-soft);
    border-radius: 10px;
    background: rgba(61, 26, 92, 0.28);
  }
  .score b { color: var(--gold-soft); font-variant-numeric: tabular-nums; }
  .score-sep { width: 1px; height: 12px; background: var(--edge); }
  .score-note { color: var(--faint); }
  .score-note.is-capped { color: var(--gold); }

  /* ------------------------------------------------------ fullscreen card */
  .viewer {
    position: fixed;
    inset: 0;
    z-index: 20;
    background: rgba(12, 8, 22, 0.94);
    display: grid;
    place-items: center;
    padding: 44px 20px 28px;
  }
  .viewer[hidden] { display: none; }
  .viewer-fig { margin: 0; display: flex; flex-direction: column; align-items: center; gap: 12px; }
  .viewer-fig img {
    max-width: min(78vw, 380px);
    max-height: 72vh;
    width: auto;
    border-radius: 12px;
    box-shadow: 0 18px 46px rgba(0, 0, 0, 0.6);
  }
  .viewer-fig img.is-reversed { transform: rotate(180deg); }
  .viewer-fig figcaption {
    font-family: var(--font-mark);
    font-style: italic;
    font-size: 15px;
    color: var(--ink);
    text-align: center;
  }
  .viewer-fig .rev { display: block; font-family: var(--font-thai); font-style: normal; font-size: 12px; color: var(--gold); }
  .viewer-close {
    position: absolute;
    top: 14px;
    right: 16px;
    background: transparent;
    border: 1px solid var(--edge);
    border-radius: 999px;
    padding: 7px;
    cursor: pointer;
    line-height: 0;
  }
  .viewer-close svg { width: 16px; height: 16px; stroke: var(--muted); fill: none; stroke-width: 1.8; }
  .viewer-close:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

  .slot-open {
    position: absolute;
    inset: 0;
    z-index: 3;
    background: transparent;
    border: 0;
    border-radius: var(--radius);
    cursor: zoom-in;
    padding: 0;
  }
  .slot-open:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

  /* per-card reveal: the face-down card itself is the button */
  .slot-flip {
    position: absolute;
    inset: 0;
    z-index: 3;
    background: transparent;
    border: 0;
    border-radius: var(--radius);
    cursor: pointer;
    padding: 0;
  }
  .slot-flip:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

  /* จัดเรียง: a laid card can be picked up again, so it must own the gesture */
    /* native image drag steals the pointer mid-swap on a mouse */
  .slot img, .fan-card img { -webkit-user-drag: none; user-select: none; }
.slot.is-grabbable,
  .slot-open.is-grabbable { touch-action: none; cursor: grab; }

  /* the swap target lights on the card itself — the slot's inset ring would
     hide under the card art */
  .slot.is-drop-hover .card-side img {
    box-shadow:
      0 0 0 2px var(--gold),
      0 0 16px rgba(212, 154, 44, .4);
  }

  /* ---------------------------------------------------------- save image */
  .reveal-row { gap: 10px; flex-wrap: wrap; }
  .btn--ghost {
    background: transparent;
    color: var(--gold-soft);
    border: 1px solid var(--gold);
  }

  .save-modal { position: fixed; inset: 0; z-index: 22; display: grid; place-items: center; padding: 20px; }
  .save-modal[hidden] { display: none; }
  .save-scrim { position: absolute; inset: 0; background: rgba(10, 6, 18, .8); }
  .save-card {
    position: relative;
    width: min(340px, 100%);
    max-width: calc(100vw - 32px);
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 18px;
    background: var(--surface);
    border: 1px solid var(--edge);
    border-radius: 16px;
  }
  .save-title { margin: 0; font-size: 15px; font-weight: 600; color: var(--ink); }
  .save-label { font-size: 11px; color: var(--faint); }
  .save-input {
    font-family: var(--font-thai);
    /* >= 16px, or iOS zooms the page on focus and the fixed modal renders
       half off-screen against the visual viewport (real-iPhone report). */
    font-size: 16px;
    color: var(--ink);
    background: var(--surface-2);
    border: 1px solid var(--edge);
    border-radius: 10px;
    padding: 10px 12px;
  }
  .save-input:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
  .save-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
  /* The role picker offers two honest choices, not confirm/cancel — split the
     row evenly so neither button reads as the default. */
  .save-actions--split > button { flex: 1 1 0; }

  /* -------------------------------------------------------- brand strip */
  /* Persistent for the whole session and in every state, by design: querents
     screenshot readings, and an always-on strip rides every screenshot. */
  .strip {
    position: sticky;
    bottom: 0;
    /* Cards are transformed, which gives them their own stacking context and
       paints them over a sticky element that has no z-index -- the brand strip
       was disappearing behind the deck. It must survive every screenshot. */
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    /* viewport-fit=cover means the home indicator overlaps the last row. */
    padding: 11px 16px calc(11px + env(safe-area-inset-bottom, 0px));
    background: #120c22;
    border-top: 1px solid var(--edge);
  }

  .mark {
    font-family: var(--font-mark);
    font-style: italic;
    font-size: 19px;
    letter-spacing: -0.01em;
    color: var(--gold-soft);
  }

  .strip-rule { width: 1px; height: 18px; background: var(--edge); }
  .strip-copy { font-size: 12px; color: var(--muted); }
  .strip a { color: inherit; text-decoration: none; }
  .strip a:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

  @media (prefers-reduced-motion: reduce) {
    .card { transition-duration: 1ms; }
    .fan-card { transition: none; }
  }

/* ---- shared room ---------------------------------------------------------
   One strip above the mat carrying the 4-digit code and who is connected. The
   pair agrees on the number on the call and both type the same digits — the
   first one in creates the room. Deliberately quiet — it sits on screen in
   front of a paying customer for the whole reading. */
.room-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px 0;
  min-height: 30px;
  font-size: 13px;
  color: var(--ink-soft);
  /* Thai has no spaces, so a flex child that is allowed to shrink breaks a label
     one glyph per line. Nothing in this bar shrinks; it scrolls instead. */
  overflow-x: auto;
  scrollbar-width: none;
}
.room-bar::-webkit-scrollbar { display: none; }
.room-bar > * { flex: 0 0 auto; white-space: nowrap; }

.room-code-wrap {
  display: none;
  align-items: baseline;
  gap: 6px;
}

body.is-room .room-code-wrap { display: inline-flex; }
/* A chat-keyed room has no code to show — the chat is the code. */
body.is-chat-room .room-code-wrap { display: none; }

.room-code-label {
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  opacity: .7;
}

.room-code {
  font-size: 19px;
  letter-spacing: .16em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.room-status { opacity: .85; }

.room-btn {
  padding: 4px 10px;
  font-size: 12.5px;
}

/* The one control on the board that แม่หมอ has to find on her first reading,
   in a bar that is otherwise deliberately quiet. */
.room-btn--lock {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border-color: var(--gold, #c9a24a);
  color: var(--ink);
}

/* .ghost svg already paints every icon in a room button gold, and it outranks a
   single class — so this has to name the button, or the padlock goes gold on
   gold the moment the locked state fills the button with gold. */
.room-btn--lock .lock-ico {
  width: 13px;
  height: 13px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* One icon, two shackles: the closed one is the state, the open one is the
   offer. Swapping paths beats swapping icons — the body never moves, so the
   button does not jump under a finger that is about to press it again. */
.room-btn--lock .lock-shackle { display: none; }
.room-btn--lock[aria-pressed="true"] .lock-shackle { display: inline; }
.room-btn--lock[aria-pressed="true"] .lock-shackle-open { display: none; }

/* Locked is a live state during a reading, not a setting — it has to read from
   across a phone held by someone else. */
.room-btn--lock[aria-pressed="true"] {
  background: var(--gold, #c9a24a);
  border-color: var(--gold, #c9a24a);
  color: #17110c;
}

.room-btn--lock[aria-pressed="true"] .lock-ico { stroke: #17110c; }

/* On a narrow phone the bar scrolls, and a control she needs mid-call must not
   be the thing that scrolled away. The padlock alone carries the state; the
   aria-label keeps the meaning for anyone not reading pixels. */
@media (max-width: 430px) {
  .room-btn--lock .lock-text { display: none; }
  .room-btn--lock { padding-left: 7px; padding-right: 7px; }
  .room-btn--lock .lock-ico { width: 15px; height: 15px; }
}

/* A locked board must not feel broken to the ลูกดวง — the cards are visibly
   held, not dead. The server refuses the pick either way; this is so she does
   not tap six times wondering why nothing happens. */
body.is-locked.is-role-querent .fan-card,
body.is-locked.is-role-querent .stack { pointer-events: none; }
body.is-locked.is-role-querent .fan { opacity: .62; }

/* Sharing is the step that decides whether the ลูกดวง ever reaches the board,
   so it reads as the action and the copy buttons read as the fallback. */
.room-btn--share {
  border-color: #06c755;
  color: var(--ink);
}

/* In a room the ลูกดวง only picks: the deck plate, the tool bar and the
   reader's controls all belong to แม่หมอ's screen. The server enforces this;
   hiding it here is so the querent's board stays a board. */
body.is-room.is-role-querent .mt-ds-plate,
body.is-room.is-role-querent .tool-row,
body.is-room.is-role-querent .reader { display: none; }

/* ---------------------------------------------------------- room dialog */
/* The number is read out loud on a call, so it is the object on this screen,
   not a form field: it sits in the same recess the cloth takes when a card is
   lifted off it, with four notches marking where the digits land. */
.join-card { gap: 12px; }
.join-lede { margin: -2px 0 2px; font-size: 12px; line-height: 1.55; color: var(--muted); }

.join-dial {
  position: relative;
  display: block;
  padding: 10px;
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(0, 0, 0, .3), rgba(0, 0, 0, .16));
  box-shadow:
    inset 0 2px 7px rgba(0, 0, 0, .55),
    inset 0 0 0 1px rgba(255, 255, 255, .045),
    0 1px 0 rgba(255, 255, 255, .04);
}

/* Four seats for four digits, laid out like the depressions the cloth takes
   when a card is lifted off it. The input itself is invisible on top of them:
   a caret cannot be aligned to a grid this wide, and the digits it holds are
   painted into the cells instead. */
.join-cells { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.join-cells i {
  display: grid;
  place-items: center;
  aspect-ratio: .78;
  border-radius: 8px;
  background: rgba(255, 255, 255, .03);
  box-shadow: inset 0 0 0 1px rgba(212, 154, 44, .18);
  font-family: var(--font-thai);
  font-style: normal;
  font-size: 30px;
  font-weight: 600;
  color: var(--gold-soft);
  font-variant-numeric: tabular-nums;
}
.join-cells i:empty::after { content: '·'; color: var(--faint); font-size: 22px; }
.join-dial.is-focused .join-cells i.is-next {
  box-shadow: inset 0 0 0 1.5px var(--gold);
  background: rgba(212, 154, 44, .08);
}

.join-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* 16px is the floor for any input on iOS: below it Safari zooms the page on
     focus and every fixed layer misplaces against the visual viewport. */
  font-size: 16px;
  color: transparent;
  caret-color: transparent;
  background: transparent;
  border: 0;
  padding: 0;
  text-align: center;
  cursor: pointer;
}
.join-input::selection { background: transparent; }
.join-input:focus-visible { outline: none; }

.join-reroll {
  align-self: center;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  padding: 6px 12px;
}
.join-reroll svg { width: 13px; height: 13px; fill: none; stroke: var(--gold); stroke-width: 2; stroke-linecap: round; }

.join-legend { margin: 6px 0 0; font-size: 11px; color: var(--faint); }

/* Who you are at this table is a choice made once, before the cards — so it
   is two seats to sit in, not a toggle to flip. */
.role-choice { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.role-opt {
  display: grid;
  justify-items: center;
  gap: 4px;
  padding: 12px 8px 10px;
  border-radius: 12px;
  background: var(--surface-2);
  border: 1px solid var(--edge-soft);
  color: var(--muted);
  font-family: var(--font-thai);
  cursor: pointer;
  text-align: center;
}
.role-opt .role-ico { width: 34px; height: 26px; fill: none; stroke: currentColor; stroke-width: 1.6; opacity: .75; }
.role-name { font-size: 14px; font-weight: 600; color: var(--ink); }
.role-note { font-size: 10.5px; line-height: 1.4; color: var(--faint); }
.role-opt[aria-checked="true"] {
  background: linear-gradient(180deg, rgba(212, 154, 44, .16), rgba(212, 154, 44, .05));
  border-color: var(--gold);
  color: var(--gold-soft);
}
.role-opt[aria-checked="true"] .role-ico { opacity: 1; }
.role-opt[aria-checked="true"] .role-name { color: var(--gold-soft); }
.role-opt:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.join-error {
  margin: 6px 0 0;
  min-height: 18px;
  font-size: 13px;
  color: #e79b8a;
}

.room-invite {
  margin: 4px 14px 0;
  font-size: 12px;
  color: var(--ink-soft);
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.room-invite a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
  /* One line, always. A wrapped URL costs three rows on a phone and pushes
     แม่หมอ's own controls off the bottom of the board. */
  flex: 1 1 150px;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
