﻿/* DegenParty casino shell - the sidebar + topbar chrome that wraps every public
   page (home, account, and the coin games as they land).

   Layout is modelled on RuneChat: a fixed left rail of categorised links, a slim
   top bar carrying the coin balance and account pill, and a wide scrolling
   content column of card rows. Colours come entirely from /theme.css tokens, so
   the Branding panel still drives the whole thing - nothing here hardcodes a
   palette beyond the neutral tints that theme.css itself flips for light mode.

   Pages opt in with <body class="dp-shell"> and provide three empty hosts:
     <aside id="dpSidebar">  filled by shell.js
     <header id="dpTop">     filled by shell.js
     <main class="dp-main">  the page's own content

   This file deliberately re-declares the handful of body/.wrap rules that
   style.css sets for the old narrow single-column pages, because account.html
   loads BOTH stylesheets (it needs style.css's card/avatar/form components). */

:root {
  --dp-rail:      248px;   /* sidebar width, expanded */
  --dp-rail-thin:  64px;   /* sidebar width, collapsed to icons */
  --dp-top:        60px;   /* top bar height */
  /* Phone tab bar. Zero above the drawer breakpoint, where there isn't one -
     declared anyway because the drawer height and --dp-chat-offset both do
     arithmetic with it, and calc() with an undefined var is an invalid
     declaration, not a zero. */
  --dp-botnav-h:    0px;

  /* Poppins carries no emoji, so anywhere emoji can appear names this stack as
     a FALLBACK after the text font. Never as the whole family, and never via
     font-variant-emoji - see .dp-chat-text. */
  --dp-emoji: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', 'Twemoji Mozilla';

  /* Big wins. Fixed, not theme tokens - see the badge rules for why the chat
     marks do not follow branding.

     Two of them, because the gold does two jobs. --dp-win is the MARK: the
     border, the trophy, the ground under the label. --dp-win-ink is TEXT drawn
     on those grounds. On a dark theme one colour does both; on a light one it
     cannot, because the ink has to be dark enough to read against a ground the
     mark has just tinted. */
  --dp-win:     #F2C14E;
  --dp-win-ink: #F2C14E;
}
/* This gold on a light theme is about 1.7:1 against the panel - a colour you
   can see but not read, and the win row puts it on a name and a label rather
   than only on an icon. Light mode gets a gold dark enough for text and a
   slightly lighter one for the marks, so the row still reads as gold instead of
   as a brown box. branding.js sets data-mode from the active theme, so this
   follows whatever the streamer picked. */
:root[data-mode="light"] {
  --dp-win:     #9A6D00;
  --dp-win-ink: #5C4200;
}

/* ---- frame ----------------------------------------------------------------
   Three regions, two rows:

     +-------------------------------------------------+
     |  top   (logo + collapse | balance | account)     |   full bleed
     +--------------+----------------------------------+
     |  rail        |  col  (the page, scrolls)         |
     +--------------+----------------------------------+

   The top bar spans BOTH columns - it is the site's masthead, not the content
   column's header - which is why shell.js lifts #dpTop out of .dp-col and makes
   it a direct child of <body>. Only a direct grid child can claim a grid area,
   and a bar that stops where the rail ends is a bar that reads as belonging to
   the page instead of to the site. */

body.dp-shell {
  /* style.css gives body 64px of padding for its floating top-right bar; the
     shell owns that chrome instead, so reclaim the space. */
  padding: 0;
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text-body);
  font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  display: grid;
  grid-template-columns: var(--dp-rail) minmax(0, 1fr);
  /* The rows carry the viewport height (rather than the body carrying it) so
     the mobile chat's padding-bottom on <body> still adds space UNDER the grid
     instead of being swallowed by a fixed height. dvh second as the upgrade:
     100vh on a phone is the viewport without the address bar. */
  grid-template-rows: var(--dp-top) calc(100vh - var(--dp-top));
  grid-template-rows: var(--dp-top) calc(100dvh - var(--dp-top));
  grid-template-areas: "top top" "rail col";
}
body.dp-shell.rail-thin { grid-template-columns: var(--dp-rail-thin) minmax(0, 1fr); }

/* The content column scrolls; the rail scrolls separately; the bar does neither. */
body.dp-shell > .dp-col {
  grid-area: col;
  min-width: 0;
  min-height: 0;   /* a grid item defaults to min-content - without this it refuses to shrink and the whole page scrolls instead of the column */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  /* Two soft brand glows across the top of the page. `local` (not the default
     `scroll`) attaches them to the CONTENT rather than to this element's frame,
     so they sit behind the hero and scroll away with it instead of following
     you down a game page like a stuck highlight. */
  background:
    radial-gradient(900px 400px at 16% -10%, rgba(var(--grad-rgb-a), 0.11), transparent 70%),
    radial-gradient(780px 360px at 86% -14%, rgba(var(--grad-rgb-b), 0.11), transparent 70%);
  background-attachment: local, local;
  background-repeat: no-repeat, no-repeat;
}

.dp-main { flex: 1 1 auto; padding: 22px 26px 60px; min-width: 0; }
.dp-main > .wrap {
  /* style.css pins .wrap to 560px + zoom for the old mobile-first pages. Inside
     the shell we want a real content column at full scale. */
  max-width: 1180px;
  zoom: 1;
  margin: 0 auto;
}
@media (max-width: 600px) { .dp-main > .wrap { zoom: 1; } }

/* ---- sidebar -------------------------------------------------------------- */

#dpSidebar {
  grid-area: rail;
  /* Height comes from the grid row (which is viewport-minus-bar), so the rail
     no longer sizes itself - it just must not refuse to shrink. */
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  /* Older iOS needs this for momentum scrolling inside a fixed element. */
  -webkit-overflow-scrolling: touch;
  /* Don't hand the scroll to the page behind when the rail hits its end. */
  overscroll-behavior: contain;
  /* --bg, not --surface: the rail is now the GROUND the nav groups sit on
     rather than a panel of its own. Each section below is a --surface card, so
     the rail matches the page and the groups match the top bar and every other
     box on the site. No border-right - the colour step is the edge. */
  background: var(--bg);
  display: flex;
  flex-direction: column;
  gap: 0;
  /* The brand used to sit here in a sticky header row; it lives in the top bar
     now, so the first card needs its own air at the top instead. */
  padding: 10px 0 24px;
  scrollbar-width: thin;
}
#dpSidebar::-webkit-scrollbar { width: 6px; }
#dpSidebar::-webkit-scrollbar-thumb { background: var(--soft-strong); border-radius: 99px; }

/* ---- rail promo cards ------------------------------------------------------
   Two shortcuts at the head of the rail, on gradient. They point at pages the
   rail already lists further down - the value is that they're above the fold on
   every page, not that they're new doors. Two is the cap: a column of promo
   cards is a column nobody reads. */
.dp-promos { display: flex; flex-direction: column; gap: 8px; padding: 0 10px 10px; }
.dp-promo {
  position: relative; overflow: hidden;
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: 2px 10px;
  border-radius: var(--radius); padding: 11px 13px;
  text-decoration: none; color: #fff;
  background: var(--grad);
}
/* The second card runs the sweep the other way, so two stacked cards read as a
   pair rather than as the same card printed twice. */
.dp-promo.tone-b { background: linear-gradient(135deg, var(--grad-b), var(--grad-a)); }
/* Winna's own blue, NOT the house gradient. This is the one card that leaves
   the site for a real-money casino, and it should not be able to pass for
   another page of DegenParty. */
.dp-promo.tone-winna { background: linear-gradient(135deg, #13b9f0, #0a5f95); }
/* The glyph is scenery: oversized, clipped by the card's bottom-right corner,
   and behind the words. A real illustration can take its place later without
   touching the layout. */
.dp-promo-art {
  position: absolute; right: -10px; bottom: -14px;
  font-size: 60px; color: rgba(255,255,255,0.18); pointer-events: none;
}
/* The SVG variant needs an explicit box - a glyph gets its size from font-size,
   a drawing does not. Inset a little further because the W's own path fills its
   viewBox edge to edge, where a Phosphor icon carries its own padding. */
/* A touch brighter than the icon-font glyphs: the W is a thin, open shape, so
   the same alpha that reads as a watermark on a solid gift icon all but
   disappears on this one. */
svg.dp-promo-art {
  width: 62px; height: 62px; right: -6px; bottom: -10px;
  color: rgba(255,255,255,0.26);
}
.dp-promo-cta > i { font-size: 10px; margin-left: 3px; vertical-align: -1px; }
.dp-promo-kicker {
  grid-column: 1; font-size: 10px; font-weight: 700; letter-spacing: 0.5px;
  text-transform: uppercase; color: rgba(255,255,255,0.75);
}
.dp-promo-title { grid-column: 1; font-size: 14.5px; font-weight: 800; line-height: 1.2; }
.dp-promo-cta {
  grid-column: 1; justify-self: start; margin-top: 7px;
  font-size: 10.5px; font-weight: 800;
  background: rgba(0,0,0,0.3); border-radius: var(--radius-pill); padding: 4px 10px;
}
.dp-promo:hover .dp-promo-cta { background: rgba(0,0,0,0.45); }

/* Nav sections. Each one is a card on the rail rather than a run of rows under
   a heading - which is what turns six headings into six things you can see the
   shape of at a glance, instead of one long column you have to read to find the
   breaks in. */
.dp-navsec {
  background: var(--surface);
  border-radius: var(--radius-lg);
  margin: 0 10px 8px;
  padding: 10px 8px 8px;
}
/* Folded shut, the card is just its heading - so the bottom padding that was
   holding a list up goes with the list. */
.dp-navsec.shut { padding-bottom: 2px; }
/* The heading is the fold control for its section. Full-width button so the
   whole strip is the target, not just the eight characters of the word. */
.dp-navsec-h {
  display: flex; align-items: center; gap: 9px; width: 100%;
  background: transparent; border: 0; cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 800;
  color: var(--text); padding: 3px 8px 8px; text-align: left;
}
.dp-navsec-h > i:first-child { font-size: 16px; color: var(--primary); flex: 0 0 auto; }
.dp-navsec-h > span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.dp-navsec-caret {
  flex: 0 0 auto; font-size: 12px; color: var(--text-faint);
  transition: transform 0.16s ease;
}
.dp-navsec-h:hover > span { color: var(--text); }
.dp-navsec-h:hover .dp-navsec-caret { color: var(--text-muted); }
.dp-navsec.shut .dp-navsec-caret { transform: rotate(180deg); }
.dp-navsec.shut .dp-nav { display: none; }

.dp-nav { display: flex; flex-direction: column; gap: 2px; }
.dp-navlink {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 10px; border-radius: 9px;
  color: var(--text-body); text-decoration: none;
  font-size: 13.5px; font-weight: 600; white-space: nowrap;
  border: 1px solid transparent;
  transition: background 0.12s, color 0.12s;
}
.dp-navlink > i { font-size: 17px; color: var(--text-muted); flex: 0 0 22px; text-align: center; }
/* A drawn glyph in place of an icon-font one. Painted through a mask so the SVG
   supplies only the SHAPE and the colour comes from here - which is what lets it
   pick up the muted grey, the hover accent and the current-row accent exactly
   like the font icons it sits with. Sized a shade larger because these shapes
   are drawn to fill their box, where a font glyph carries its own padding. */
.dp-navart {
  flex: 0 0 22px; height: 19px; align-self: center;
  background: var(--text-muted);
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
  transition: background 0.12s;
}
.dp-navlink:hover { background: var(--soft); color: var(--text); }
.dp-navlink:hover > i { color: var(--primary); }
.dp-navlink:hover > .dp-navart { background: var(--primary); }
/* The current page wears the brand gradient as a tinted fill, and nothing else.
   No bar down the left edge and no coloured border: one row in the rail is
   filled and the rest are not, which is already the whole message. */
.dp-navlink.on {
  background: var(--grad-soft); color: var(--text);
}
.dp-navlink.on > i { color: var(--text); }
.dp-navlink.on > .dp-navart { background: var(--text); }
.dp-navlink .dp-navlabel { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* A variant hung under its parent - Keno Masterpiece under Keno. Indented and
   joined by an elbow, so the two rows read as one game and its second version
   rather than as two games that happen to share a word.

   The elbow is drawn on the ROW, not between rows: it starts above this row's
   top edge (into the 2px the list gaps with) and stops at its own middle, which
   is where the icon sits - so the line lands on the glyph it belongs to and the
   next row down is untouched. Two variants under one parent stack their elbows
   into a continuous run for free.

   Slightly smaller and dimmer than a top-level row: the parent should still be
   the one your eye lands on. */
.dp-navlink.dp-navsub {
  position: relative;
  margin-left: 18px; padding-left: 12px;
  font-size: 12.5px; color: var(--text-muted);
}
.dp-navlink.dp-navsub::before {
  content: ''; position: absolute; left: -1px; top: -4px; bottom: 50%;
  width: 9px; pointer-events: none;
  /* --line is the 10%-white a card border is drawn in - too quiet for a stroke
     9px long that has to read as a JOIN. Taken off the faint text colour
     instead, which is the dimmest thing on the rail that is still meant to be
     looked at, and which flips with the light theme on its own. */
  border-left: 1.5px solid color-mix(in srgb, var(--text-faint) 55%, transparent);
  border-bottom: 1.5px solid color-mix(in srgb, var(--text-faint) 55%, transparent);
  border-bottom-left-radius: 7px;
}
.dp-navlink.dp-navsub > i { font-size: 15px; flex: 0 0 18px; }
.dp-navlink.dp-navsub:hover,
.dp-navlink.dp-navsub.on { color: var(--text); }

/* "Soon" / count chips riding on the right of a nav row */
.dp-chip {
  flex: 0 0 auto; font-size: 9.5px; font-weight: 800; letter-spacing: 0.6px;
  text-transform: uppercase; padding: 2px 7px; border-radius: var(--radius-pill);
  background: var(--soft-strong); color: var(--text-faint);
}
.dp-chip.live { background: color-mix(in srgb, var(--success) 22%, transparent); color: var(--success); }
/* New is green, here and on the tiles. It was the accent, which is the colour
   every other piece of chrome on the rail already uses - so the one row that
   was meant to catch the eye was wearing the same paint as its neighbours. */
.dp-chip.hot  { background: color-mix(in srgb, var(--success) 20%, transparent); color: var(--success); }
/* Real money. Filled rather than tinted like the two above it, because it is
   not a status - it is the answer to "which of these is worth actual cash", and
   at 9.5px a tint is a whisper. It also has to be distinguishable at a glance
   from the green New tag two sections up the same rail, and filled-vs-tinted
   does that without inventing a second green. */
.dp-chip.cash {
  padding-left: 5px;
  background: var(--success); color: #04210f;
}
/* Aligned with vertical-align rather than a flex row on purpose. The collapsed
   rail switches .dp-chip to `display: revert` at phone widths, on a selector
   heavier than this one - so a display set here would lose exactly there, and
   the glyph would drop half a line. Nothing here depends on display. */
.dp-chip.cash > i { font-size: 11px; vertical-align: -1px; margin-right: 1px; }

/* Collapsed rail: icons only. Labels and section headings fold away, and the
   nav rows centre their glyph. The way back out is the toggle in the top bar,
   which is unaffected by any of this - that's the point of it living up there. */
body.dp-shell.rail-thin .dp-navlabel,
body.dp-shell.rail-thin .dp-chip,
body.dp-shell.rail-thin .dp-promos,
body.dp-shell.rail-thin .dp-navsec-h { display: none; }
/* Folded sections open back up when the rail collapses: at 64px the heading -
   the only way to unfold one - isn't on screen, so a shut section would be a
   row of games with no way back to them. */
body.dp-shell.rail-thin .dp-navsec.shut .dp-nav { display: flex; }
body.dp-shell.rail-thin .dp-navlink { justify-content: center; padding: 10px 0; }
body.dp-shell.rail-thin .dp-navlink > i { flex: 0 0 auto; }
/* At 64px every row is a centred glyph with no label, so a nested row has no
   parent to be nested under on screen - the indent and its elbow come off and
   it sits in the column like everything else. */
body.dp-shell.rail-thin .dp-navlink.dp-navsub {
  margin-left: 0; padding-left: 0; font-size: inherit;
}
body.dp-shell.rail-thin .dp-navlink.dp-navsub::before { display: none; }
body.dp-shell.rail-thin .dp-navlink.dp-navsub > i { font-size: 17px; }
/* At 64px the card is most of the rail, so it gives back the side room. The
   border-top that used to separate sections here is gone with it - the cards
   are the separation now. */
body.dp-shell.rail-thin .dp-navsec { margin: 0 6px 6px; padding: 6px 4px; }

/* ---- top bar -------------------------------------------------------------- */

/* One long bar across the whole window. Three zones, laid out as a grid rather
   than a flex row with a spacer, because the middle zone has to be centred on
   the WINDOW - not on whatever is left after the two side clusters have taken
   their room. The balance is the thing people look for, and a balance that
   drifts left when a username gets longer is a balance you have to hunt for.
   The 1fr side columns are what pin it: they're equal by definition. */
#dpTop {
  grid-area: top;
  height: var(--dp-top);
  /* Above the mobile drawer (60) and its scrim (55): the bar stays put and the
     rail slides out from under it, so the burger you opened it with is still
     there to close it. */
  position: relative; z-index: 70;
  /* No border, no line, no shadow. The bar is --surface and the page behind it
     is --bg; three points of lightness is the whole separation, and that is
     deliberate - a rule under the bar cuts the window in half, where the two
     tones read as one dark chrome with the page floating on it. */
  background: var(--surface);
  /* Flex, not a 1fr/auto/1fr grid. The grid centred the balance on the WINDOW,
     which only looks centred when the two side clusters are the same width -
     and they never are. The logo is a wordmark; the right-hand side is an
     avatar. Measured on the front page, logged in, the gap either side of the
     pill came out 8/99 at 420px, 101/214 at 760 and 393/507 at 1440: on a phone
     the balance was literally touching the logo.
     So the middle takes whatever is left over and centres itself in THAT -
     45/45, 158/157, 280/280 and 450/450 at the same four widths. The pill sits
     midway between the logo and the right-hand cluster at every size, which is
     what "centred" means when you are looking at the bar rather than measuring
     it. The logo gets its natural width back as a side effect: the old 1fr
     track was squeezing it to 142px on a 420px phone, against the 165 it
     wants. */
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 16px;
}

.dp-top-left  { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 0 1 auto; }
/* flex:1 whether or not it has anything in it - logged out the balance is
   hidden, and an empty middle still has to hold the login button over on the
   right rather than letting it drift inward. */
.dp-top-mid   { display: flex; align-items: center; gap: 9px; flex: 1 1 auto; min-width: 0; justify-content: center; }
.dp-top-right { display: flex; align-items: center; gap: 9px; flex: 0 0 auto; justify-content: flex-end; }

/* The two menu buttons. Only one is ever on screen: the collapse toggle above
   the drawer breakpoint (where the rail is a column that can be narrowed) and
   the burger below it (where the rail is a drawer that can only be opened or
   shut). Same slot, same size, so the bar doesn't reflow across the break. */
.dp-iconbtn {
  position: relative; flex: 0 0 auto;
  background: var(--raised); border: 1px solid var(--line); color: var(--text-muted);
  width: 34px; height: 34px; border-radius: 10px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; font-size: 16px;
  text-decoration: none;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.dp-iconbtn:hover { color: var(--text); background: var(--soft-strong); }
.dp-iconbtn[hidden] { display: none; }
/* The gift is the one button in the row that is an offer rather than a tool, so
   it is the one that gets colour. */
.dp-top-gift { color: var(--primary); }
/* Unread mentions. A dot, not a count - see chatRenderUnread in shell.js. */
.dp-top-dot {
  position: absolute; top: 5px; right: 5px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--danger); border: 2px solid var(--surface);
  box-sizing: content-box;
}
.dp-top-dot[hidden] { display: none; }
.dp-burger { display: none; }   /* mobile only - see the drawer breakpoint */
/* The phone tab bar, same deal in reverse - built on every page, shown only
   below the drawer breakpoint. See the block at the end of the 900px query. */
.dp-botnav { display: none; }

/* ---- the bell ------------------------------------------------------------
   Announcements: a challenge opening, a raffle settling, the stream going live,
   and anything typed by hand in the admin panel. Unlike the gift and the chat
   shortcuts beside it, this one stays on a phone - the whole point of it is
   reaching somebody who is NOT already looking at the site, and the top bar is
   the only chrome that is on every page. */
.dp-bell { position: relative; flex: 0 0 auto; }
/* The count, not a bare dot. "3 things happened" is a different message from
   "something happened", and the number is the one that gets it opened. */
.dp-bell-n {
  position: absolute; top: -6px; right: -6px;
  /* 16px, and a circle at one digit rather than a 19px rounded rectangle. The
     first version was 55% of the button's width and only 2px clear of its edge,
     so it sat ON the bell rather than beside it - the number was competing with
     the icon it was counting for.
     A pill again at two digits, which is what min-width plus padding gives:
     "12" cannot be a circle without shrinking the type past reading. */
  min-width: 16px; height: 16px; padding: 0 3px;
  box-sizing: border-box; border-radius: 999px;
  background: var(--danger); color: #fff;
  font-size: 9.5px; font-weight: 900; letter-spacing: -0.2px;
  /* Centred by flex, not by line-height: the 2px ring is inside the 16px box
     (border-box), so a 16px line would sit 2px low. */
  display: inline-flex; align-items: center; justify-content: center;
  /* The bar's colour, not the button's - at this offset most of the ring is
     over the bar, and it is what makes the badge read as floating above the
     button rather than printed on it. */
  border: 2px solid var(--surface);
}
.dp-bell-n[hidden] { display: none; }

.dp-bell-drop {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 90;
  width: min(340px, calc(100vw - 24px));
  max-height: min(420px, 70vh); overflow-y: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 6px;
  box-shadow: 0 16px 40px rgba(0,0,0,0.5);
}
.dp-bell-drop[hidden] { display: none; }
.dp-bell-head {
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.7px; text-transform: uppercase;
  color: var(--text-faint); padding: 7px 9px 8px;
}
.dp-bell-empty {
  padding: 22px 16px; text-align: center;
  font-size: 12.5px; line-height: 1.6; color: var(--text-muted);
}

.dp-bell-row {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 9px; border-radius: 9px; text-decoration: none;
}
a.dp-bell-row:hover { background: var(--raised); }
.dp-bell-ic { font-size: 15px; margin-top: 2px; flex: 0 0 auto; color: var(--text-muted); }
.dp-bell-ic.win       { color: #ffc531; }
.dp-bell-ic.challenge { color: var(--primary); }
.dp-bell-ic.live      { color: var(--danger); }
/* Green rather than the trophy gold that .win uses: this row is not "somebody
   won something", it is "this is running right now" - the same thing the
   bottom bar and the tournament page say in the same colour. */
.dp-bell-ic.tournament { color: var(--success); }
.dp-bell-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.dp-bell-t { font-size: 13px; font-weight: 800; color: var(--text); line-height: 1.35; }
.dp-bell-b { font-size: 12px; color: var(--text-muted); line-height: 1.45; }
.dp-bell-w { font-size: 11px; color: var(--text-faint); font-weight: 700; }
/* Addressed to you by name. A left edge rather than a background, so a row that
   is also a link still gets its hover. */
.dp-bell-row.mine {
  background: rgba(var(--accent-rgb, 0,155,214), 0.07);
  box-shadow: inset 2px 0 0 var(--primary);
}


/* ---- the drawer's profile block ------------------------------------------
   Built into the rail on every page and shown only in the DRAWER, below the
   breakpoint. Above it the rail is a permanent column and the account pill is
   already in the bar an inch away, so a second copy of your face would be two
   of the same thing on one screen. */
.dp-me { display: none; }
/* Not a card. It sits ON the drawer rather than in a box of its own: a bordered
   panel above two more bordered panels made the top of the drawer read as three
   competing objects, and this one is a label for where you are, not something
   to act on. The hairline under it is the whole separation.
   The 10px side padding is the rail's own inset - the promos and every nav
   section already sit on it, and without it this was the one thing in the
   drawer touching both edges. */
.dp-me-who {
  display: flex; align-items: center; gap: 12px; text-decoration: none;
  padding: 4px 2px 12px;
  border-bottom: 1px solid var(--line);
}
/* No width here. The hexagon sizes itself from the number renderMe passes to
   hexAvatar, and a second size on the box around it is a second source of truth
   for one measurement - which is exactly how a 44px hexagon ended up hanging
   out of a 36px slot and over the name. The box is whatever is in it. */
.dp-me-av { flex: 0 0 auto; display: flex; line-height: 0; }
.dp-me-av img { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; display: block; }
/* The level chip, tucked into the bottom-right corner OVER the picture rather
   than hanging off it. hexAvatar's own proportions are drawn for the 44px+ this
   used to be; at 36 the default chip is a third of the avatar and reads as a
   badge with a face behind it. Smaller, and pulled inside the hexagon's edge.
   The ring in --bg is what keeps three digits legible over a photograph - the
   chip is a gradient and so is half the faces it lands on. */
.dp-me-av .dp-hex-lv {
  right: -2px; bottom: -1px;
  min-width: 15px; height: 15px; padding: 0 3px;
  font-size: 9px; letter-spacing: -0.2px;
  box-shadow: 0 0 0 1.5px var(--bg);
}
.dp-me-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.dp-me-n {
  font-size: 13.5px; font-weight: 800; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dp-me-l { font-size: 11.5px; font-weight: 700; color: var(--text-muted); }

/* Logged out it IS a box - that one is a pitch rather than a label, and it has
   a button in it. */
.dp-me-out {
  background: var(--raised); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 14px;
}
.dp-me-t { font-size: 13.5px; font-weight: 900; color: var(--text); }
.dp-me-s { font-size: 12px; color: var(--text-muted); line-height: 1.5; margin: 4px 0 11px; }
.dp-me-login {
  display: flex; align-items: center; justify-content: center; gap: 7px; width: 100%;
  background: var(--primary); color: var(--on-primary); border: 0;
  border-radius: var(--radius); padding: 10px; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 800;
}
.dp-me-login:hover { filter: brightness(1.08); }

/* The drawer's Log out row. Hidden above the breakpoint, where the account pill
   in the bar carries it - see the note in navSections. */
.dp-navlink.dp-logout { display: none; cursor: pointer; }
.dp-navlink.dp-logout:hover { color: var(--danger); }
body.dp-shell.rail-thin .dp-rail-toggle i { transform: rotate(180deg); }

/* flex: 0 1 auto + object-fit is the overflow valve. A five-figure balance and a
   long name can want more room than a 360px phone has; when that happens the
   logo scales down a few pixels instead of the account pill getting clipped off
   the edge. object-fit keeps it from squashing as it goes. */
/* text-decoration matters now that the phone bar shows the wordmark as TEXT
   rather than an image - an <a> underlines its text and never underlined the
   <img> that used to be the only thing in here. */
.dp-top-brand { display: block; flex: 0 1 auto; min-width: 0; line-height: 0; text-decoration: none; }
.dp-top-brand img {
  /* 34, not 28: the lockup is now a burst mark plus a wordmark on one line
     (~5.7:1, where the old graffiti wordmark was ~3.9:1), so the same box
     height renders the letters noticeably smaller. 34 puts the cap height back
     where it was and still clears the 60px bar. */
  height: 34px; width: auto; max-width: 100%;
  object-fit: contain; object-position: left center; display: block;
}
/* The wordmark, set rather than drawn. Poppins named explicitly because this
   sits in the shell's own bar rather than inside .theme-base, and inheriting
   the stack is not something to leave to luck for the site's name.
   "Degen" 600 and "Party" 900 is a real weight step, not two shades of the same
   one: theme.css loads Poppins at 400/500/600 only, so 600 is a genuine cut and
   900 is the browser's synthetic bold on top of it. Anything from 700 up would
   render identically to 900 - there is one level of synthesis, not three - so
   600-against-900 is the whole range available without adding a font file. */
.dp-top-brand .dp-brand-text {
  font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  /* Caps, like the lockup it stands in for. Caps run wider than the mixed-case
     version, so the size comes down a little to keep the bar's left cluster the
     same width it was - the balance beside it is what that room is for. */
  font-weight: 600; font-size: 16px; letter-spacing: 0.6px; white-space: nowrap;
  text-transform: uppercase;
  color: var(--text); line-height: 1.2;
}
/* All white now. The accent on the second half was fighting the coin pill for
   the only spot of colour in the bar, and the pill is the one that has to win. */
.dp-top-brand .dp-brand-text span { color: inherit; font-weight: 900; }
/* The wordmark text is the fallback for when a streamer has no wide logo, so it
   is visible by default and branding.js hides it by putting .dp-brand-wide on
   <html> once one loads. That is a class rather than the hidden attribute
   because [hidden] is display:none !important in the UA stylesheet, and the
   phone rule below has to be able to override it. */
html.dp-brand-wide .dp-top-brand .dp-brand-text { display: none; }

/* No site-wide sound toggle here any more - sound belongs to each game page,
   where it can offer more than one switch (music and effects separately, with
   levels) instead of the single site mute this slot used to hold. */

/* Coin balance pill - the casino's anchor element, and now the centre of the
   bar, so it gets the accent AND a bit more weight than the chrome around it.
   --raised, not --surface: the bar itself is --surface, and a pill the same
   colour as the thing it sits on isn't a pill. */
.dp-coins {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--raised); border: 1px solid var(--line);
  border-radius: var(--radius-pill); padding: 7px 16px;
  font-size: 14px; font-weight: 800; color: var(--text);
  text-decoration: none; white-space: nowrap;
  transition: border-color 0.12s;
}
.dp-coins:hover { color: var(--text); border-color: var(--text-faint); }
/* Squarer chrome in the BAR only. Everything up there used to be a different
   shape from everything beside it: fully-round pills for the balance and the
   login button, a 22px capsule for the account, and 10px squircles for the icon
   buttons - four radii across five objects on one 60px strip. They all take the
   icon buttons' 10px now, which is the one that could not change (a round icon
   button is a bubble, not a control).
   Scoped to #dpTop and not done on --radius-pill, because that token is worn by
   every chip and tag on the site and this is a decision about the bar. */
#dpTop .dp-coins,
#dpTop .dp-login-btn,
#dpTop .dp-dc-btn,
#dpTop .dp-iconbtn,
#dpTop .account-trigger { border-radius: 10px; }
/* The face inside the account pill stays round - it is a person, not a control. */
#dpTop .account-av { border-radius: 50%; }
/* Tournament mode. The pill is showing somebody ELSE's money - a stack the
   house handed out for the event - and the one thing it must never do is look
   like the balance it replaced.
   It is set apart by SHAPE rather than by colour: the normal pill is a filled
   --raised capsule, this one is an outline with a dashed edge, the minted coin
   is desaturated to a plain token, and the label reads "tournament". A brand
   colour invented for one feature would have said the same thing while making
   the bar stop looking like the rest of the site. */
.dp-coins.tour {
  background: transparent;
  border: 1px dashed var(--text-faint);
  color: var(--text);
}
.dp-coins.tour:hover { border-color: var(--text-muted); color: var(--text); }
/* The minted coin is a Degen Coin and these are not, so it is shown as a plain
   token rather than as the currency it isn't. */
.dp-coins.tour .dp-coin { filter: grayscale(1) brightness(1.35); opacity: 0.7; }
.dp-coins.tour .dp-coins-label { color: var(--text-muted); letter-spacing: 0.02em; }

.dp-coins i { color: var(--primary); font-size: 15px; }
/* The coin itself is theme.css's .dp-coin; this is only its size in the pill.
   18px against the 15px the glyph had: a circle reads smaller than a letterform
   in the same box, and this one has a rim to lose. */
.dp-coins .dp-coin { width: 18px; height: 18px; }

/* Two spellings of the balance, one shown at a time. Full by default; the
   abbreviated one takes over on a phone - see the 620px query. Never both, and
   never neither, so the pill cannot render empty if one span goes missing. */
.dp-coins-abbr { display: none; }

/* A mod handing out coins lands here: the pill flashes while the number counts
   up (shell.js). Gold for a gain, red for a deduction, so a correction never
   reads as a prize. */
.dp-coins.bump {
  /* The glow colour goes through a variable so the deduction case can repaint
     it - a keyframe can't be overridden per-modifier, and a gold halo around a
     red border reads as a prize arriving when coins just left. */
  --dp-bump: 255,197,49;
  animation: dpCoinsBump 0.9s ease-out;
  border-color: rgb(var(--dp-bump));
}
.dp-coins.bump.down { --dp-bump: 229,72,77; }
.dp-coins.bump i { animation: dpCoinsSpin 0.6s ease-out; }
@keyframes dpCoinsBump {
  0%   { transform: scale(1);    box-shadow: 0 0 0 0  rgba(var(--dp-bump), 0); }
  18%  { transform: scale(1.09); box-shadow: 0 0 0 5px rgba(var(--dp-bump), .35); }
  100% { transform: scale(1);    box-shadow: 0 0 0 12px rgba(var(--dp-bump), 0); }
}
@keyframes dpCoinsSpin {
  0%   { transform: rotate(0)      scale(1); }
  55%  { transform: rotate(180deg) scale(1.3); }
  100% { transform: rotate(360deg) scale(1); }
}

/* The "+500" lifting off the pill. Fixed to the pill's position in the page
   rather than nested inside it - the pill clips its own overflow, and a tag
   that has to escape a rounded border can't live within one. */
.dp-coins-float {
  position: absolute; z-index: 120; pointer-events: none;
  transform: translate(-50%, 0);
  font-size: 14px; font-weight: 800; color: #ffc531;
  text-shadow: 0 1px 3px rgba(0,0,0,.6);
  animation: dpCoinsFloat 1.3s ease-out forwards;
}
.dp-coins-float.down { color: var(--danger, #e5484d); }
@keyframes dpCoinsFloat {
  0%   { opacity: 0; transform: translate(-50%, 4px)   scale(0.85); }
  15%  { opacity: 1; transform: translate(-50%, -6px)  scale(1); }
  100% { opacity: 0; transform: translate(-50%, -34px) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  /* Keep the colour change - it is the part that says something happened -
     and drop the movement. */
  .dp-coins.bump { animation: none; }
  .dp-coins.bump i { animation: none; }
  .dp-coins-float { animation: dpCoinsFade 1.3s ease-out forwards; transform: translate(-50%, -14px); }
  @keyframes dpCoinsFade { 0%, 70% { opacity: 1; } 100% { opacity: 0; } }
}
.dp-coins .dp-coins-label { color: var(--text-faint); font-weight: 600; font-size: 11.5px; }

/* The phone balance used to be abbreviated here - "1.46m" instead of
   "1,455,204" - to keep the pill from growing into the logo. It reads the exact
   figure at every width now: a balance you have to tap through to read properly
   is the wrong trade in a casino, and the room it needed comes from the logo
   instead, which drops to its wordmark on a phone (see .dp-top-brand below).
   .dp-coins-abbr stays in the markup and stays display:none - the abbreviated
   span is still what the tournament pill and the /coins page reuse. */

/* Phone: the logo is the wordmark alone, not the full lockup. The burst mark
   costs about 40px of a 390px bar, and the bar has to hold the balance and the
   account pill as well.
   The second selector has to name .dp-brand-wide too: it is what hides the text
   when a logo has loaded, and an unqualified rule here would lose to it on
   specificity no matter where it sits in the file.
   NOTE: the fallback text is DegenParty's own wordmark. A streamer with their
   own logo gets that logo everywhere else, but on a phone this shows
   DegenParty's name - see the note in shell.js. */
@media (max-width: 620px) {
  .dp-top-brand img { display: none; }
  html.dp-brand-wide .dp-top-brand .dp-brand-text,
  .dp-top-brand .dp-brand-text { display: block; }
}

/* Staff-only pills, same idea as the old #modTools but inline in the bar.
   The layout is a CLASS, not an inline style, so the media query below can hide
   the group on small screens - an inline display would have beaten it and
   forced an !important. Shown/hidden per-viewer by the `hidden` attribute, which
   has to keep winning over the display:flex, hence the attribute selector. */
.dp-modtools { display: flex; align-items: center; gap: 8px; }
.dp-modtools[hidden] { display: none; }



/* Kick login button keeps Kick's own green - it's a platform affordance, not a
   brand surface, so it stays recognisable across any DegenParty theme. */


/* "Login with Discord". Shaped like the Kick button beside it, in Discord's own
   blurple - the colour is the whole recognition, so it is worth the two rules.
   Kick stays first and green; this is always the second option. */
.dp-dc-btn {
  display: inline-flex; align-items: center; gap: 7px;
  background: #5865f2; color: #fff; font-weight: 800; font-size: 13px;
  border-radius: var(--radius-pill); padding: 8px 16px; text-decoration: none;
  border: none; white-space: nowrap;
}
.dp-dc-btn:hover { background: #6b77f5; }
/* The gate-page version sits under a full-size Kick button, so it matches that
   one's proportions instead of the top bar's. Only the .lg form is used now -
   the top bar opens the login sheet instead of stacking provider buttons. */
.dp-dc-btn.lg {
  border-radius: var(--radius); padding: 12px 22px; font-size: 14px; gap: 8px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.28); margin-top: 10px;
}

/* ---- the one login button, and the sheet behind it ---------------------- */
/* Both marks sit on the button so it reads as "ways in", not as one provider's
   button - which is what a single green Kick button would say. */
.dp-login-btn {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--primary); color: var(--on-primary);
  font: inherit; font-weight: 800; font-size: 13px;
  border: none; border-radius: var(--radius-pill); padding: 8px 15px;
  cursor: pointer; white-space: nowrap;
}
.dp-login-btn:hover { filter: brightness(1.08); }
.dp-login-btn[hidden] { display: none; }
.dp-login-ics { display: inline-flex; align-items: center; gap: 5px; font-size: 15px; }

.dp-login { text-align: center; padding: 4px 2px 2px; }
.dp-login h2 { margin: 0 0 8px; font-size: 20px; }
.dp-login-sub {
  margin: 0 auto 20px; max-width: 330px;
  font-size: 13px; line-height: 1.55; color: var(--text-muted);
}
.dp-login-opt {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; box-sizing: border-box; margin: 0 0 10px; padding: 13px 18px;
  font-size: 14.5px; font-weight: 800; border: none; border-radius: var(--radius);
  text-decoration: none; cursor: pointer;
}
.dp-login-opt i { font-size: 18px; }
.dp-login-opt.kick    { background: #53fc18; color: #0a0a0a; }
.dp-login-opt.kick:hover    { background: #6bff3c; }
.dp-login-opt.discord { background: #5865f2; color: #fff; }
.dp-login-opt.discord:hover { background: #6b77f5; }
/* Telegram renders its own widget in here, so this only has to make room. */
.dp-login-tg:not(:empty) { margin: 0 0 10px; display: flex; justify-content: center; }
.dp-login-note {
  display: flex; align-items: flex-start; gap: 7px; text-align: left;
  margin: 16px 0 0; padding-top: 14px; border-top: 1px solid var(--line);
  font-size: 12px; line-height: 1.5; color: var(--text-muted);
}
.dp-login-note i { flex: none; margin-top: 1px; font-size: 14px; color: var(--primary); }

/* Challenges is hidden for logged-out visitors (see renderSession) - [hidden]
   needs saying out loud against the flex display .dp-iconbtn sets. */
.dp-top-gift[hidden] { display: none; }

/* The "still there?" prompt. Deliberately friendly and deliberately plain: the
   overwhelming majority of people who ever see it are not cheating, they just
   played a long session, and it should read as a check-in rather than an
   accusation. */
.dp-human { text-align: center; padding: 6px 2px 2px; }
.dp-human > i { font-size: 40px; color: var(--primary); display: block; margin-bottom: 10px; }
.dp-human h2 { margin: 0 0 8px; font-size: 20px; }
.dp-human p {
  margin: 0 auto 20px; max-width: 320px;
  font-size: 13.5px; line-height: 1.55; color: var(--text-muted);
}
.dp-human .dp-btn-lg { width: 100%; box-sizing: border-box; justify-content: center; }

/* ---- content primitives (rows of game tiles) ------------------------------ */

.dp-sec { margin: 0 0 30px; }
.dp-sec-head {
  display: flex; align-items: center; gap: 11px; margin-bottom: 13px;
}
.dp-sec-head h2 {
  margin: 0; font-size: 17px; font-weight: 800; color: var(--text);
  display: flex; align-items: center; gap: 9px;
}
.dp-sec-head h2 > i { color: var(--primary); font-size: 19px; }
.dp-sec-head .dp-sec-sub { color: var(--text-muted); font-size: 12.5px; }
/* Filled rather than outlined: on a page of cards an outlined button reads as
   another empty box, and this one is meant to look pressable. */
.dp-sec-head .dp-sec-more {
  margin-left: auto; flex: 0 0 auto;
  background: var(--surface); border: 1px solid var(--line);
  color: var(--text-body); font-size: 12px; font-weight: 700; text-decoration: none;
  border-radius: var(--radius); padding: 7px 15px; white-space: nowrap;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.dp-sec-head .dp-sec-more:hover {
  color: var(--text); background: var(--raised); border-color: var(--text-faint);
}

/* Game tile grid. Tiles are portrait like a casino thumbnail. */
.dp-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
  gap: 13px;
}
.dp-tile {
  position: relative; display: block;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-lg);
  overflow: hidden;
  text-decoration: none; color: #fff;
  border: 1px solid var(--line);
  background: var(--surface);
  transition: transform 0.14s ease, box-shadow 0.14s ease;
}
.dp-tile:hover { transform: translateY(-4px); box-shadow: 0 14px 30px rgba(0,0,0,0.42); }
/* The art layer: a two-stop gradient per game (set inline via --g1/--g2) with a
   big glyph. Real artwork can replace this later by dropping a background-image
   on .dp-tile-art without touching any other rule. */
.dp-tile-art {
  position: absolute; inset: 0;
  background: linear-gradient(150deg, var(--g1, #3a3f4d), var(--g2, #22262f));
  display: flex; align-items: center; justify-content: center;
}
.dp-tile-art > i { font-size: 62px; color: rgba(255,255,255,0.9); filter: drop-shadow(0 6px 14px rgba(0,0,0,0.45)); }
/* A drawn glyph facing a tile. Masked rather than placed as an image so it wears
   the same near-white and the same drop shadow as the icon-font glyphs on every
   other tile - a tile that's a different colour from its neighbours reads as a
   different KIND of thing, which isn't what a house icon should say. */
.dp-tile-glyph {
  width: 62px; height: 62px;
  background: rgba(255,255,255,0.9);
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
  filter: drop-shadow(0 6px 14px rgba(0,0,0,0.45));
}
.dp-tile-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Caption sits on a bottom scrim so it stays readable over any art. Name only -
   a tile grid is scanned, not read - so the scrim can be shallower than it was
   when it also had to cover a line of description. */
.dp-tile-cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 22px 12px 12px;
  background: linear-gradient(to top, rgba(0,0,0,0.82), rgba(0,0,0,0));
}
.dp-tile-t { font-weight: 800; font-size: 15px; line-height: 1.25; }
/* Corner ribbon - SOON / NEW / LIVE */
.dp-tile-flag {
  position: absolute; top: 9px; left: 9px; z-index: 2;
  font-size: 9.5px; font-weight: 800; letter-spacing: 0.7px; text-transform: uppercase;
  padding: 3px 9px; border-radius: var(--radius-pill);
  background: rgba(0,0,0,0.55); color: #fff; backdrop-filter: blur(3px);
}
.dp-tile-flag.new  { background: var(--success); color: #04210f; }
/* Unbuilt games read as present-but-locked rather than clickable. */
.dp-tile.soon { cursor: default; }
.dp-tile.soon .dp-tile-art { filter: saturate(0.35) brightness(0.62); }
.dp-tile.soon:hover { transform: none; box-shadow: none; }

/* ---- challenges ------------------------------------------------------------
   Streamer-set races on the home page. Reward on the right, because that's the
   number that decides whether anyone reads the rest of the row. */
.dp-chl {
  display: flex; align-items: center; gap: 16px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 15px 17px; margin-bottom: 9px;
}
.dp-chl.done { opacity: 0.72; }
/* Scheduled but not open yet - present, clearly not yet playable. */
.dp-chl.soon { opacity: 0.82; border-style: dashed; }
.dp-chl-main { flex: 1 1 auto; min-width: 0; }
.dp-chl-t { font-weight: 800; font-size: 15px; color: var(--text); }
.dp-chl-b { font-size: 12.5px; color: var(--text-muted); margin-top: 3px; line-height: 1.45; }
.dp-chl-rule {
  display: inline-block; margin-top: 7px;
  font-size: 11px; font-weight: 800; letter-spacing: .4px;
  color: var(--primary); background: color-mix(in srgb, var(--primary) 13%, transparent);
  border-radius: var(--radius-pill); padding: 3px 10px;
}
.dp-chl-side { flex: 0 0 auto; text-align: right; }
.dp-chl-reward {
  font-size: 17px; font-weight: 900; color: var(--text);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.dp-chl-reward > i { color: var(--primary); font-size: 15px; }
/* A real-world prize outranks the coins - it's the reason anyone chases it. */
.dp-chl-prize {
  font-size: 19px; font-weight: 900; color: var(--success);
  white-space: nowrap; line-height: 1.15;
}
.dp-chl-reward.sub { font-size: 13px; font-weight: 800; color: var(--text-muted); margin-top: 1px; }
.dp-chl-reward.sub > i { font-size: 12px; }
.dp-chl-open, .dp-chl-won { font-size: 11px; font-weight: 700; margin-top: 4px; white-space: nowrap; }
.dp-chl-open { color: var(--text-faint); }
.dp-chl-won  { color: var(--success); }

@media (max-width: 520px) {
  .dp-chl { flex-direction: column; align-items: flex-start; gap: 10px; padding: 13px; }
  .dp-chl-side { text-align: left; }
  .dp-chl-t { font-size: 14px; }
}

/* ---- live streamer cards --------------------------------------------------- */

.dp-streams { display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); gap: 14px; }
.dp-stream {
  display: flex; gap: 13px; align-items: center;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 14px;
  text-decoration: none; color: var(--text-body);
  transition: transform 0.14s, border-color 0.14s;
}
/* Only live cards are links, so only they get the lift-on-hover affordance. */
a.dp-stream:hover { transform: translateY(-3px); border-color: var(--primary); }
.dp-stream.off { cursor: default; }
.dp-stream-av {
  width: 52px; height: 52px; border-radius: 50%; flex: 0 0 auto;
  background: var(--raised); border: 2px solid var(--success);
  display: flex; align-items: center; justify-content: center;
  font-weight: 900; font-size: 19px; color: var(--text); text-transform: uppercase;
  overflow: hidden;
}
.dp-stream-av img { width: 100%; height: 100%; object-fit: cover; }
.dp-stream.off .dp-stream-av { border-color: var(--line); filter: grayscale(0.7); }
.dp-stream-meta { min-width: 0; flex: 1 1 auto; }
.dp-stream-head { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
.dp-stream-viewers { font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.dp-stream-name { font-weight: 800; font-size: 14.5px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Stream titles are arbitrary length and often long - one line, clipped. */
.dp-stream-sub {
  font-size: 12px; color: var(--text-muted); margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The "go watch" glyph, only present on live cards. */
.dp-stream-go { flex: 0 0 auto; font-size: 17px; color: var(--text-faint); transition: color 0.12s; }
a.dp-stream:hover .dp-stream-go { color: var(--primary); }
.dp-stream-code {
  display: inline-block; margin-top: 5px;
  font-weight: 800; font-size: 12px; letter-spacing: 1px;
  color: var(--primary); background: color-mix(in srgb, var(--primary) 14%, transparent);
  border: 1px dashed color-mix(in srgb, var(--primary) 50%, transparent);
  border-radius: 7px; padding: 2px 9px;
}
.dp-livedot {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10px; font-weight: 800; letter-spacing: 0.7px; text-transform: uppercase;
  color: var(--success);
}
.dp-livedot::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--success); animation: dpPulse 1.4s ease-in-out infinite;
}
@keyframes dpPulse { 50% { opacity: 0.25; } }
/* The offline half of the same badge. Same slot, same shape, no pulse - so the
   card answers "are they on?" in one place whichever the answer is, instead of
   the state living in a badge sometimes and in the subtitle other times. */
.dp-livedot.off { color: var(--text-faint); }
.dp-livedot.off::before { background: var(--text-faint); animation: none; }

/* Empty state for a section with nothing in it yet. */
.dp-empty {
  background: var(--surface); border: 1px dashed var(--line);
  border-radius: var(--radius-lg); padding: 26px 20px; text-align: center;
  color: var(--text-muted); font-size: 13.5px; line-height: 1.6;
}
.dp-empty > i { display: block; font-size: 30px; color: var(--text-faint); margin-bottom: 9px; }

/* ---- hero banner ---------------------------------------------------------- */

.dp-hero {
  position: relative; overflow: hidden;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  background:
    radial-gradient(ellipse at 8% 0%,  rgba(var(--grad-rgb-a), 0.30), transparent 62%),
    radial-gradient(ellipse at 92% 110%, rgba(var(--grad-rgb-b), 0.28), transparent 60%),
    var(--surface);
  padding: 30px 32px;
  margin-bottom: 30px;
  display: flex; align-items: center; gap: 26px; flex-wrap: wrap;
}
.dp-hero-body { flex: 1 1 340px; min-width: 0; }
.dp-hero h1 { margin: 0 0 9px; font-size: 27px; font-weight: 900; color: var(--text); line-height: 1.15; }
.dp-hero p { margin: 0 0 17px; color: var(--text-muted); font-size: 14px; line-height: 1.6; max-width: 560px; }
.dp-hero-cta { display: flex; gap: 10px; flex-wrap: wrap; }
/* The site's primary call to action, and the loudest use of the brand gradient.
   White text rather than --on-button: the button is no longer one flat colour,
   so a text colour picked for contrast against ONE end of the sweep is wrong at
   the other end.

   The shadow is plain black, like the one under a game tile. It used to be a
   purple halo the colour of the button, which is light a button cannot cast -
   it reads as decoration rather than as depth, and it was on every button on
   the site at once. */
.dp-btn-lg {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--grad); color: #fff;
  border: none; border-radius: var(--radius); padding: 12px 22px;
  font: inherit; font-size: 14px; font-weight: 800; cursor: pointer; text-decoration: none;
  box-shadow: 0 2px 8px rgba(0,0,0,0.28);
  transition: box-shadow 0.14s, filter 0.14s;
}
.dp-btn-lg:hover {
  filter: brightness(1.07);
  box-shadow: 0 4px 12px rgba(0,0,0,0.34);
}
.dp-btn-lg.ghost {
  background: var(--raised); border: 1px solid var(--line); color: var(--text);
  box-shadow: none;
}
.dp-btn-lg.ghost:hover {
  background: var(--soft-strong); border-color: var(--text-faint);
  filter: none; box-shadow: none;
}

/* Countdown block on the right of the hero (raffles / next show). */
.dp-count { display: flex; gap: 9px; flex: 0 0 auto; }
.dp-count-cell {
  background: var(--raised); border: 1px solid var(--line); border-radius: var(--radius);
  min-width: 62px; padding: 10px 6px; text-align: center;
}
.dp-count-n { font-size: 23px; font-weight: 900; color: var(--text); line-height: 1; font-variant-numeric: tabular-nums; }
.dp-count-l { font-size: 9.5px; font-weight: 700; letter-spacing: 0.8px; text-transform: uppercase; color: var(--text-faint); margin-top: 5px; }

/* ---- site originals: shared game-page furniture ---------------------------
   Every coin game (dice, mines, and the ones after them) is the same shape: a
   bet slip on the left, the game itself on the right, and a provably-fair panel
   underneath. That chrome lives here so the games can't drift apart visually;
   each page adds only what's genuinely its own (dice's track, mines' grid). */

/* Three optional blocks: the bet slip, the board, and an "extra" for reference
   material (pay tables, the fairness panel). Explicit columns rather than
   auto-placement, so `extra` lands under the board instead of under the slip. */
/* The games run at the shell's full content width (see .dp-main > .wrap). They
   used to be pinned to 900px, which on any normal desktop left the board at
   about half the window and made a 40-number keno grid look like a phone screen
   stretched across a monitor - every casino these games are compared against
   uses the whole width.
   The slip grows with it, but only a little: it holds a number field and two
   rows of small buttons, and a 400px-wide bet slip is just a wide bet slip.
   Everything the extra room buys goes to the board. */
.dp-game { display: grid; grid-template-columns: 330px minmax(0, 1fr); gap: 18px; align-items: start; }
.dp-game-slip  { grid-column: 1; }
.dp-game-board { grid-column: 2; }
.dp-game-extra { grid-column: 2; }

@media (max-width: 860px) {
  /* One column, ordered board â†’ slip â†’ extra. The board comes first so the
     result isn't under your thumb, but the SLIP must follow immediately: the
     play button has to be reachable while you can still see the board. Anything
     reference-shaped (pay tables, fairness) goes last, where scrolling for it
     is expected. */
  .dp-game { grid-template-columns: minmax(0, 1fr); }
  .dp-game-slip, .dp-game-board, .dp-game-extra { grid-column: 1; }
  .dp-game-board { order: 1; }
  .dp-game-slip  { order: 2; }
  .dp-game-extra { order: 3; }

  /* And within the slip, one order for every game, set HERE rather than in each
     game's own phone block - eight of them had their own conflicting version of
     this and they had drifted apart.

     Amount, the button, the round tools, then the settings, then the mode
     switch last. The stake and the button are what a phone needs under the
     board; a game's settings are picked once, and Manual/Auto less often than
     that, so both can sit below the fold. That is the reverse of the desktop
     column, where the mode switch leads - on a phone the top of the card is the
     only part you are guaranteed to see, and it belongs to the two controls you
     touch every round. */
  .dp-game-slip { display: flex; flex-direction: column; }
  /* Anything without a rule of its own is a game setting - board size, risk,
     rows, difficulty - and lands between the tools and the mode switch. */
  .dp-game-slip > *              { order: 6; }
  /* The amount label, whichever control happens to precede it. */
  .dp-game-slip > .dp-slip-label:first-child,
  .dp-game-slip > #autoHost + .dp-slip-label,
  .dp-game-slip > #modeSeg  + .dp-slip-label { order: 0; margin-top: 0; }
  .dp-game-slip > .dp-bet-row.box { order: 1; }
  /* .acts is blackjack's row of hit/stand buttons and .two is worms' Roll and
     Take pair - each is that game's action button, under a different name. */
  .dp-game-slip > .dp-action,
  .dp-game-slip > .acts          { order: 2; margin-top: 12px; }
  .dp-game-slip > .two           { order: 3; }
  .dp-game-slip > .dp-game-note  { order: 4; }
  .dp-game-slip > .dp-slip-tools { order: 5; margin-top: 12px; margin-bottom: 0; }
  .dp-game-slip > #autoHost,
  .dp-game-slip > #modeSeg,
  .dp-game-slip > .auto-block    { order: 7; }
  /* Group spacing for the mode switch. It gets none on desktop because it opens
     the card there; down here it lands under the last setting and without this
     the two rows of buttons touch and read as one control. The autobet body is
     excluded - it belongs to the switch directly above it. */
  .dp-game-slip > #autoHost,
  .dp-game-slip > #modeSeg       { margin-top: 16px; }
  /* And fairness goes last of all. Reference material, and the one thing in the
     slip you can scroll for without losing the round. */
  .dp-game-slip > .dp-slip-fair  { order: 99; }

  /* ---- the action button follows you ---------------------------------------
     The one control pressed on every single round, so on a phone it stops
     scrolling away while you work down the slip or read the chat.

     TOP, not bottom, and that is not a preference - it is the only direction
     that works. `position: sticky` pins an element within its containing block,
     and `bottom` pins it while you scroll UP, i.e. it holds an element that
     would otherwise leave through the bottom of the screen. This button sits at
     the TOP of the slip (order -2, so it lands under the board) and leaves
     through the TOP as you scroll down, which only `top` catches. Written as
     `bottom: <above the chat>` it reads exactly right and does nothing at all -
     measured, the sticky and static positions were identical at every scroll
     offset. If you change this, check it by comparing the two.

     So the button parks just under the top bar and stays there for the whole
     slip, while the chat holds the bottom of the screen. Both on screen at once,
     which is the point.

     The note line is deliberately NOT sticky: two stacked pinned rows eat a
     third of a phone screen, and the note is empty most of the time. */
  .dp-game-slip > .dp-action {
    position: sticky;
    top: 8px;
    z-index: 40;
    /* It now scrolls over the rest of the slip, so it needs to be opaque and to
       read as lifted off what passes underneath. .dp-action's own background is
       already solid; this is just the lift. */
    box-shadow: 0 6px 18px rgba(0, 0, 0, .35);
  }
}
/* A sticky element inside a scroll container that clips is a static element, so
   nothing in the slip's ancestry may hide its overflow. Worth stating: this is
   the failure mode where the button silently stops following and looks like a
   CSS typo rather than a layout rule. (The scrollport here is .dp-col, which
   carries overflow-y: auto - not the window. Anything measuring this has to
   scroll THAT.) */
.dp-game, .dp-game-slip { overflow: visible; }

.dp-game-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 18px;
}
/* The board is the anchor for the win popup below. */
.dp-game-board { position: relative; }

/* ---- out of coins ----------------------------------------------------------
   The one dead end on the site, so it gets a whole modal rather than another
   line of red text under a button that already isn't working. */
.dp-broke { text-align: center; padding: 6px 4px 2px; }
.dp-broke > i {
  font-size: 42px; color: var(--primary);
  filter: drop-shadow(0 6px 16px color-mix(in srgb, var(--primary) 35%, transparent));
}
.dp-broke h2 { margin: 12px 0 10px; font-size: 20px; font-weight: 900; color: var(--text); }
.dp-broke p { margin: 0 0 12px; font-size: 13.5px; line-height: 1.6; color: var(--text-body); }
.dp-broke .dp-broke-sum { color: var(--text-muted); font-size: 13px; }
.dp-broke .dp-broke-sum b { color: var(--text); }
.dp-broke .dp-btn-lg { display: inline-flex; margin-top: 4px; }

/* ---- win popup ------------------------------------------------------------
   A card over the board announcing a payout: multiplier on the accent, amount
   underneath. It deliberately covers the board - the round is over, and the
   win is the thing to look at. A click anywhere dismisses it. */
.dp-winpop {
  position: absolute; inset: 0; z-index: 5;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.dp-winpop-card {
  min-width: 186px; border-radius: 15px; overflow: hidden;
  border: 2px solid var(--primary);
  box-shadow: 0 18px 46px rgba(0, 0, 0, 0.55);
  text-align: center;
  animation: dpWinPop 0.34s cubic-bezier(0.22, 1.35, 0.36, 1);
}
/* Optional band above the multiplier, naming what the number is when that
   isn't obvious. A slot's free-spin total needs it - "×46.20" straight after
   the reels stop otherwise reads as the last spin rather than the whole
   feature. Keno never sets it, so the card is unchanged there. */
.dp-winpop-tag {
  background: var(--raised); color: var(--text-faint);
  font-size: 10.5px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase;
  padding: 7px 28px;
}
.dp-winpop-tag[hidden] { display: none; }
.dp-winpop-mult {
  background: var(--primary); color: var(--on-primary);
  font-size: 27px; font-weight: 900; letter-spacing: 0.5px;
  padding: 15px 28px; font-variant-numeric: tabular-nums;
}
.dp-winpop-amt {
  background: var(--surface); color: var(--text);
  font-size: 15px; font-weight: 700; padding: 12px 28px;
  display: flex; align-items: center; justify-content: center; gap: 7px;
  font-variant-numeric: tabular-nums;
}
.dp-winpop-amt i { color: var(--primary); font-size: 17px; }
/* No XP band. It was a third band under the coins, and by the time it shipped
   the same number was already being told twice over in the top bar - the level
   meter fills, and a +N XP chip floats up off the point on the track where the
   round landed. The popup is about what this round PAID; XP is the long game,
   and the long game has its own instrument at the top of every page. */
/* No "tap to dismiss" caption. The popup covers the board mid-game and every
   click closes it - people find that in the first second without being told,
   and the label was on screen for every win afterwards. */
@keyframes dpWinPop {
  0%   { transform: scale(0.55); opacity: 0; }
  100% { transform: scale(1);    opacity: 1; }
}
/* A big multiplier deserves a bit more noise than a small one. */
.dp-winpop-card.big { animation-duration: 0.5s; }
.dp-winpop-card.big .dp-winpop-mult { font-size: 34px; }
@media (max-width: 620px) {
  .dp-winpop-card { min-width: 156px; }
  .dp-winpop-mult { font-size: 23px; padding: 12px 22px; }
  .dp-winpop-amt  { font-size: 14px; padding: 10px 22px; }
}

/* Bet slip */
.dp-slip-label {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 11px; font-weight: 700; letter-spacing: 0.7px; text-transform: uppercase;
  color: var(--text-faint); margin: 0 0 6px;
}
/* Space ABOVE every group after the first, so a label belongs to the control
   under it instead of floating between two.
   This was `.dp-slip-label + .dp-slip-label`, which never matched a single
   element on any of the four originals: a slip always reads label, control,
   label, control, so two labels are never siblings. The group separation has
   therefore never been applied - RISK has been sitting flush against the bet
   row this whole time, 6px from the input above it and 6px from the buttons
   below, which is why the slip reads as one undifferentiated stack.
   `:not(:first-child)` is the version that matches what was meant, and it stays
   correct in the phone layout where the action button is re-ordered to the top:
   the first label is still the first CHILD, so it still gets no top margin. */
.dp-slip-label:not(:first-child) { margin-top: 18px; }
.dp-bet-row { display: flex; gap: 6px; }
/* The amount field and its chips as ONE control, the way the reference draws
   it: the border belongs to the row, the input is transparent inside it, and
   the chips sit in the same well.
   Opt-in with .box rather than applied to every .dp-bet-row, because that class
   also wraps a bare second field (limbo, worms) and mines' select - neither of
   which wants a second border drawn around it. Crash keeps the old row too: its
   auto-cashout pill rides in the same div, and that is not a bet chip. */
.dp-bet-row.box {
  align-items: center; gap: 4px; padding: 4px;
  background: var(--field); border: 1px solid var(--line);
  border-radius: var(--radius-btn);
}
.dp-bet-row.box:focus-within { border-color: var(--primary); }
.dp-bet-row.box .dp-bet-input {
  background: transparent; border: none; border-radius: 0; padding: 7px 9px;
}
.dp-bet-row.box .dp-bet-input:focus { border-color: transparent; }
.dp-bet-row.box .dp-bet-chip {
  background: var(--raised); border-color: transparent; padding: 7px 10px;
  border-radius: calc(var(--radius-btn) - 3px);
}
.dp-bet-row.box .dp-bet-chip:hover:not(:disabled) { border-color: transparent; background: var(--soft-strong, var(--raised)); color: var(--text); }
.dp-bet-input {
  flex: 1 1 auto; min-width: 0;
  background: var(--field); border: 1px solid var(--line); border-radius: var(--radius-btn);
  color: var(--text); font: inherit; font-size: 15px; font-weight: 700; padding: 11px 12px;
}
.dp-bet-input:focus { outline: none; border-color: var(--primary); }
.dp-bet-chip {
  flex: 0 0 auto; background: var(--raised); border: 1px solid var(--line);
  color: var(--text-muted); font: inherit; font-size: 12px; font-weight: 700;
  padding: 0 11px; border-radius: var(--radius-btn); cursor: pointer;
  transition: color 0.12s, border-color 0.12s;
}
.dp-bet-chip:hover:not(:disabled) { color: var(--text); border-color: var(--primary); }
.dp-bet-chip:disabled { opacity: 0.4; cursor: not-allowed; }

/* Slip tools: the things you do to a round OTHER than start it - keno's Auto
   pick and Clear, and Fairness on every game that has a seed. Sits directly
   above the action button, equal widths, and quieter than the bet chips beside
   the amount because none of it is part of placing the bet. */
.dp-slip-tools { display: flex; gap: 6px; margin-top: 18px; }
.dp-slip-tools .dp-bet-chip {
  flex: 1 1 0; min-width: 0; padding: 9px 4px; text-align: center;
  font-size: 11.5px; display: inline-flex; align-items: center;
  justify-content: center; gap: 5px;
}
/* The icons are the first thing to go when the column is narrow - the words are
   what people are looking for. */
@media (max-width: 380px) { .dp-slip-tools .dp-bet-chip i { display: none; } }
/* A live value riding on a tool button - the bet number on Fairness. Quieter
   than the label, because it answers a question you didn't ask until you're
   already interested: which nonce am I on, i.e. how many bets since the last
   seed rotation. */
.dp-tool-note { font-size: 10px; font-weight: 700; opacity: 0.6; }

/* ---- the fairness row ------------------------------------------------------
   Its own row, its own class, and NOT .dp-slip-tools - which is the whole point
   of it existing. The tools row is things you do to the game (keno's Auto pick
   and Clear), and on a phone it rides up above the action button so those are
   in reach. Fairness is the opposite kind of thing: you consult it once, or
   never, and it was being carried up there purely because it happened to share
   a class with its neighbours.

   So it is last in the slip at every width, and every game gets exactly this -
   one full-width button, nothing beside it. Full width rather than pinned to a
   third: there is nothing else in the row, and a lone third-width button reads
   as two missing buttons.

   Games must not re-order this. Four pages used to override the tools row's
   order to get the fairness button somewhere sensible (keno 1, limbo 0, worms
   0, blackjack21 3, all disagreeing) - separating the two classes is what makes
   all four overrides unnecessary. */
.dp-slip-fair { display: flex; margin-top: 18px; }
.dp-slip-fair .dp-bet-chip {
  flex: 1 1 auto; padding: 9px 12px; text-align: center;
  font-size: 11.5px; display: inline-flex; align-items: center;
  justify-content: center; gap: 6px;
}

/* Segmented control (dice's under/over, and anything like it) */
/* A slip dropdown. The alternative to .dp-seg below, for a setting with more
   options than fit across a slip - four risks and three row counts were already
   squeezing to 12px type on a phone, and a setting nobody changes mid-session
   does not need to spend a whole row showing the options it isn't on.

   appearance:none because the native control paints its own arrow in its own
   colour on every platform, and a themed field with a system-grey chevron on it
   reads as broken. The replacement is a real element rather than a background
   image so it takes --text-muted like everything else; the <label> wrapper is
   what keeps it clickable despite pointer-events:none. */
.dp-select-wrap { position: relative; display: block; }
.dp-select-wrap > i {
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  pointer-events: none; color: var(--text-muted); font-size: 13px;
}
.dp-select {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  width: 100%; padding: 12px 34px 12px 13px;
  background: var(--field); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius-btn);
  font: inherit; font-size: 14px; font-weight: 700; line-height: 1.2;
  cursor: pointer; outline: none;
  /* Long option text truncates rather than widening the slip. */
  text-overflow: ellipsis;
}
.dp-select:hover { border-color: var(--text-faint); }
.dp-select:focus { border-color: var(--primary); }
.dp-select:disabled { opacity: 0.5; cursor: not-allowed; }
.dp-select-wrap:has(.dp-select:disabled) > i { opacity: 0.5; }

.dp-seg { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 6px; }
/* Settings, not actions. Risk and rows get picked once and then left alone for
   an hour of play, while the button under them gets hit hundreds of times - so
   they shouldn't carry the same weight as it. At the original size they were
   within a few pixels of the action button's height and read as three more
   things to press; this is the second step down, and it matters most on plinko,
   which stacks two of these rows and so carried the weight twice.
   About 30px on a desktop against the action button's 54. */
.dp-seg-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  background: var(--raised); border: 1px solid var(--line); border-radius: var(--radius-btn);
  color: var(--text-muted); font: inherit; font-size: 12.5px; font-weight: 700;
  padding: 5px 6px; cursor: pointer; transition: all 0.12s;
}
/* ...but only where there's a cursor. On a touch screen these go back up to a
   44px-ish target, because a setting you can't hit reliably is worse than a
   setting that draws the eye slightly too much. */
@media (pointer: coarse) {
  .dp-seg-btn { padding: 11px 8px; font-size: 13.5px; }
}
.dp-seg-btn:hover:not(:disabled) { color: var(--text); }
.dp-seg-btn.on { background: var(--primary); color: var(--on-primary); border-color: transparent; }
.dp-seg-btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* Live readouts */
.dp-stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 16px; }
.dp-stat { background: var(--raised); border: 1px solid var(--line); border-radius: var(--radius); padding: 9px 11px; }
.dp-stat-l { font-size: 10px; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase; color: var(--text-faint); }
.dp-stat-v { font-size: 15px; font-weight: 800; color: var(--text); margin-top: 3px; font-variant-numeric: tabular-nums; }
.dp-stat-v.win { color: var(--success); }

/* The one big action button (Roll / Start / Cash out) */
.dp-action {
  width: 100%; margin-top: 16px;
  background: var(--button); color: var(--on-button);
  border: none; border-radius: var(--radius-btn);
  font: inherit; font-size: 16px; font-weight: 800; padding: 15px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 9px;
  transition: background 0.12s, transform 0.05s;
}
.dp-action:hover:not(:disabled) { background: var(--button-hover); }
.dp-action:active:not(:disabled) { transform: translateY(1px); }
.dp-action:disabled { opacity: 0.45; cursor: not-allowed; }
/* Cashing out is a different kind of act from betting - it gets its own colour
   so you never hit it by muscle memory when you meant to start a round. */
.dp-action.cash { background: var(--success); color: #04210f; }
.dp-action.cash:hover:not(:disabled) { filter: brightness(1.08); }

.dp-game-note { font-size: 12px; margin-top: 10px; min-height: 16px; text-align: center; }
.dp-game-note.err { color: var(--danger); }
.dp-game-note.ok  { color: var(--success); }

/* Provably-fair disclosure panel */
.dp-fair { margin-top: 18px; }
.dp-fair > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: 9px;
  font-size: 13px; font-weight: 700; color: var(--text-muted); padding: 4px 0;
}
.dp-fair > summary::-webkit-details-marker { display: none; }
.dp-fair > summary i { color: var(--primary); font-size: 16px; }
.dp-fair > summary:hover { color: var(--text); }
.dp-fair-body { padding-top: 14px; }
/* In the modal the title already provides that space. */
.dp-modal .dp-fair-body { padding-top: 0; }
.dp-fair-row { margin-bottom: 13px; }
.dp-fair-k { font-size: 10px; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase; color: var(--text-faint); margin-bottom: 4px; }
.dp-fair-v {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px; color: var(--text-body); word-break: break-all;
  background: var(--field); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px;
}
.dp-fair-form { display: flex; gap: 6px; }
.dp-fair-form input {
  flex: 1 1 auto; min-width: 0;
  background: var(--field); border: 1px solid var(--line); border-radius: 8px;
  color: var(--text); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px; padding: 8px 10px;
}
.dp-fair-form input:focus { outline: none; border-color: var(--primary); }
/* The "verify your old games" line quotes a seed and a 64-character hash in
   running text. Same treatment as the panel's code blocks, and it matters
   inline on the page too, not just in the dialog - a card is only ever 300-odd
   pixels wide in the slip column. */
.dp-fair-help { font-size: 12px; color: var(--text-muted); line-height: 1.65; overflow-wrap: anywhere; }
.dp-fair-help code {
  background: var(--soft-strong); border-radius: 4px; padding: 1px 5px;
  font-size: 11.5px; color: var(--text-body);
}

/* ---- per-game leaderboards -------------------------------------------------
   Live / Biggest / Luckiest / Mine, as tabs over one table. Same markup on
   every original, built by DPShell.mountWins(). */
/* Four tabs where there were three, so they're smaller: the widget sits in a
   fixed column beside the game, and three wide pills already wrapped onto two
   lines on a phone. */
.dp-wins-tabs { display: flex; gap: 5px; margin-bottom: 14px; flex-wrap: wrap; }
.dp-wins-tab {
  background: var(--raised); border: 1px solid var(--line); color: var(--text-muted);
  font: inherit; font-size: 11.5px; font-weight: 700;
  padding: 6px 12px; border-radius: var(--radius-pill); cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.dp-wins-tab:hover { color: var(--text); }
.dp-wins-tab.on { background: var(--primary); color: var(--on-primary); border-color: transparent; }

/* The live dot. It pulses whether or not anything is arriving - it marks the
   tab as the one that updates itself, which is true even when the table is
   empty. */
.dp-live-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto;
  background: var(--success); animation: dp-live-pulse 1.8s ease-in-out infinite;
}
.dp-wins-tab.on .dp-live-dot { background: var(--on-primary); }
@keyframes dp-live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }

/* The feed's own columns. The face replaces the rank, and a payout can now be
   negative - the boards never had to show a loss.
   The avatar is a COLUMN here, not an ornament beside the name the way it is on
   the boards: on a list where every row is a different person it's the fastest
   thing to scan, faster than reading four names. Which is also why it isn't
   hidden on a phone the way the boards' inline avatar is - see the media query
   at the end of this file. */
.dp-feed-face { width: 44px; padding-right: 0 !important; }
.dp-feed-av {
  width: 28px; height: 28px; border-radius: 50%; object-fit: cover;
  background: var(--raised); border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 800; color: var(--text-muted);
}
.dp-wins-loss { font-weight: 800; color: var(--text-faint); }
/* Your own rows ARE worth marking here, unlike on the boards: the feed is
   mostly other people, so this is a rare mark rather than one that fires on
   every second row. A chip, not a background - see the note on the zebra
   striping below. */
.dp-wins-you {
  font-size: 9px; font-weight: 800; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--primary); border: 1px solid color-mix(in srgb, var(--primary) 45%, transparent);
  border-radius: var(--radius-pill); padding: 1px 6px; flex: 0 0 auto;
}
/* A new row slides into place from just above, rather than flashing a colour.
   The difference matters: a colour flash is a NOTIFICATION - it says look at
   me, and on a busy table it says it constantly. Movement into position just
   reads as the list growing, which is all that actually happened.
   Short and small on purpose: 8px and a third of a second is enough to see
   where a row came from and not enough to be a performance. */
.dp-wins-row.fresh td { animation: dp-feed-slide 320ms ease-out; }
@keyframes dp-feed-slide {
  from { transform: translateY(-8px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .dp-live-dot { animation: none; }
  .dp-wins-row.fresh td { animation: none; }
}

.dp-wins-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.dp-wins-table th {
  text-align: left; font-size: 10px; font-weight: 700; letter-spacing: 0.7px;
  text-transform: uppercase; color: var(--text-faint);
  padding: 0 10px 9px; white-space: nowrap;
}
.dp-wins-table td { padding: 11px 10px; border-top: 1px solid var(--line); white-space: nowrap; }
/* Zebra striping, neutral. This does the work an accent highlight was doing
   badly: one player usually holds most of the top rows, so tinting "your" rows
   lit up nearly the whole table and told you nothing. Alternating greys are
   what actually make a long list scannable, and they leave the payout column
   as the only colour on the row. */
.dp-wins-table tbody tr:nth-child(even) td { background: var(--soft); }
/* No own-row marker at all. There was one, and it never earned its keep: the
   boards are short, "My wins" is already a tab of nothing but your rows, and on
   the shared boards one player tends to hold most of the list - so the marker
   fired on nearly every row and stopped meaning anything. If it ever comes
   back, mark it with something that is NOT a background: a fill has to beat the
   zebra stripe to be seen, which erases the striping. And do not call the class
   `you` - style.css already owns that name for the account profile card
   (display:flex; flex-direction:column) and it stacks a row's cells vertically. */
/* Player is the flexible column; the rest stay just wide enough to read. */
.dp-wins-table th:nth-child(2), .dp-wins-table td:nth-child(2) { width: 99%; }
.dp-wins-num { font-variant-numeric: tabular-nums; }
.dp-wins-rank { width: 52px; color: var(--text-faint); font-weight: 800; font-variant-numeric: tabular-nums; }
/* Top three get a medal tint rather than a bare number. */
.dp-wins-rank.m1 { color: #ffd24d; }
.dp-wins-rank.m2 { color: #d6dce6; }
.dp-wins-rank.m3 { color: #e0a06a; }
.dp-wins-player { display: flex; align-items: center; gap: 9px; min-width: 0; }
.dp-wins-av {
  width: 24px; height: 24px; border-radius: 50%; flex: 0 0 auto; object-fit: cover;
  background: var(--raised); border: 1px solid var(--line);
}
.dp-wins-name { font-weight: 700; color: var(--text); overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
.dp-wins-mult { font-weight: 800; color: var(--text); }
.dp-wins-pay  { font-weight: 800; color: var(--success); }
.dp-wins-empty { color: var(--text-muted); font-size: 13px; padding: 18px 2px; text-align: center; }
/* Rows open the board behind the win. */
.dp-wins-row { cursor: pointer; }
.dp-wins-row:hover td { background: var(--soft-strong); }
.dp-wins-row:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }

/* ---- win details modal ------------------------------------------------------
   Shared by all four originals - the leaderboards are, so this is too. */
.dp-modal {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center; padding: 18px;
}
.dp-modal[hidden] { display: none; }
.dp-modal-scrim { position: absolute; inset: 0; background: rgba(0,0,0,0.62); }
.dp-modal-card {
  position: relative; z-index: 1;
  width: 100%; max-width: 460px; max-height: calc(100vh - 36px); overflow-y: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 20px;
  box-shadow: 0 24px 60px rgba(0,0,0,0.55);
  /* Both of these exist to stop one long unbreakable string - a 64-character
     server seed - from setting the width of the dialog.
     min-width: 0 because the card is a flex item, and a flex item refuses by
     default to shrink below the widest thing inside it: the max-width above
     was being overruled, the card grew past the viewport, and since a fixed
     container can't be scrolled, the overflow on the LEFT became unreachable -
     which is why the title read "bly fair".
     overflow-wrap: anywhere because min-width alone would only move the
     problem inside, turning the card into a sideways scroller. `anywhere`
     rather than `break-word` deliberately: only this value also lowers the
     element's min-content width, which is the half that lets the flex item
     shrink in the first place. */
  min-width: 0; overflow-wrap: anywhere;
}
/* Wider variant for grid-shaped panels - see openPanel's `wide`. */
.dp-modal-card.wide { max-width: 640px; }
/* Heading for a borrowed panel (DPShell.openPanel). The win modal writes its
   own header, so this is only for panels that arrive without one. */
.dp-modal-title {
  font-size: 15px; font-weight: 900; color: var(--text);
  margin: 0 30px 14px 0;    /* clear of the close button */
}
.dp-modal-x {
  position: absolute; top: 12px; right: 12px;
  width: 30px; height: 30px; padding: 0; border-radius: 50%;
  background: var(--soft); border: 1px solid var(--line);
  color: var(--text-muted); font-size: 14px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.dp-modal-x:hover { color: var(--text); border-color: var(--primary); }
/* The page behind must not scroll while a modal is up. */
body.dp-modal-open { overflow: hidden; }

.dp-wm-game { font-size: 19px; font-weight: 900; color: var(--text); }
.dp-wm-sub  { font-size: 11.5px; color: var(--text-faint); margin-top: 1px; }
.dp-wm-by {
  display: flex; align-items: center; gap: 8px; margin-top: 12px;
  font-size: 13px; font-weight: 700; color: var(--text-body);
}
.dp-wm-av {
  width: 24px; height: 24px; border-radius: 50%; flex: 0 0 auto;
  background: var(--raised); object-fit: cover;
}
.dp-wm-stats {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
  margin: 15px 0 16px; background: var(--line);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
}
.dp-wm-stat { background: var(--raised); padding: 10px 8px; text-align: center; }
.dp-wm-stat-l {
  font-size: 9.5px; font-weight: 800; letter-spacing: 0.6px;
  text-transform: uppercase; color: var(--text-faint);
}
.dp-wm-stat-v { font-size: 14px; font-weight: 800; color: var(--text); margin-top: 2px;
  font-variant-numeric: tabular-nums; }
.dp-wm-stat-v.pay { color: var(--success); }

.dp-wm-legend { font-size: 11.5px; color: var(--text-muted); margin-bottom: 9px; text-align: center; }
.dp-wm-none { font-size: 12.5px; color: var(--text-faint); text-align: center; padding: 6px 0 2px; }
.dp-wm-foot { font-size: 11px; color: var(--text-faint); text-align: center; margin-top: 15px; }

/* Share the open replay into chat. Full width and quiet: it sits under the
   board, below the thing people opened the modal to look at, and it is not
   what most people came for. */
.dp-wm-share {
  width: 100%; margin-top: 14px;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--raised); border: 1px solid var(--line); border-radius: var(--radius);
  color: var(--text-muted); font: inherit; font-size: 13px; font-weight: 700;
  padding: 11px; cursor: pointer; transition: color 0.12s, border-color 0.12s;
}
.dp-wm-share i { font-size: 15px; }
.dp-wm-share:hover:not(:disabled) { color: var(--text); border-color: var(--primary); }
.dp-wm-share:disabled { cursor: default; opacity: 0.75; }
/* Done says so in the win colour rather than going grey - grey reads as "it
   didn't work" on a button that just did. */
.dp-wm-share.done { color: var(--success); border-color: color-mix(in srgb, var(--success) 45%, transparent); opacity: 1; }

/* Provably-fair receipt, collapsed by default. */
.dp-wm-fair { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 12px; }
.dp-wm-fair > summary {
  cursor: pointer; list-style: none; font-size: 12px; font-weight: 700;
  color: var(--text-muted); display: flex; align-items: center; gap: 7px;
}
.dp-wm-fair > summary::-webkit-details-marker { display: none; }
.dp-wm-fair > summary:hover { color: var(--text); }
.dp-wm-fair > summary > i { color: var(--primary); font-size: 14px; }
.dp-wm-fair-body { margin-top: 10px; }
.dp-wm-fair-body p { font-size: 11.5px; color: var(--text-faint); line-height: 1.55; margin: 10px 0 0; }
.dp-wm-fair-row { margin-bottom: 8px; }
.dp-wm-fair-row span {
  display: block; font-size: 9.5px; font-weight: 800; letter-spacing: .5px;
  text-transform: uppercase; color: var(--text-faint); margin-bottom: 2px;
}
/* Seeds and hashes are long and must stay copyable, so they wrap rather than
   scroll or ellipsize - a truncated hash can't be checked against anything. */
.dp-wm-fair-row code, .dp-wm-fair-body p code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; color: var(--text-body);
  word-break: break-all; overflow-wrap: anywhere;
}
.dp-wm-fair-row code {
  display: block; background: var(--field); border: 1px solid var(--line);
  border-radius: 7px; padding: 6px 9px;
}

/* Boards. The cell states mirror the live games so a replayed win looks the way
   it did when it landed. */
.dp-wm-grid { display: grid; gap: 4px; }
.dp-wm-grid.keno  { grid-template-columns: repeat(8, 1fr); }
/* The 80-tile board, ten across like the live one - so a shared win is laid
   out the way the player saw it land. */
.dp-wm-grid.keno.w { grid-template-columns: repeat(10, 1fr); gap: 3px; }
.dp-wm-grid.keno.w .dp-wm-cell { font-size: 11px; }
.dp-wm-grid.mines { grid-template-columns: repeat(5, 1fr); max-width: 260px; margin: 0 auto; }
/* The slot window: five reels across, three rows down. Not square - a slot cell
   is wider than it is tall, and squaring it here would show a screen the game
   never displayed. */
.dp-wm-grid.slots { grid-template-columns: repeat(5, 1fr); }
.dp-wm-grid.slots .dp-wm-cell { aspect-ratio: 1.15; font-size: 18px; }
.dp-wm-sym { position: relative; overflow: hidden; }
/* Scoped to the slot grid so it beats the generic .dp-wm-cell.off further down,
   which is the same specificity and later in the file. Held back harder than
   the shared 0.4 because a slot window has fifteen busy drawings in it and the
   line that paid has to be the thing you see first. */
.dp-wm-grid.slots .dp-wm-cell.off { opacity: 0.26; }
/* A winning slot cell keeps its PICTURE - the picture is what says what won.
   It was being filled solid green by the generic .dp-wm-cell.hit rule below,
   which was written for keno's numbered cells and had no business reaching a
   painted symbol: it buried the art under a flat green square. Overridden
   back to the cell's own colour here, and the win is marked the way the game
   marks it - a ring and a glow around the symbol, not instead of it. */
.dp-wm-grid.slots .dp-wm-cell.hit { background: var(--raised); color: inherit; }
.dp-wm-sym.hit {
  box-shadow: 0 0 0 2px var(--success),
              0 0 16px color-mix(in srgb, var(--success) 45%, transparent);
  background: color-mix(in srgb, var(--success) 15%, var(--raised));
  z-index: 1;
}

/* Stepping through a feature. A free-spins run is a sequence, and showing one
   frozen frame of it is a still of a film - these walk the spins. */
.dp-wm-step {
  display: flex; align-items: center; gap: 8px; margin: 2px 0 9px;
}
.dp-wm-step-b {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: var(--radius);
  background: var(--raised); border: 1px solid var(--line); color: var(--text-muted);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 15px; cursor: pointer; transition: color 0.12s, border-color 0.12s;
}
.dp-wm-step-b:hover:not(:disabled) { color: var(--text); border-color: var(--primary); }
/* Disabled at the ends rather than wrapping: a feature has a beginning and an
   end, and looping makes it impossible to tell where you are without reading. */
.dp-wm-step-b:disabled { opacity: 0.3; cursor: default; }
.dp-wm-step-l {
  flex: 1 1 auto; min-width: 0; text-align: center;
  display: flex; align-items: center; justify-content: center; gap: 8px;
}
.dp-wm-step-l b { font-size: 13px; font-weight: 800; color: var(--text); }
/* The running multiplier, which is the whole reason to watch a feature go by -
   the back half of a run is worth more than the front. */
.dp-wm-step-l span {
  font-size: 11.5px; font-weight: 800; color: var(--primary);
  font-variant-numeric: tabular-nums;
}
.dp-wm-step-note {
  font-size: 11px; color: var(--text-faint); text-align: center; margin: -4px 0 8px;
}

/* One row per paying line, under the window. The grid shows WHERE a line ran;
   these say what it was and what it was worth, which the grid cannot. */
.dp-wm-wins { margin-top: 9px; display: flex; flex-direction: column; gap: 5px; }
.dp-wm-win {
  display: flex; align-items: center; gap: 8px;
  background: var(--raised); border: 1px solid var(--line);
  border-radius: 8px; padding: 7px 10px; font-size: 12.5px;
}
.dp-wm-win-l {
  flex: 0 0 auto; font-size: 10px; font-weight: 800; letter-spacing: 0.5px;
  text-transform: uppercase; color: var(--text-faint);
}
.dp-wm-win b { color: var(--text); font-weight: 800; }
/* The beard doubling a line is the most interesting thing that can happen on
   one, and it is invisible in the grid - the wild is just another picture. */
.dp-wm-win-w {
  font-size: 10px; font-weight: 800; color: var(--primary);
  text-transform: uppercase; letter-spacing: 0.4px;
}
.dp-wm-win-p {
  margin-left: auto; flex: 0 0 auto;
  color: var(--success); font-weight: 800; font-variant-numeric: tabular-nums;
}
@media (max-width: 480px) {
  /* Wrap rather than drop anything: the wild note is the most interesting thing
     on the row, so it goes to a second line before it goes away. The pay stays
     pinned right on the first line either way. */
  .dp-wm-win { font-size: 11.5px; gap: 6px; padding: 6px 8px; flex-wrap: wrap; }
  .dp-wm-win-w { order: 3; flex-basis: 100%; }
}
/* The painted symbol sits over the icon, so a symbol with no drawing yet leaves
   its icon showing rather than an empty square. URL set inline per symbol. */
.dp-wm-art {
  position: absolute; inset: 0;
  background-repeat: no-repeat;
  background-size: 86%;
  background-position: center;
}
.dp-wm-sym.boom { color: #ff7a2f; }   .dp-wm-sym.leprechaun { color: #57c84d; }
.dp-wm-sym.scissors { color: #ffd24d; } .dp-wm-sym.warrior { color: #8fbf3f; }
.dp-wm-sym.dragon { color: #d98a3d; } .dp-wm-sym.beard { color: #b0742f; }
.dp-wm-sym.crying { color: #5c9dff; } .dp-wm-sym.shirt { color: #ff6fb5; }
.dp-wm-sym.wild { color: var(--primary); }
.dp-wm-sym.scatter { color: #ffd24d; }
.dp-wm-cell {
  aspect-ratio: 1; border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 800; font-variant-numeric: tabular-nums;
  background: var(--raised); color: var(--text-faint);
}
.dp-wm-grid.mines .dp-wm-cell { font-size: 15px; }
/* Drawn and theirs. */
.dp-wm-cell.hit  { background: var(--success); color: #04210f; }
/* Drawn, not theirs - dark cell, red number, same as the live keno board. */
.dp-wm-cell.miss { background: color-mix(in srgb, var(--raised) 45%, #000); color: var(--danger); }
/* Theirs, never drawn. */
.dp-wm-cell.cold { background: color-mix(in srgb, var(--primary) 16%, transparent); color: var(--text-muted); }
.dp-wm-cell.mine { background: color-mix(in srgb, var(--danger) 20%, transparent); color: var(--danger); }
.dp-wm-cell.off  { opacity: 0.4; }

/* The keno replay board borrows the live board's depth (keno.html .cell),
   scaled down for a ~45px cell in a modal: a 2px lower edge rather than 3px,
   and NO colour spill at all. A replay is a still image, and eight lit tiles
   in a grid this tight pool into one glow instead of reading as eight keys.
   Scoped to .keno on purpose - .dp-wm-cell is shared with the mines and slots
   replays, which were designed flat and are left that way. */
/* One pixel at both sizes. Two read as a lip on a 45px cell rather than as
   thickness, and under the 80-tile board's 3px gap it closed the gap up. */
.dp-wm-grid.keno { --wm-edge: 1px; }

.dp-wm-grid.keno .dp-wm-cell {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--raised) 88%, #fff) 0%,
    var(--raised) 46%,
    color-mix(in srgb, var(--raised) 94%, #000) 100%);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--raised) 80%, #fff),
    0 var(--wm-edge) 0 color-mix(in srgb, var(--raised) 58%, #000);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.28);
}
.dp-wm-grid.keno .dp-wm-cell.hit {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--success) 78%, #fff) 0%,
    var(--success) 48%,
    color-mix(in srgb, var(--success) 88%, #000) 100%);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--success) 52%, #fff),
    0 var(--wm-edge) 0 color-mix(in srgb, var(--success) 50%, #000);
  text-shadow: none;
}
.dp-wm-grid.keno .dp-wm-cell.cold {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--primary) 26%, transparent) 0%,
    color-mix(in srgb, var(--primary) 16%, transparent) 60%,
    color-mix(in srgb, var(--primary) 9%, transparent) 100%);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--primary) 24%, transparent),
    0 var(--wm-edge) 0 rgba(0, 0, 0, 0.30);
}
/* Drawn without them: sunken, so a spent number reads as a hole in the board
   and the hits keep owning it - the same call the live board makes. */
.dp-wm-grid.keno .dp-wm-cell.miss {
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.45);
  text-shadow: none;
}

/* ---- the Worms replay strip ------------------------------------------------
   One column per roll: the tile it landed on, the dice that put it there, and
   the total underneath. Five columns always, so the empty ones say how much of
   the round was left when it stopped. */
.dp-wm-worms {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px;
  padding: 2px 0 4px;
}
.dp-wm-worm-col { display: flex; flex-direction: column; align-items: center; gap: 7px; }
.dp-wm-worm-tile {
  width: 100%; aspect-ratio: 1; border-radius: 11px;
  display: flex; align-items: center; justify-content: center;
  font-size: clamp(11px, 2.4vw, 15px); font-weight: 800;
  font-variant-numeric: tabular-nums;
  /* Same raised treatment the live board gives its tiles. */
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.12);
}
.dp-wm-worm-tile.pay {
  background: linear-gradient(180deg,
              color-mix(in srgb, var(--primary) 92%, #fff) 0%,
              color-mix(in srgb, var(--primary) 94%, #000) 100%);
  color: var(--on-primary);
}
.dp-wm-worm-tile.bust {
  background: linear-gradient(180deg,
              color-mix(in srgb, var(--danger) 92%, #fff) 0%,
              color-mix(in srgb, var(--danger) 94%, #000) 100%);
  color: #fff;
}
/* A roll that never happened: the slot is there, nothing is in it. */
.dp-wm-worm-col.empty .dp-wm-worm-tile {
  background: var(--raised); box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
  opacity: 0.5;
}
.dp-wm-wormico { width: 66%; max-width: 30px; height: auto; }

.dp-wm-worm-dice { display: flex; gap: 4px; min-height: 20px; }
.dp-wm-die {
  width: 20px; height: 20px; border-radius: 5px; background: #E8ECF5;
  display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr);
  padding: 3px;
}
.dp-wm-die i { width: 3px; height: 3px; border-radius: 50%; background: #1B2230;
               align-self: center; justify-self: center; }
.dp-wm-worm-n {
  font-size: 10.5px; font-weight: 700; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.dp-wm-roll { text-align: center; padding: 4px 0 2px; }
.dp-wm-roll-v { font-size: 34px; font-weight: 900; color: var(--text); font-variant-numeric: tabular-nums; }

/* Plinko: the bucket strip, drawn the way the game draws it. Same shape, same
   colours, same numbers - a replay of a board game should be a picture of the
   board, and the multipliers are the board. The colours come from the renderer
   as inline hsl(), matching paintBuckets exactly. */
.dp-wm-slots { display: flex; gap: 3px; align-items: flex-end; }
.dp-wm-slot {
  flex: 1 1 0; min-width: 0; border-radius: 5px; padding: 6px 1px;
  text-align: center; color: #1a1204;
  font-size: 11px; font-weight: 800; line-height: 1.1;
  font-variant-numeric: tabular-nums;
  /* A label that wraps puts the × on its own line and reads as two buckets.
     Better to keep it on one line and let the size do the fitting - see wmMult
     for the four-digit case, which no size would have solved. */
  white-space: nowrap;
  /* The ones it missed are context, not the story - held back so the eye lands
     on the bucket it fell into without having to be told which that is. */
  opacity: 0.42;
  transition: none;
}
/* The one it landed in: full colour, lifted, and ringed in the page's own
   background so the ring reads as a gap around the pill rather than a border
   drawn on it. No animation - this is a still of something that already
   happened. */
.dp-wm-slot.on {
  opacity: 1;
  padding: 9px 1px;
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 3.5px #fff, 0 6px 16px rgba(0, 0, 0, 0.5);
  position: relative; z-index: 1;
}
/* Sixteen rows is seventeen buckets, and "110×" has to fit in about twenty
   pixels of a phone-width modal. The game's own board does the same thing at
   the same count. */
.dp-wm-slots.tight .dp-wm-slot { font-size: 8.5px; padding: 5px 0; border-radius: 4px; }
.dp-wm-slots.tight .dp-wm-slot.on { padding: 8px 0; }
@media (max-width: 480px) {
  .dp-wm-slots { gap: 2px; }
  .dp-wm-slot { font-size: 9.5px; }
  .dp-wm-slots.tight .dp-wm-slot { font-size: 7.5px; }
}

/* Blackjack: the hand as it was dealt, drawn the way the felt draws it - a seat
   label with the total on a pill, the cards fanned under it, and the result
   ringed on the player's cards in the same three colours the table uses. It used
   to be a caption and a row of chips with the rank printed on them, which told
   you what the cards were without ever looking like a hand.

   Smaller than the game's 64px cards: a modal that has to fit a dealer and up to
   four split hands cannot spend that much height per row. */
.dp-bj-seat + .dp-bj-seat { margin-top: 18px; }
.dp-bj-h {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  margin-bottom: 10px; min-width: 0;
}
.dp-bj-h-l {
  font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-muted);
  /* A player's name is whatever they chose it to be, and it sits next to a pill
     in a centred row. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Same pill as the felt's seat header, down a size. Tabular and wide enough for
   "9, 19" so a soft total does not shove the name sideways. */
.dp-bj-t {
  display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box;
  flex: 0 0 auto; height: 22px; min-width: 50px; padding: 0 9px;
  background: rgba(0, 0, 0, 0.45); color: var(--text);
  border: 1px solid rgba(255, 255, 255, 0.09); border-radius: var(--radius-pill);
  font-size: 11.5px; font-weight: 800; font-variant-numeric: tabular-nums;
}
.dp-bj-t.bust { background: color-mix(in srgb, var(--danger) 22%, transparent); color: var(--danger); }
.dp-bj-t.bj   { background: color-mix(in srgb, var(--success) 22%, transparent); color: var(--success); }
/* Settled, and after the two above so a hand that busted AND has settled lands
   here rather than on the tint. */
.dp-bj-t.win  { background: var(--success); color: #04210f; border-color: transparent; }
.dp-bj-t.lose { background: var(--danger);  color: #fff;    border-color: transparent; }
.dp-bj-t.push { border-color: rgba(255, 255, 255, 0.34); }

/* The fan. Each card steps down and to the right off --i, so the row costs the
   width of about one card however long the hand ran - which is what lets four
   split hands stack in a modal. The container reserves the drop, because a
   transform does not affect layout and the last card would otherwise hang out
   over whatever comes next.

   The proportions are taken off a real table's bet view: a card about 15% of the
   content column wide, overlapped by a bit under half, and stepped down by
   roughly a third of its own height so the hand reads as a hand held in one and
   not as a row of cards that happen to touch. The first pass at this had the
   card at 12% and the step at 12%, which was a fan you had to be told was one. */
.dp-bj-cards {
  /* Both live on the ROW and are inherited by the cards: the row reserves the
     drop and each card applies it, and one declaration is what keeps those two
     the same number. --n is the hand's length, written on by the renderer. */
  --cw: 62px;
  --step: calc(var(--cw) * 0.32);
  display: flex; justify-content: center;
  padding-bottom: calc(max(var(--n) - 1, 0) * var(--step));
}
.dp-bj-card {
  width: var(--cw); height: calc(var(--cw) * 1.435); flex: 0 0 auto; position: relative;
  margin-left: calc(var(--cw) * -0.46);
  transform: translateY(calc(var(--i) * var(--step)));
  border-radius: 8px;
  background: linear-gradient(160deg, #fff, #e8e3d6); color: #16181d;
  box-shadow: 0 8px 18px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.45);
}
.dp-bj-card:first-child { margin-left: 0; }
.dp-bj-card.red { color: #c0342f; }
/* The same face as the game's: the rank alone in the corner and one big pip,
   both sized off the card's own width. */
.dp-bj-c {
  position: absolute; top: calc(var(--cw) * 0.05); left: calc(var(--cw) * 0.10);
  font-size: calc(var(--cw) * 0.40); font-weight: 800; line-height: 1;
}
.dp-bj-p {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: calc(var(--cw) * 0.64); line-height: 1;
}
/* The result, worn on every card in the hand - the same ring the felt puts on a
   settled hand. Dealer cards stay neutral: the colour is about what happened to
   the money. Each rule repeats the card's own drop shadow, because box-shadow
   replaces rather than merges.

   The ring only, without the felt's 16px glow. The felt lays its cards out with
   a gap so the glow falls on green baize; here they overlap by nearly half, so
   each card's glow landed across the FACE of the one under it and washed its
   rank red or green - measured black ranks that read as red. */
.dp-bj-seat.win .dp-bj-card {
  box-shadow: 0 0 0 3px var(--success), 0 8px 18px rgba(0, 0, 0, 0.5);
}
.dp-bj-seat.lose .dp-bj-card {
  box-shadow: 0 0 0 3px var(--danger), 0 8px 18px rgba(0, 0, 0, 0.5);
}
.dp-bj-seat.push .dp-bj-card {
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.34), 0 8px 18px rgba(0, 0, 0, 0.5);
}
.dp-bj-tags { display: flex; justify-content: center; gap: 7px; margin-top: 9px; }
.dp-bj-tag {
  display: inline-flex; align-items: center; height: 19px; padding: 0 8px;
  border-radius: var(--radius-pill); background: var(--soft-strong); color: var(--text-muted);
  font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em;
}
/* A phone's modal is a 314px column rather than 418. The card comes down with
   it, but not as far - the height is what runs out on a phone, not the width,
   and five cards in a fan is 2.3 cards tall whatever they measure across. */
@media (max-width: 480px) {
  .dp-bj-cards { --cw: 54px; }
}
/* The table is wider than a phone; let it scroll rather than squashing. */
.dp-wins-scroll { overflow-x: auto; scrollbar-width: none; }
.dp-wins-scroll::-webkit-scrollbar { display: none; }
@media (max-width: 620px) {
  .dp-wins-table { font-size: 12px; }
  .dp-wins-table td { padding: 8px 7px; }
  .dp-wins-table th { padding: 0 7px 8px; }
  .dp-wins-name { max-width: 92px; }
  /* The boards' inline avatar goes, to buy the name room. The feed's stays -
     it's a column of its own and the reason a row is readable at a glance. */
  .dp-wins-av { display: none; }
  .dp-feed-face { width: 34px; }
  .dp-feed-av { width: 24px; height: 24px; font-size: 11px; }
}

/* Logged-out / feature-locked stand-in, shown instead of the game */
.dp-gate { text-align: center; padding: 40px 22px; }
.dp-gate > i { font-size: 42px; color: var(--primary); }
.dp-gate h2 { margin: 14px 0 8px; font-size: 21px; color: var(--text); }
.dp-gate p { color: var(--text-muted); font-size: 14px; margin: 0 auto 20px; max-width: 400px; line-height: 1.6; }

/* ---- site chat -------------------------------------------------------------
   Docked bottom-right, collapsed to a tab. Fixed rather than in the grid so no
   page has to make room for it, and so it can't push a game's layout around. */
.dp-chat {
  position: fixed; right: 16px; bottom: 0; z-index: 55;
  width: min(340px, calc(100vw - 32px));
  display: flex; flex-direction: column; align-items: stretch;
  font-size: 13px;
}
.dp-chat-tab {
  /* The wrapper aligns the row now, so the tab no longer places itself.
     touch-action:none because the tab is a drag handle as well as a button -
     without it a pull upwards scrolls the page instead of opening the chat. */
  display: flex; align-items: center; gap: 8px; touch-action: none;
  padding: 9px 14px; border: 1px solid var(--line); border-bottom: 0;
  border-radius: 12px 12px 0 0; cursor: pointer;
  background: var(--bg); color: var(--text); font: inherit; font-weight: 700;
  /* The whole chat is one dark unit now, which means it is the same colour as
     the page it floats over - so it is lifted off the page rather than tinted
     away from it. Without this the tab all but disappears against a dark page,
     and the tab is what people click to open the thing. */
  box-shadow: 0 -4px 18px rgba(0, 0, 0, .45);
}
.dp-chat-tab:hover { background: var(--surface); }
.dp-chat-tab i { color: var(--primary); font-size: 16px; }
/* How many people are on the site. A quiet pill rather than a bright badge -
   it sits on the tab permanently, so it has to read as information and not as
   an unread count demanding a tap. */
.dp-chat-online {
  display: inline-flex; align-items: center; gap: 5px;
  margin-left: 2px; padding: 2px 7px 2px 6px;
  border-radius: 999px; background: var(--surface);
  color: var(--text-faint); font-size: 12px; font-weight: 700;
  font-variant-numeric: tabular-nums;   /* the tab must not twitch as the count ticks */
  line-height: 1.5;
}
.dp-chat-online-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: #35c463; flex: 0 0 auto;
}
.dp-chat-tab:hover .dp-chat-online { background: var(--raised); }
/* Collapsed: the panel is laid out but clipped to nothing, so opening it never
   has to measure anything. */
.dp-chat-inner {
  height: 0; overflow: hidden;
  border: 1px solid var(--line); border-bottom: 0; border-radius: 12px 0 0 0;
  background: var(--bg);
  box-shadow: 0 -4px 18px rgba(0, 0, 0, .45);   /* same lift as the tab */
  transition: height .16s ease;
}
/* Height comes from --dp-chat-h, which shell.js sets: a default per screen size
   on first load, or whatever the player dragged it to. One variable so the panel
   and the space the body reserves for it can never disagree. */
.dp-chat.open .dp-chat-inner { height: var(--dp-chat-h, 560px); display: flex; flex-direction: column; }
/* The grab strip. Its own element rather than the panel edge so dragging can
   never start on the message list, and touch-action:none because otherwise a
   drag down the handle scrolls the page instead of resizing. */
.dp-chat-grip {
  height: 16px; flex: 0 0 16px; cursor: ns-resize; touch-action: none;
  display: flex; align-items: center; justify-content: center;
  border-bottom: 1px solid var(--line); background: var(--bg);
}
.dp-chat-grip::before {
  content: ''; width: 40px; height: 4px; border-radius: 2px;
  background: var(--line);
}
.dp-chat-grip:hover::before { background: var(--text-faint); }
.dp-chat.resizing .dp-chat-inner { transition: none; }   /* follow the finger exactly */
.dp-chat.resizing { user-select: none; }
/* The message well is recessed: it takes the page base rather than the panel's
   raised surface, so the text sits on a darker ground and reads more easily.
   Deliberately a token and not a hardcoded grey - a streamer's theme can be
   light, in which case base-under-surface still describes the right
   relationship. The grip and the composer stay on --surface, so the strip you
   type into still reads as a control rather than as more of the log. */
.dp-chat-list {
  flex: 1; overflow-y: auto; padding: 10px 10px;
  display: flex; flex-direction: column; gap: 3px;
  background: var(--bg);
}

/* The scroll bar, styled to disappear into the panel.
   Chrome ignores every ::-webkit-scrollbar rule the moment the standard
   scrollbar-width / scrollbar-color properties are set on the same element -
   the two are alternative styling paths, not layers, and the standard one
   wins. Setting both is why this first came out as Windows' default bar,
   stepper arrows and all. So: pseudo-elements for Chrome and the webviews,
   and the standard properties only where those do not exist (Firefox). */
.dp-chat-list::-webkit-scrollbar { width: 8px; }
.dp-chat-list::-webkit-scrollbar-track { background: transparent; }
/* Windows draws stepper arrows at both ends by default. They are the ugliest
   part of the default bar and nothing on a touch-first panel needs them. */
.dp-chat-list::-webkit-scrollbar-button { display: none; height: 0; width: 0; }
.dp-chat-list::-webkit-scrollbar-corner { background: transparent; }
.dp-chat-list::-webkit-scrollbar-thumb {
  background: var(--soft-strong); border-radius: 99px;
  /* Inset off the panel edge. A transparent border with background-clip is the
     only way to pad a scrollbar thumb - it has no margin of its own. */
  border: 2px solid transparent; background-clip: padding-box;
}
.dp-chat-list::-webkit-scrollbar-thumb:hover {
  background: var(--text-faint); background-clip: padding-box;
}

@supports not selector(::-webkit-scrollbar) {
  .dp-chat-list { scrollbar-width: thin; scrollbar-color: var(--soft-strong) transparent; }
}
/* Each message sits on its own faint block. Separation without a divider line:
   in a fast chat a rule under every message becomes a ladder the eye catches on,
   and full bubbles cost vertical room a docked panel cannot spare - so the fill
   is quiet and the padding is tight. */
/* No fill per message: the panel is the box, and the messages sit flat inside
   it. A block behind every line turned a busy room into a column of stacked
   cards, which costs vertical space a docked panel can't spare and reads as a
   list of objects rather than as a conversation. Separation comes from the
   list's gap and from every row carrying its own name.
   The radius and padding stay for the states that DO need a shape - hover, and
   a message that tagged you. */
.dp-chat-msg {
  position: relative;
  display: flex; gap: 8px; align-items: flex-start;
  padding: 3px 8px; border-radius: 9px;
}
/* Marks the line under the pointer. It used to be there to give the mod handle
   in the corner something to sit on; the handle is gone and this stays, because
   picking one line out of a fast-moving list is worth it on its own - and to a
   mod it is the row whose name they are about to click. */
.dp-chat-msg:hover { background: rgba(255, 255, 255, .035); }
.dp-chat-av { width: 22px; height: 22px; border-radius: 50%; flex: 0 0 22px; object-fit: cover; background: var(--raised); }
/* The level, left of the picture. A hexagon because that is the shape a level
   wears everywhere else on the site - the ring around a leaderboard avatar, the
   chip on it - so a player reading chat and a player reading a board are being
   shown the same object at two sizes.

   Wider than it is tall on purpose: a regular hexagon big enough to hold two
   digits comfortably would be taller than the line it sits on. Squashing it
   horizontally keeps the row height and still reads as the same shape.

   The row's flex gap already spaces it, and it is the tightest column on the
   narrowest panel on the site, so nothing here adds margin. */
.dp-chat-lv {
  /* Top-aligned like the avatar, not centred in the row. A line carrying a
     shared-bet card is three times the height of a plain one, and a centred
     chip slid down the side of it while the picture stayed up by the name -
     the two things that identify one person, pulled apart. The 1px nudges its
     20px against the avatar's 22. */
  flex: 0 0 auto; margin-top: 1px;
  min-width: 20px; height: 20px; padding: 0 3px;
  display: flex; align-items: center; justify-content: center;
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  background: linear-gradient(150deg, var(--hex-from), var(--hex-to));
  color: #10131a; font-size: 10px; font-weight: 900; line-height: 1;
  font-variant-numeric: tabular-nums;
  /* The panel is 340px and the message is the point - the gap either side of
     this comes off the text, so it is 6px rather than the row's 8. */
  margin-right: -2px;
}
.dp-chat-body { min-width: 0; line-height: 1.45; }
/* White and bold, with the colon that separates it from what was said. The
   colon is a pseudo-element rather than part of the text so it can never end up
   inside a copied username, and so the name a mod clicks is still just the
   name. */
.dp-chat-name { font-weight: 800; color: var(--text); margin-right: 2px; }
/* A name the server is talking ABOUT, inside the body of an announcement -
   not the author beside it, and not a tag. Brighter than the sentence it sits
   in, which is the whole job. */
.dp-chat-em { font-weight: 800; color: var(--text); }
.dp-chat-name::after { content: ':'; }
/* Staff mark. Sits before the name so it reads as a title rather than as
   punctuation, and is coloured differently from the name so it doesn't just
   look like part of it. */
.dp-chat-badge { font-size: 12px; margin-right: 4px; vertical-align: -1px; }
/* Fixed colours, not theme variables. --brand-accent is the same blue as
   --primary here, so the crown came out the exact colour of the username next
   to it and read as part of the name rather than as a mark. Green sword / gold
   crown is also what people already know from every other stream platform, so
   it needs no explaining. */
.dp-chat-badge.mod { color: #21d07a; }
.dp-chat-badge.admin { color: #ffc531; }
/* Chat is the one place strangers' text is shown, so it must never be able to
   blow the panel open with one long word. */
/* A step down from the white of the name, so the two read as speaker and
   speech rather than as one run of text. Secondary, not faint - this is the
   content of the panel and still has to be the easiest thing in it to read. */
.dp-chat-text { color: var(--text-muted); overflow-wrap: anywhere; }
/* "Shared a bet" is a label for what follows, not something they said - so it
   reads as the accent rather than as speech. */
.dp-chat-text.shared { color: var(--primary); font-weight: 700; }

/* A link inside a chat message. Only staff lines and the going-live
   announcement carry them - see CHAT_LINKABLE in shell.js. Underlined rather
   than colour-only, because a coloured word among coloured names is not
   obviously a link. */
.dp-chat-link {
  color: var(--primary); text-decoration: underline;
  text-underline-offset: 2px; text-decoration-thickness: 1px;
  word-break: break-word;      /* a long URL must not widen the whole panel */
}
.dp-chat-link:hover { color: var(--text); }

/* The going-live announcement. Green, and the only chat row with a fill behind
   it - it is the one line in the room that is worth interrupting a conversation
   for, and it scrolls past exactly like everything else if it does not stand
   out from it. */
.dp-chat-badge.live { color: var(--success); }
/* Gold, not the green the live badge uses - two server announcements that
   look identical are two announcements nobody tells apart. */
.dp-chat-badge.win  { color: var(--dp-win); }
.dp-chat-msg[data-badge="live"] {
  background: color-mix(in srgb, var(--success) 9%, transparent);
  border-radius: 10px;
}
.dp-chat-msg[data-badge="live"] .dp-chat-name { color: var(--success); }

/* ---- a big win ------------------------------------------------------------
   The other announcement the server makes, and the only other row with a fill
   of its own. Gold where going-live is green, so the two are never taken for
   each other at a glance.

   Drawn as an inset shadow rather than a real border for the same reason
   .tagged is: .dp-chat-msg has no border, and giving one row a real one would
   make it a pixel wider than the lines above and below it, so the column would
   step in and out as it scrolled. */
.dp-chat-msg[data-badge="win"] {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--dp-win) 50%, transparent);
  background: color-mix(in srgb, var(--dp-win) 8%, transparent);
  padding-top: 6px; padding-bottom: 6px;
}
.dp-chat-msg[data-badge="win"]:hover {
  background: color-mix(in srgb, var(--dp-win) 13%, transparent);
}
.dp-chat-msg[data-badge="win"] .dp-chat-name { color: var(--dp-win-ink); }
/* Nothing is said after the name on this row, so the colon has nothing to
   introduce - see .dp-chat-name::after. */
.dp-chat-msg.win .dp-chat-name::after { content: none; }

/* What kind of row this is. A word rather than a figure: every number that
   belongs to this win is already on the card underneath, and repeating one of
   them here is how the row got long in the first place. */
.dp-win-tag {
  display: inline-block; margin-left: 6px; padding: 1px 6px;
  border-radius: 5px; vertical-align: 1px;
  background: color-mix(in srgb, var(--dp-win) 18%, transparent);
  color: var(--dp-win-ink);
  font-size: 10px; font-weight: 800; letter-spacing: 0.4px; text-transform: uppercase;
}

/* Inside the gold block the card gives up its own frame. Two nested borders on
   a 300px panel is the busyness this row was meant to lose - the block is the
   edge, the card is just the line inside it. */
/* The body is a flex child, so with the sentence gone it shrank to the width of
   the name and the card's own width:100% resolved against THAT - leaving the
   card stopping short of the gold edge with dead space beside it, and the
   payout no longer pushed to the right. Only win rows need this; every other
   row still has prose to set its width. */
.dp-chat-msg.win .dp-chat-body { flex: 1; }
.dp-chat-msg.win .dp-bet-card {
  margin-top: 4px; border-color: transparent;
}
.dp-chat-msg.win .dp-bet-card:hover {
  border-color: color-mix(in srgb, var(--dp-win) 55%, transparent);
  background: var(--raised);
}
.dp-chat-msg.win .dp-bet-card-g i { color: var(--dp-win); }

/* The @everyone row in the picker: a megaphone where a face would be, and a
   label that says what it does rather than who it is. */
.dp-chat-pick-av.all {
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--danger) 22%, transparent);
  color: var(--danger); font-size: 12px;
}
.dp-chat-pick-here.all { color: var(--danger); }

/* ---- @mentions ------------------------------------------------------------
   Two strengths on purpose. Any tag gets the quiet chip, so a line reads as
   addressed to someone; the one addressed to YOU gets the loud chip and a mark
   down the side of the row. A single style for both would mean scanning every
   highlighted line to find out whether it was for you, which is the whole job
   the highlight is supposed to do. */
.dp-chat-at {
  font-weight: 700; color: var(--primary);
  background: rgba(var(--accent-rgb), .13);
  border-radius: 5px; padding: 0 3px;
  /* The chip is inline in a sentence that already wraps anywhere, so it must be
     allowed to break rather than push the panel wide on a long handle. */
  overflow-wrap: anywhere;
}
.dp-chat-at.me {
  color: var(--bg); background: var(--primary);
}
/* A blue box around the whole message. Drawn as an inset shadow rather than a
   real border because .dp-chat-msg has none of its own - adding one would push
   every tagged line a pixel wider than the ones above and below it, and the
   column of messages would step in and out as you scrolled. */
/* Addressed to you. Now that ordinary messages have no fill of their own, this
   is the only boxed row in the list - so it needs a faint ground behind the
   outline, or the border reads as a stray rectangle rather than as this line
   being picked out. */
.dp-chat-msg.tagged {
  box-shadow: inset 0 0 0 1px var(--primary);
  background: rgba(var(--accent-rgb), .09);
  padding-top: 5px; padding-bottom: 5px;
}
.dp-chat-msg.tagged:hover { background: rgba(var(--accent-rgb), .14); }

/* A bet posted into chat. One tap-sized row: which game, what it did, what it
   cost or paid, and a chevron saying there is more behind it. Deliberately
   compact - the chat panel is a third of a phone screen and the whole reason
   people keep it open is that it doesn't take the game away. */
.dp-bet-card {
  display: flex; align-items: center; gap: 8px; width: 100%;
  margin-top: 5px; padding: 7px 8px;
  background: var(--field); border: 1px solid var(--line); border-radius: 9px;
  color: var(--text-muted); font: inherit; font-size: 11.5px; font-weight: 700;
  cursor: pointer; text-align: left;
  transition: border-color 0.12s, background 0.12s;
}
.dp-bet-card:hover { border-color: var(--primary); background: var(--raised); }
.dp-bet-card-g {
  display: inline-flex; align-items: center; gap: 5px; min-width: 0;
  color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dp-bet-card-g i { color: var(--primary); font-size: 13px; flex: 0 0 auto; }
.dp-bet-card-m {
  display: inline-flex; align-items: center; gap: 3px; flex: 0 0 auto;
  color: var(--text-body); font-variant-numeric: tabular-nums;
}
.dp-bet-card-m i { font-size: 11px; opacity: 0.7; }
/* Losses are the common case and shouldn't shout; a win gets the colour. */
.dp-bet-card-p {
  margin-left: auto; flex: 0 0 auto; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.dp-bet-card-p.up { color: var(--success); }
.dp-bet-card-x { flex: 0 0 auto; font-size: 11px; opacity: 0.45; }
/* Narrow panels drop the game NAME before anything else - the icon still says
   which game, and the numbers are the part that can't be inferred. */
@media (max-width: 420px) {
  .dp-bet-card { gap: 6px; font-size: 11px; padding: 6px 7px; }
}
/* Names are only clickable for someone who can actually act on them - for
   everyone else they must not look like a control that does nothing. */
.dp-chat.mod .dp-chat-name { cursor: pointer; }
.dp-chat.mod .dp-chat-name:hover { text-decoration: underline; }
.dp-chat-menu {
  position: fixed; z-index: 70; min-width: 190px; padding: 6px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0,0,0,.45);
  display: flex; flex-direction: column; gap: 2px;
}
.dp-chat-menu-h {
  font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .5px;
  color: var(--text-muted); padding: 5px 8px 6px; border-bottom: 1px solid var(--line);
  margin-bottom: 3px; overflow-wrap: anywhere;
}
.dp-chat-menu button, .dp-chat-menu a {
  text-align: left; background: none; border: 0; border-radius: 7px; cursor: pointer;
  padding: 8px 9px; font: inherit; font-size: 12.5px; color: var(--text-body); text-decoration: none;
}
.dp-chat-menu button:hover, .dp-chat-menu a:hover { background: var(--raised); }
.dp-chat-menu button.danger { color: var(--danger, #ee5253); }
/* Giving coins is the one action here that isn't moderation, so it doesn't look
   like the others. */
.dp-chat-menu button.coins { color: #ffc531; }

/* ---- rain ---------------------------------------------------------------
   A card among the messages rather than its own furniture on the page: the
   chat is small on purpose so people can play and talk at once, and a permanent
   rain widget would spend exactly the room that makes that work. */
/* The pill, beside the chat tab. Only exists while a rain is open. It replaced
   an in-chat card that scrolled away the moment the room got busy - which meant
   anyone arriving late never saw the button at all. */
.dp-chat-tabs { display: flex; align-items: flex-end; justify-content: flex-end; gap: 6px; }
.dp-rain-pill {
  display: flex; align-items: center; gap: 6px;
  padding: 9px 12px; border: 1px solid #2f7fd6; border-bottom: 0;
  border-radius: 12px 12px 0 0; cursor: pointer; font: inherit; font-weight: 800;
  background: linear-gradient(180deg, rgba(47,127,214,.35), rgba(47,127,214,.15));
  color: #fff; font-variant-numeric: tabular-nums;
  animation: dpRainPulse 1.6s ease-in-out infinite;
}
.dp-rain-pill i { color: #6bc7ff; font-size: 16px; }
.dp-rain-pill:hover { filter: brightness(1.15); }
/* Already in: stop pulling at them for attention they've already given. */
.dp-rain-pill.in { animation: none; border-color: var(--success, #1dd1a1); }
.dp-rain-pill.in i { color: var(--success, #1dd1a1); }
@media (prefers-reduced-motion: reduce) { .dp-rain-pill { animation: none; } }

/* The winners still land in chat: it's the record of what happened, and it's
   what gets people talking about the next one. */
.dp-rain {
  border: 1px solid var(--line); border-radius: 12px; padding: 10px 11px;
  background: rgba(255,255,255,.04);
  display: flex; flex-direction: column; gap: 7px;
}
.dp-rain-top { display: flex; align-items: center; gap: 7px; font-size: 13px; letter-spacing: .5px; }
.dp-rain-top i { color: #6bc7ff; font-size: 16px; }
.dp-rain-sub { font-size: 11.5px; color: var(--text-muted); }

/* The join panel. */
.dp-rainpanel { display: flex; flex-direction: column; gap: 12px; min-width: 250px; text-align: center; }
/* Both of these panels are PARKED in the body between uses, hidden, and lifted
   into the modal when wanted. `display: flex` on the bare class outranks the
   browser's own [hidden] { display: none }, so the attribute was doing nothing
   and the panel was on the page from the first time it was built - which is why
   a closed Give coins modal left its contents sitting under the footer. */
.dp-rainpanel[hidden], .dp-coingrant[hidden] { display: none; }
.dp-rp-pot { font-size: 34px; font-weight: 800; color: #ffc531; line-height: 1; }
.dp-rp-pot span { display: block; font-size: 11px; font-weight: 700; letter-spacing: .6px;
                  text-transform: uppercase; color: var(--text-muted); margin-top: 3px; }
.dp-rp-line { font-size: 13px; color: var(--text-muted); }
.dp-rp-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.dp-rp-grid > div {
  padding: 9px 6px; border-radius: 10px; background: var(--raised); border: 1px solid var(--line);
}
.dp-rp-grid span { display: block; font-size: 10px; text-transform: uppercase; letter-spacing: .4px; color: var(--text-muted); }
.dp-rp-grid b { font-size: 15px; color: var(--text); font-variant-numeric: tabular-nums; }
.dp-rp-go {
  display: block; text-decoration: none; text-align: center;
  padding: 13px; font: inherit; font-size: 15px; font-weight: 800; cursor: pointer;
  border: 0; border-radius: 10px; background: #2f7fd6; color: #fff;
}
.dp-rp-go:hover { filter: brightness(1.1); }
.dp-rp-go:disabled { background: var(--raised); color: var(--success, #1dd1a1); cursor: default; }
.dp-rp-note { font-size: 11.5px; color: var(--text-muted); }
.dp-rp-note.bad { color: var(--danger, #ee5253); }
.dp-rain-done { border-color: var(--line); background: rgba(255,255,255,.04); }
.dp-rain-win { display: flex; justify-content: space-between; gap: 10px; font-size: 12.5px; }
.dp-rain-win b { color: #ffc531; }

/* The chat tab no longer needs to shout for rain - the pill beside it IS the
   announcement, and it stays put instead of scrolling away. Kept for any other
   caller of chatAlert(). */
/* Unread tags. Red rather than the brand blue: the tab, its icon and the alert
   state are all already blue, and a fourth blue thing on the same button is not
   a count anybody notices. This is the one element on the tab that means
   somebody is waiting on you. */
.dp-chat-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 17px; height: 17px; padding: 0 5px; margin-left: 2px;
  border-radius: 9px; background: var(--danger, #e5484d); color: #fff;
  font-size: 11px; font-weight: 800; line-height: 1;
}
.dp-chat-count[hidden] { display: none; }
.dp-chat.alert .dp-chat-tab { border-color: #2f7fd6; color: #fff; }
.dp-chat.alert .dp-chat-tab i { color: #6bc7ff; }
@keyframes dpRainPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(47,127,214,.55); }
  50%      { box-shadow: 0 0 0 7px rgba(47,127,214,0); }
}
@media (prefers-reduced-motion: reduce) {
  .dp-chat.alert .dp-chat-tab { animation: none; }
}

/* ---- the give-coins panel ---- */
.dp-coingrant { display: flex; flex-direction: column; gap: 10px; min-width: 260px; }
.dp-cg-who { display: flex; align-items: baseline; gap: 8px; font-size: 16px; font-weight: 800; color: var(--text); }
.dp-cg-rank { font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; }
.dp-cg-bal {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 10px 12px; border-radius: 10px; background: var(--raised); border: 1px solid var(--line);
}
.dp-cg-l { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; color: var(--text-muted); }
.dp-cg-v { font-size: 18px; font-weight: 800; color: #ffc531; font-variant-numeric: tabular-nums; }
.dp-coingrant input {
  width: 100%; padding: 11px 12px; font: inherit; font-size: 16px; font-weight: 700;
  color: var(--text); background: var(--raised); border: 1px solid var(--line);
  border-radius: 10px; outline: none;
}
.dp-coingrant input:focus { border-color: var(--primary); }
.dp-cg-quick { display: flex; gap: 6px; flex-wrap: wrap; }
.dp-cg-quick button {
  flex: 1; min-width: 62px; padding: 8px 4px; font: inherit; font-size: 12px; font-weight: 700;
  cursor: pointer; color: var(--text-body); background: var(--raised);
  border: 1px solid var(--line); border-radius: 8px;
}
.dp-cg-quick button:hover { border-color: #ffc531; color: #ffc531; }
.dp-cg-go {
  padding: 12px; font: inherit; font-size: 15px; font-weight: 800; cursor: pointer;
  border: 0; border-radius: 10px; background: #ffc531; color: #15181f;
}
.dp-cg-go:disabled { opacity: .5; cursor: not-allowed; }
.dp-cg-note { font-size: 12px; color: var(--text-muted); min-height: 15px; }
.dp-cg-note.bad { color: var(--danger, #ee5253); }
.dp-chat-menu a { border-top: 1px solid var(--line); margin-top: 3px; padding-top: 9px; color: var(--text-muted); }

.dp-chat-form { display: flex; gap: 6px; padding: 8px; border-top: 1px solid var(--line);
  /* The @ picker is positioned against this. */
  position: relative; }

/* The "@" picker. Sits ON the messages rather than pushing them, because the
   panel is a fixed height the player chose - growing the composer would shove
   the line they are replying to off the top just as they go to name them. */
.dp-chat-pick {
  position: absolute; bottom: 100%; left: 8px; right: 8px; z-index: 3;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 10px; margin-bottom: 6px; padding: 4px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .45);
  /* Roughly four rows, then it scrolls. A list taller than that covers the
     conversation on a phone, which is where the names come from. */
  max-height: 176px; overflow-y: auto;
}
.dp-chat-pick-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-radius: 7px; cursor: pointer;
}
.dp-chat-pick-row.sel { background: rgba(var(--accent-rgb), .18); }
.dp-chat-pick-av {
  width: 20px; height: 20px; border-radius: 50%; flex: 0 0 20px;
  object-fit: cover; background: var(--raised);
}
.dp-chat-pick-name {
  font-weight: 700; color: var(--text); font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Which of two same-named accounts this is. Pushed right so the names stay in
   one column and the eye can run down them. */
.dp-chat-pick-here {
  margin-left: auto; flex: 0 0 auto;
  font-size: 10px; letter-spacing: .3px; text-transform: uppercase;
  color: var(--text-muted);
}
/* ---- the emoji picker -------------------------------------------------------
   Positioned against the form, like the @ picker, and for the same reason: the
   panel is docked to the bottom of a phone and anything measured against the
   viewport ends up under the keyboard. */
.dp-chat-emo {
  position: absolute; bottom: 100%; left: 8px; right: 8px; z-index: 4;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 10px; margin-bottom: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .45);
  display: flex; flex-direction: column; overflow: hidden;
}
.dp-chat-emo[hidden] { display: none; }

.dp-emo-tabs {
  display: flex; gap: 2px; padding: 5px 5px 0;
  border-bottom: 1px solid var(--line);
}
/* Every rule for the panel's own buttons is scoped under .dp-chat-emo, and it
   has to be: this panel lives INSIDE the composer form (see the markup for
   why), and `.dp-chat-form button` above paints every button in there the
   primary colour. Two classes beats one class plus one element, so the scope
   wins on specificity without a single !important. */
.dp-chat-emo .dp-emo-tab {
  flex: 1 1 0; min-width: 0; padding: 6px 0 7px;
  background: transparent; border: 0; border-bottom: 2px solid transparent;
  color: var(--text-faint); font-size: 16px; cursor: pointer; line-height: 1;
  border-radius: 0;
}
.dp-chat-emo .dp-emo-tab:hover { color: var(--text-body); }
.dp-chat-emo .dp-emo-tab.on { color: var(--primary); border-bottom-color: var(--primary); }

/* Around five rows before it scrolls. Taller than this and the picker covers
   the conversation it is being written into. */
.dp-emo-body { max-height: 186px; overflow-y: auto; overscroll-behavior: contain; padding: 6px; }
.dp-emo-body::-webkit-scrollbar { width: 6px; }
.dp-emo-body::-webkit-scrollbar-thumb { background: var(--soft-strong); border-radius: 99px; }

/* auto-fill, not a fixed column count: the panel is as wide as the chat, and
   the chat is a width the player dragged. */
.dp-emo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(32px, 1fr)); gap: 2px; }
.dp-chat-emo .dp-emo {
  aspect-ratio: 1; border: 0; background: transparent; color: inherit; cursor: pointer;
  border-radius: 7px; padding: 0; line-height: 1;
  /* The glyph font, not the UI font - Poppins carries no emoji, so without this
     the cells fall to whatever the browser picks and can disagree with how the
     same character renders in the message list two inches above. */
  font-family: var(--dp-emoji), sans-serif;
  font-size: 20px;
  display: flex; align-items: center; justify-content: center;
}
.dp-chat-emo .dp-emo:hover { background: var(--soft-strong); }

/* The trigger. Sits between the box and Send, and is deliberately NOT the
   button colour - one primary action per row, and it is the one that posts. */
/* The trigger. Scoped the same way and for the same reason as the cells above.
   Deliberately NOT the button colour - one primary action per row, and it is
   the one that posts the message. */
.dp-chat-form .dp-chat-emo-btn {
  flex: 0 0 auto; padding: 0 10px;
  background: var(--field); color: var(--text-muted);
  border: 1px solid var(--line); border-radius: 9px; cursor: pointer;
  font-size: 16px;
}
.dp-chat-form .dp-chat-emo-btn:hover:not(:disabled) { color: var(--text); }
.dp-chat-form .dp-chat-emo-btn[aria-expanded="true"] { color: var(--primary); border-color: var(--primary); }

/* Emoji inside a message want the emoji font too - so name it as a FALLBACK,
   after the text font.

   Not font-variant-emoji: emoji. That property does not mean "use the emoji
   font for emoji"; it forces emoji presentation onto every character that has
   both a text and an emoji form, and Unicode's variation-sequence list puts the
   keycap bases 0-9, # and * in that set. Every digit in the panel came back
   from the colour emoji font - both what players typed and the figures in the
   automatic win lines. Phones only, because that is where the property is
   supported and where the emoji font has the digit glyphs to give.

   As a fallback the split is automatic: digits are in Poppins so they never
   reach the emoji font, emoji are not so they do. */
.dp-chat-text {
  font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', var(--dp-emoji), sans-serif;
}

.dp-chat-form input {
  flex: 1; min-width: 0; padding: 9px 11px; font: inherit; color: var(--text);
  /* Sunk into the dark strip rather than raised off it. --field, not a fixed
     grey, so this is still an input ground on a light theme. */
  background: var(--field); border: 1px solid var(--line); border-radius: 9px; outline: none;
}
.dp-chat-form input:focus { border-color: var(--primary); }
.dp-chat-form input:disabled { opacity: .6; }
.dp-chat-form button {
  padding: 0 12px; border: 0; border-radius: 9px; cursor: pointer;
  background: var(--button); color: var(--on-button); font-size: 15px;
}
.dp-chat-form button:disabled { opacity: .45; cursor: not-allowed; }
.dp-chat-note { padding: 0 12px 9px; font-size: 12px; color: var(--text-muted); }

/* ---- the mute dialog -------------------------------------------------------
   Four menu items became one, because picking a duration off a hover menu was
   an irreversible act with no chance to also clear what they posted, and no
   chance to change your mind. Shares the coin dialog's furniture - the same
   .dp-cg-* label, note and action button - because they are the same kind of
   thing: a small form about one person, opened from the same menu. */
.dp-mute { display: flex; flex-direction: column; gap: 4px; }
.dp-mute-state {
  font-size: 12px; color: var(--text-body); line-height: 1.5;
  background: var(--raised); border: 1px solid var(--line);
  border-radius: 9px; padding: 8px 10px; margin-bottom: 6px;
}
.dp-mute-state[hidden] { display: none; }
.dp-mute-lens { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; margin-bottom: 12px; }
.dp-mute-len {
  padding: 9px 10px; border-radius: 9px; cursor: pointer;
  background: var(--raised); border: 1px solid var(--line); color: var(--text-body);
  font: inherit; font-size: 13px; font-weight: 700;
}
.dp-mute-len:hover { border-color: var(--text-faint); color: var(--text); }
/* The chosen one, and it has to be obvious - the button underneath changes its
   own label to match, but a form where you cannot see what you picked is a form
   you press twice. */
.dp-mute-len.on {
  background: rgba(var(--accent-rgb, 0,155,214), 0.14);
  border-color: var(--primary); color: var(--text);
}
.dp-mute-purge {
  display: flex; align-items: center; gap: 9px; cursor: pointer;
  font-size: 12.5px; color: var(--text-body); margin-bottom: 12px;
}
.dp-mute-purge input { width: 16px; height: 16px; accent-color: var(--primary); flex: 0 0 auto; }
.dp-cg-go.danger { background: var(--danger); color: #fff; }
.dp-cg-go.danger:hover { filter: brightness(1.08); }
/* An Unmute row is not a destructive one, so it does not wear the red the mute
   rows above it do. */
.dp-chat-menu button.ok { color: var(--success); }

/* A command row in the composer's picker. Same list the "@" names appear in, so
   it inherits the row, the hover and the selected state - only the contents
   differ. Monospace on the command and its arguments because they are things you
   type character for character, not words you read. */
.dp-chat-pick-row.cmd { gap: 8px; }
.dp-chat-pick-cmdic { font-size: 14px; color: var(--primary); width: 16px; text-align: center; }
.dp-chat-pick-row.cmd .dp-chat-pick-name {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px; font-weight: 700;
}
.dp-chat-pick-args {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px; color: var(--text-faint); white-space: nowrap;
}

/* A staff-only line - the record of a slash command someone ran. Dimmed, with
   a dashed left edge, because it is not part of the conversation: it is a note
   between the people moderating it. Normal clients never receive one, so this
   is only ever seen by somebody who already knows why it looks different. */
.dp-chat-msg.staffonly {
  opacity: 0.62;
  border-left: 2px dashed var(--line);
  padding-left: 8px; margin-left: -10px;
}
.dp-chat-msg.staffonly:hover { opacity: 0.85; }
.dp-chat-staff {
  font-size: 9.5px; font-weight: 800; letter-spacing: 0.4px; text-transform: uppercase;
  color: var(--text-faint); border: 1px solid var(--line);
  border-radius: 5px; padding: 0 4px; vertical-align: 1px;
}
.dp-chat-note a { color: var(--primary); }

@media (max-width: 900px) {
  /* Full width on a phone, and it starts closed - see the note in shell.js. */
  .dp-chat { right: 0; left: 0; width: auto; bottom: var(--dp-botnav-h); }
  /* Shut, the floating pill is gone: the bottom bar's Chat tab is the way in
     now, and the pill was a second door to the same room sitting on top of the
     last row of game tiles. It comes straight back the moment the panel opens -
     as the panel's own header and grab bar, which is what the .dp-chat.open
     rules below turn it into. Unread moves to the tab's dot (shell.js). */
  .dp-chat:not(.open) .dp-chat-tab { display: none; }
  .dp-chat-tab { margin-right: 12px; }
  .dp-chat-inner { border-radius: 12px 12px 0 0; }
  /* Taller grab strip on a phone - 16px is a comfortable mouse target and a
     miserable thumb one. */
  .dp-chat-grip { height: 22px; flex-basis: 22px; }

  /* ---- open on a phone: the tab IS the drag bar --------------------------
     Closed, the tab is a floating pill sitting over the page. Open, that pill
     plus a separate grab strip underneath cost two bars of chrome above the
     first message - about 60px of a screen that has to hold a game AND a
     conversation, and the pill covers whatever the game put at the bottom,
     which on most of them is the button you press to play.

     So when it opens the tab stops floating and becomes the panel's header:
     full width, flush with the messages, carrying the grab line itself. The
     separate strip goes, and its height goes back to the messages.

     No JS needed for the drag - the tab's own handler already opens, resizes
     and closes in one gesture (chatTabDrag), which is exactly what a header
     that doubles as a grab bar has to do. */
  .dp-chat.open .dp-chat-tab {
    flex: 1; margin-right: 0; position: relative;
    padding: 6px 12px; border-radius: 12px 12px 0 0;
  }
  /* The grab line, so it still reads as something you can pull. Centred on the
     bar rather than on the label, because the gesture belongs to the whole
     strip. */
  .dp-chat.open .dp-chat-tab::after {
    content: ''; position: absolute; left: 50%; top: 50%;
    width: 40px; height: 4px; margin: -2px 0 0 -20px;
    border-radius: 2px; background: var(--line);
  }
  .dp-chat.open .dp-chat-inner { border-radius: 0; }
  .dp-chat.open .dp-chat-grip { display: none; }

  /* How much of the bottom of the screen the chat dock is using right now: the
     collapsed tab when it's shut, the panel plus its merged header when it's
     open. 34px is that header; it was 46 when an open chat still carried a
     floating tab on top of the panel.

     Declared once and read twice - by the page padding below, and by the sticky
     action button in the game layout. That is the point of it being a variable
     rather than the number written out in both places: the button parks exactly
     on top of the chat, and the two cannot drift apart when somebody changes
     the dock. */
  /* The tab bar is part of this now. --dp-chat-offset is read by the page
     padding below AND by every game's sticky action button (see GAME-UI.md), so
     folding the bar in here is what keeps that button parked above the bar
     instead of behind it - without touching a single game page. */
  /* Shut, the chat now occupies nothing - the 46px was the pill, which is gone.
     So the only bottom chrome is the tab bar, and the page gets those 46px back. */
  body.dp-shell { --dp-chat-offset: var(--dp-botnav-h); }
  body.dp-shell.dp-chat-open {
    --dp-chat-offset: calc(var(--dp-chat-h, 380px) + 34px + var(--dp-botnav-h));
  }

  /* Reserve the space the panel occupies rather than floating it over the page.
     Without this the open chat sits on top of whatever is at the bottom of a
     game - on the slot, that is the SPIN button. The collapsed tab gets its own
     smaller reservation for the same reason. */
  body.dp-shell { padding-bottom: var(--dp-chat-offset); }
}

/* Telegram's login widget - an <iframe> we don't control the inside of, so all
   we can do is give it room and centre it under the Kick button. In the top bar
   it has to sit on the baseline with everything else. */
.dp-tg-login { display: flex; justify-content: center; margin-top: 12px; min-height: 40px; }
.dp-topbar .dp-tg-login { margin: 0 0 0 8px; min-height: 0; align-items: center; }

/* ---- responsive ----------------------------------------------------------- */

@media (max-width: 900px) {
  /* Rail becomes an off-canvas drawer; the burger in the top bar opens it. The
     bar keeps its own row - it is the one piece of chrome that never moves. */
  body.dp-shell, body.dp-shell.rail-thin {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "top" "col";
  }
  /* The drawer starts BELOW the bar rather than covering it, so the burger that
     opened it is still on screen (and is still what closes it). */
  #dpSidebar {
    position: fixed; left: 0; z-index: 60;
    top: var(--dp-top);
    /* Stops short of the bottom bar as well as the top one, for the same
       reason: the Menu tab that opened this is what shuts it again, so it
       has to still be on screen and still be tappable. */
    height: calc(100vh - var(--dp-top) - var(--dp-botnav-h));
    height: calc(100dvh - var(--dp-top) - var(--dp-botnav-h));
    width: var(--dp-rail);
    transform: translateX(-100%); transition: transform 0.2s ease;
    box-shadow: 0 0 40px rgba(0,0,0,0.5);
  }
  body.dp-shell.rail-open #dpSidebar { transform: none; }
  body.dp-shell.rail-thin #dpSidebar { width: var(--dp-rail); }
  /* Collapsed-rail rules must not apply to the drawer. */
  body.dp-shell.rail-thin .dp-navlabel,
  body.dp-shell.rail-thin .dp-chip { display: revert; }
  /* Explicit, not `revert`: these two are laid out with flex, and reverting a
     <button> lands on the UA's inline-block rather than back on our own rule. */
  body.dp-shell.rail-thin .dp-promos   { display: flex; }
  body.dp-shell.rail-thin .dp-navsec-h { display: flex; }
  body.dp-shell.rail-thin .dp-navsec.shut .dp-nav { display: none; }
  body.dp-shell.rail-thin .dp-navlink { justify-content: flex-start; padding: 9px 10px; }
  /* ...including the nesting. The drawer is a full-width rail with labels on,
     so the elbow has a parent row to point at again. Same specificity as the
     collapsed rules above, later in the file, inside the query - so it wins
     here and only here. */
  body.dp-shell.rail-thin .dp-navlink.dp-navsub {
    margin-left: 18px; padding-left: 12px; font-size: 12.5px;
  }
  body.dp-shell.rail-thin .dp-navlink.dp-navsub::before { display: block; }
  body.dp-shell.rail-thin .dp-navlink.dp-navsub > i { font-size: 15px; flex: 0 0 18px; }
  /* Narrowing a drawer to icons isn't a thing you can want, and the burger that
     used to take this slot now lives in the bottom bar - which is the corner of
     a phone your thumb actually reaches. Both go; the top-left is just the logo.
     #dpBurger stays in the DOM and stays wired: it is one of the five things
     that can open the drawer, and hiding it costs nothing. */
  .dp-rail-toggle { display: none; }
  .dp-burger { display: none; }

  /* Challenges follows it down for the same reason - and unlike up here, the
     bottom bar shows it whether or not you're logged in. See BOTNAV. */
  .dp-top-gift { display: none; }

  /* Your face moves out of the corner and into the drawer, where there is room
     for your name and your level beside it instead of a 26px disc and a caret.
     The pill goes with it - two ways to the same account page, one of them in
     the hardest corner of the screen to reach, is one too many. */
  #dpAccount { display: none; }
  /* The rail's own 10px inset, so the block lines up with the promo cards and
     the nav sections under it instead of running to both edges. */
  .dp-me { display: block; padding: 0 10px; margin-bottom: 12px; }
  .dp-navlink.dp-logout { display: flex; }

  /* The scrim stops at the bar for the same reason the drawer does. */
  .dp-scrim {
    position: fixed; left: 0; right: 0; bottom: 0; top: var(--dp-top);
    z-index: 55; background: rgba(0,0,0,0.55);
    opacity: 0; pointer-events: none; transition: opacity 0.2s;
  }
  body.dp-shell.rail-open .dp-scrim { opacity: 1; pointer-events: auto; }
  /* Staff Play/Admin pills are a desktop affordance. On a phone they crowd the
     top bar and push the coin balance - which every viewer needs - toward the
     edge, to serve the handful of people who run the stream from a desktop
     anyway. /web/play and /admin are still reachable by URL. */
  .dp-modtools { display: none; }

  /* And the chat button goes with them, for a better reason: down here the chat
     is a full-width panel docked to the bottom of the screen with its own tab
     across it - not the 30px corner strip it is on a desktop. It cannot be
     missed, so a second way to open it is spending top-bar width on a door
     that's already standing open. */
  .dp-top-chat { display: none; }

  /* The bell STAYS. The other two are shortcuts to things already on screen
     down here - the chat is a docked panel with its own tab, and Challenges is
     a tab in the bottom bar - but an announcement has nowhere else to appear,
     and the whole point of it is reaching somebody who was not looking. */
  .dp-bell { display: block; }
  /* Full width under the bar, not hanging off the button. The bell sits a third
     of the way across a 420px screen, so a 340px panel anchored to it lands
     flush against one edge whichever way it is pinned - and a panel that touches
     the screen edge reads as cut off. Fixed to the viewport for the same reason
     the drawer is: it belongs to the bar, not to the button inside it. */
  .dp-bell-drop {
    position: fixed; top: calc(var(--dp-top) + 4px);
    left: 12px; right: 12px; width: auto; transform: none;
    max-height: calc(100dvh - var(--dp-top) - var(--dp-botnav-h) - 24px);
  }

  /* Side padding is kept tight on small screens. The cards already carry their
     own inset, so gutter out here is doubled-up margin - and on a keno board it
     comes straight out of the tiles, which are the thing worth the pixels.
     The front page is the exception: nothing there needs the last few pixels,
     and it reads better with a little air. */
  .dp-main { padding: 18px 11px 50px; }
  body.dp-home .dp-main { padding-left: 16px; padding-right: 16px; }
  body.dp-roomy .dp-main { padding-left: 16px; padding-right: 16px; }
  /* The legal and explainer pages are the other exception, and they need it
     more than the front page does: everywhere else the text sits inside a card
     that carries its own inset, so 11px of gutter is really 11 + the card's.
     These are bare prose against the background, so 11px is all there is, and
     a paragraph starting a few pixels from the edge of a phone is unpleasant
     to read for a page of it. */
  body.legal-page .dp-main { padding-left: 18px; padding-right: 18px; }
  #dpTop { padding: 0 12px; gap: 8px; }
  .dp-hero { padding: 22px 20px; }
  .dp-hero h1 { font-size: 22px; }
  /* Scoped to the hero: the same class is the full-size button on the login
     gates, which has a whole card to itself and shouldn't shrink. */
  .dp-hero-cta { gap: 8px; }
  .dp-hero-cta .dp-btn-lg { padding: 11px 15px; font-size: 13px; }

  /* ---- the tab bar --------------------------------------------------------
     54px of bar plus whatever the phone reserves for its own home indicator.
     The env() is added into the variable rather than only into the padding,
     because everything else on the screen positions itself off the variable -
     an iPhone would otherwise dock the chat 34px inside the bar. */
  body.dp-shell { --dp-botnav-h: calc(54px + env(safe-area-inset-bottom, 0px)); }
  .dp-botnav {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    position: fixed; left: 0; right: 0; bottom: 0;
    height: var(--dp-botnav-h);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    /* Above the scrim (55) and the drawer (60) so the Menu tab stays lit and
       tappable while the drawer it opened is over the page; below the top bar
       (70), which the two never fight over since they're at opposite edges. */
    z-index: 68;
    background: var(--surface);
    /* The top bar gets away with no border because it scrolls off with the
       page. This one is fixed and the page runs underneath it, so three points
       of lightness is not enough on its own. */
    border-top: 1px solid var(--line);
  }
  .dp-bot-tab {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; position: relative;
    border: 0; background: none; padding: 0; margin: 0;
    font: inherit; font-size: 10.5px; font-weight: 600; letter-spacing: 0.2px;
    color: var(--text-muted); text-decoration: none; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .dp-bot-tab i { font-size: 21px; line-height: 1; }
  /* All four the same, always. There is no "current tab" here on purpose.
     Two of them are toggles - Menu opens the drawer, Chat opens the panel - and
     highlighting those alongside "the page you are on" put two unrelated ideas
     in one colour: on the home page with the chat open, half the bar lit and
     none of it meant the same thing.
     Whether the drawer or the chat is open is already unmistakable, because the
     thing itself is on the screen. Which page you are on is what the page says.
     A bar that stays still is easier to read than one that keeps changing which
     of its four labels is brightest.
     The .on class stays: it is what keeps aria-expanded honest on the two
     toggles, which is a different job from painting them. */
  .dp-bot-tab:active { opacity: 0.6; }
  /* Unread chat, on the icon rather than beside the label - the label is 10px
     and a dot next to it reads as a full stop. */
  .dp-bot-dot {
    position: absolute; top: 6px; left: 50%; margin-left: 6px;
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--primary); border: 2px solid var(--surface);
  }
  .dp-bot-dot[hidden] { display: none; }

  /* The one tab that is allowed to be different, and only while a tournament is
     actually running. The note above says there is no "current tab" here, and
     that still holds - this is not "the page you are on", it is "this is
     happening right now and it stops". Same green the tournament page uses for
     LIVE NOW, and it takes itself off when the clock does.

     The bar has room for exactly one of these. If a second thing ever wants to
     shout from down here, they cannot both. */
  .dp-bot-tab.tour { color: var(--success); }
  .dp-bot-tab.tour i { color: var(--success); }

  /* The time left, riding ON the bar's top edge rather than inside it.
     Inside was the obvious place and it was wrong twice: the pill clipped
     against the top of the nav, and making room for it pushed this tab's icon
     and label 5.5px below the other three - which is exactly the alignment the
     rest of this block exists to keep. Overhanging costs nothing, because the
     nav is fixed to the bottom of the screen and the page scrolls behind it.
     The border in the surface colour is the same trick the unread dot uses to
     sit on top of something. */
  .dp-bot-timer {
    position: absolute; top: -13px; left: 50%; transform: translateX(-50%);
    font-size: 9px; font-weight: 800; line-height: 1;
    letter-spacing: 0.02em; font-variant-numeric: tabular-nums;
    color: var(--on-button, #0b0d12);
    background: var(--success);
    border: 2px solid var(--surface);
    border-radius: 999px; padding: 2px 6px;
    pointer-events: none; white-space: nowrap;
  }
}

@media (max-width: 520px) {
  /* Tighter again on phones - the narrower the screen, the more a fixed gutter
     costs as a share of it. */
  .dp-main { padding: 15px 7px 46px; }
  body.dp-home .dp-main { padding-left: 13px; padding-right: 13px; }
  body.dp-roomy .dp-main { padding-left: 14px; padding-right: 14px; }
  /* Section headers put the title and its subtitle on one flex row, which on a
     phone meant "DegenParty Originals" wrapping to two lines with "Play anytime
     with Degen Coins" wrapping alongside it - four lines of chrome above every
     grid. Stack them and bring the type down. The gap between sections comes in
     too - it adds up over four of them. */
  .dp-sec { margin-bottom: 18px; }
  .dp-sec-head { flex-direction: column; align-items: flex-start; gap: 1px; margin-bottom: 10px; }
  .dp-sec-head h2 { font-size: 15px; gap: 7px; }
  .dp-sec-head h2 > i { font-size: 16px; }
  .dp-sec-head .dp-sec-sub { font-size: 11.5px; }

  /* Tiles drop from portrait to square. At two-up on a phone a 3/4 tile is
     ~220px tall, so one row filled the screen and the second was a scroll away
     - which hides half the games from someone who only skims the front page. */
  .dp-tiles { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 10px; }
  .dp-tile { aspect-ratio: 1 / 1; }
  .dp-tile-art > i { font-size: 40px; }
  .dp-tile-cap { padding: 16px 10px 9px; }
  .dp-tile-t { font-size: 13.5px; }
  /* Top bar on a phone: burger, logo, balance, you. Everything in the right-hand
     cluster gives up whatever it can so the logo keeps its full width - the
     balance loses its "coins" label and some padding, the account pill drops the
     username for the avatar it already shows, and the login button loses "with
     Kick" (the green and the icon carry that on their own). */
  #dpTop { padding: 0 11px; gap: 8px; }
  .dp-top-left { gap: 8px; }
  .dp-top-right { gap: 7px; }
  .dp-iconbtn { width: 32px; height: 32px; font-size: 15px; border-radius: 9px; }
  /* The login button keeps both marks and its label down here - it is already
     one button doing the work of two, and it is the only call to action a
     logged-out visitor has in the bar. */
  .dp-login-btn { padding: 7px 12px; font-size: 12.5px; gap: 6px; }
  .dp-login-ics { gap: 4px; font-size: 14px; }
  .dp-top-brand img { height: 29px; }
  .dp-coins { padding: 5px 10px; font-size: 12.5px; gap: 6px; }
  .dp-coins i { font-size: 13px; }
  .dp-coins .dp-coin { width: 16px; height: 16px; }
  .dp-coins .dp-coins-label { display: none; }
  /* ...except in tournament mode, where the word is the whole point. The label
     is hidden on a phone because "coins" is obvious; "tournament" is not, and
     this is the one place someone might otherwise read a falling number as
     their own balance draining away. */
  .dp-coins.tour .dp-coins-label { display: inline; font-size: 10.5px; }
  /* The avatar IS the identity here; the name beside it spent ~90px repeating
     what the picture already says, and it was the first thing to run out of
     room. The caret stays - without it the avatar reads as decoration rather
     than a menu.
     .account-* belongs to style.css, so these are scoped under #dpTop: it keeps
     them off any other account pill and wins on specificity rather than on
     shell.css happening to be the later <link>. */
  #dpTop .account-name { display: none; }
  #dpTop .account-trigger { padding: 3px 8px 3px 3px; gap: 5px; }
  #dpTop .account-av { width: 26px; height: 26px; }

  /* The hero is a pitch, not the product. Every pixel it takes is a pixel of
     games nobody scrolls to - so on a phone it shrinks hard: smaller heading,
     smaller body, tighter leading, less padding. Enough comes back that the
     first row of Originals is on screen from the top.
     Buttons also hold one line here; stacked, they pushed the live streamers
     below the fold too. */
  .dp-hero { padding: 15px 14px; }
  .dp-hero h1 { font-size: 18px; margin-bottom: 6px; }
  .dp-hero p  { font-size: 12.5px; line-height: 1.5; margin-bottom: 12px; }
  .dp-hero-cta { flex-wrap: nowrap; gap: 7px; }
  .dp-hero-cta .dp-btn-lg {
    padding: 9px 11px; font-size: 12px; gap: 5px;
    min-width: 0; white-space: nowrap;
  }
  .dp-hero-cta .dp-btn-lg i { font-size: 13px; }

  /* Two streamer cards per row. auto-fill drops to a single column at this
     width, and one card per line spends most of its width on empty space next
     to a 52px avatar. The card shrinks to suit: smaller avatar, and the "go
     watch" chevron goes - the whole card is the link anyway. */
  .dp-streams { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 9px; }
  .dp-stream { padding: 11px; gap: 9px; }
  .dp-stream-av { width: 40px; height: 40px; font-size: 15px; }
  .dp-stream-name { font-size: 13px; }
  .dp-stream-sub { font-size: 11px; }
  .dp-stream-go { display: none; }
  /* Half a phone leaves ~95px for text beside the avatar. The Live pill is the
     part that matters; "1,234 watching" alongside it would just push the name
     into an ellipsis. */
  .dp-stream-viewers { display: none; }
}

/* ---- legal & explainer pages ------------------------------------------------
   Terms, privacy and the how-it-works page. Long-form reading rather than a
   dashboard, so the rules are different from the rest of the site: a measured
   line length, real paragraph spacing, and headings that can be scanned by
   someone looking for one specific answer. Shared because three pages use them
   and they must not drift apart. */
.legal { max-width: 720px; margin: 0 auto; }
.legal-head { margin-bottom: 26px; }
.legal-head h1 { font-size: 30px; font-weight: 900; color: var(--text); margin: 0 0 8px; line-height: 1.15; }
.legal-head .legal-sub { font-size: 14px; color: var(--text-muted); line-height: 1.6; margin: 0; }
.legal-updated {
  font-size: 11px; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--text-faint); margin-top: 12px;
}

/* The one thing on the page that has to be read. Used once per page, at the
   top, for the fact people keep getting wrong. */
.legal-call {
  border: 1px solid color-mix(in srgb, var(--primary) 45%, transparent);
  background: color-mix(in srgb, var(--primary) 10%, transparent);
  border-radius: var(--radius-lg); padding: 16px 18px; margin-bottom: 26px;
  display: flex; gap: 13px; align-items: flex-start;
}
.legal-call > i { color: var(--primary); font-size: 22px; flex: 0 0 auto; margin-top: 1px; }
.legal-call p { margin: 0; font-size: 14.5px; line-height: 1.65; color: var(--text); }
.legal-call p + p { margin-top: 8px; }
.legal-call b { color: var(--text); }

.legal section { margin-bottom: 30px; }
.legal h2 {
  font-size: 17px; font-weight: 800; color: var(--text);
  margin: 0 0 10px; padding-bottom: 8px; border-bottom: 1px solid var(--line);
  display: flex; align-items: baseline; gap: 9px;
}
/* Numbered so a dispute can point at a clause instead of quoting a paragraph. */
.legal h2 .legal-n {
  font-size: 12px; font-weight: 800; color: var(--text-faint);
  font-variant-numeric: tabular-nums; flex: 0 0 auto;
}
.legal h3 { font-size: 14px; font-weight: 800; color: var(--text); margin: 18px 0 6px; }
.legal p, .legal li { font-size: 14px; line-height: 1.7; color: var(--text-body); }
.legal p { margin: 0 0 11px; }
.legal ul { margin: 0 0 11px; padding-left: 20px; }
.legal li { margin-bottom: 6px; }
.legal a { color: var(--primary); }
.legal b, .legal strong { color: var(--text); font-weight: 800; }
.legal code {
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12.5px;
  background: var(--raised); border: 1px solid var(--line);
  border-radius: 5px; padding: 1px 5px; color: var(--text-muted);
}

/* A two-column fact list - "what this is / what this isn't". Reads faster than
   the same content as prose, which is the whole job of the page. */
.legal-two { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px; }
.legal-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 15px 16px;
}
.legal-card h3 { margin: 0 0 9px; display: flex; align-items: center; gap: 8px; font-size: 13.5px; }
.legal-card.yes h3 i { color: var(--success); }
.legal-card.no  h3 i { color: var(--danger); }
.legal-card ul { margin: 0; padding-left: 18px; }
.legal-card li { font-size: 13px; line-height: 1.6; }
@media (max-width: 620px) {
  .legal-two { grid-template-columns: 1fr; }
  .legal-head h1 { font-size: 25px; }
  .legal p, .legal li { font-size: 13.5px; }
}

/* Numbered steps, for the eligibility walkthrough. */
.legal-steps { counter-reset: ls; margin: 0 0 12px; padding: 0; list-style: none; }
.legal-steps li {
  counter-increment: ls; position: relative; padding-left: 34px; margin-bottom: 11px;
}
.legal-steps li::before {
  content: counter(ls); position: absolute; left: 0; top: 1px;
  width: 23px; height: 23px; border-radius: 50%;
  background: var(--raised); border: 1px solid var(--line); color: var(--text-muted);
  font-size: 11.5px; font-weight: 800; display: flex; align-items: center; justify-content: center;
}

/* The site-wide footer. Every page gets it, because a terms link that only
   exists on the front page is a terms link nobody can find from where the
   question actually occurs to them. */
.dp-foot {
  border-top: 1px solid var(--line); margin-top: 40px; padding: 20px 0 8px;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 6px 16px; text-align: center;
}
.dp-foot a { color: var(--text-faint); font-size: 12px; font-weight: 700; text-decoration: none; }
.dp-foot a:hover { color: var(--text-muted); text-decoration: underline; }
.dp-foot .dp-foot-note {
  flex-basis: 100%; font-size: 11.5px; color: var(--text-faint); line-height: 1.6;
  margin-top: 4px;
}

/* ---- typing on a phone must not zoom the page ------------------------------
   Safari on iOS zooms the whole page in whenever you focus a field whose text
   is smaller than 16px - and it does NOT zoom back out when the keyboard goes
   away. So one message sent from a phone leaves the site scaled up and cropped
   for the rest of the visit, which is exactly what people were seeing after
   typing in chat.

   Nothing in the viewport meta can fix that without also banning pinch-zoom,
   which is a real accessibility control. 16px in the fields themselves is the
   only fix that costs nothing. Padding comes down where the field is a fixed
   width so the row keeps its height and the box keeps its contents.

   pointer:coarse rather than a width query - it's the touch keyboard that
   triggers this, not the screen size, and a narrow desktop window has neither. */
@media (pointer: coarse) {
  .dp-chat-form input,
  .dp-bet-input,
  .dp-fair-form input,
  .dp-coingrant input { font-size: 16px; }

  .dp-chat-form input { padding: 8px 11px; }
  .dp-fair-form input { padding: 7px 10px; }
}

/* --- badges ------------------------------------------------------------------
   Shared by three surfaces so they cannot drift: the unlock toast, the unlock
   card, and the collection page. Tier is a class - t-common ... t-emblem - and
   every one of them does the same thing: set --bt, the tier colour, which
   everything else on the card reads. Adding a tier is one block.

   The five colours are the ones the inventory and the blackjack badge case
   already use for item rarity, deliberately: two rarity ladders on one site
   should be the same ladder. Emblems are the exception - they are not a rarity,
   they are a section finished, so they wear the house gradient instead. */
.t-common    { --bt: #9aa6a0; }
.t-uncommon  { --bt: #2fd6bf; }
.t-rare      { --bt: #5aa9ff; }
.t-epic      { --bt: #c07bff; }
.t-legendary { --bt: #f5c451; }
.t-emblem    { --bt: var(--grad-b); }

/* ---- unlock toast (common + uncommon) ---
   Bottom LEFT, which is the one corner nothing else uses: chat owns bottom
   right, the coin counter owns top right. Stacks upward, newest at the bottom,
   so a badge landing while you read the last one does not shove it. */
.dp-badge-toasts {
  /* Clear of the rail, not on top of it. A toast is transient and the rail is
     not, and covering somebody's navigation to tell them they won 25 coins is
     the wrong trade. Follows the rail when it collapses to icons, and sits at
     the screen edge on a phone, where there is no rail to avoid. */
  position: fixed; left: calc(var(--dp-rail) + 16px); bottom: 16px; z-index: 60;
  display: flex; flex-direction: column; gap: 8px;
  pointer-events: none;
}
body.dp-shell.rail-thin .dp-badge-toasts { left: calc(var(--dp-rail-thin) + 16px); }
.dp-badge-toast {
  pointer-events: auto; cursor: pointer;
  display: flex; align-items: center; gap: 14px;
  padding: 13px 16px 13px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 4px solid var(--bt);
  border-radius: var(--radius-lg);
  box-shadow: 0 16px 38px rgba(0, 0, 0, 0.5);
  animation: dpBadgeToastIn 0.32s cubic-bezier(0.22, 1.3, 0.36, 1);
  transition: opacity 0.4s ease, transform 0.4s ease;
  width: min(400px, calc(100vw - 32px));
}
.dp-badge-toast.out { opacity: 0; transform: translateX(-14px); }
.dp-badge-toast:hover { border-color: var(--bt); transform: translateY(-2px); }
.dp-badge-toast:hover .dp-badge-toast-go { color: var(--bt); transform: translateX(2px); }
.dp-badge-toast:focus-visible { outline: 2px solid var(--bt); outline-offset: 2px; }
/* The one bit of the toast that says it does something. Small on purpose - the
   badge is the point, this is just the hint that there is more of it. */
.dp-badge-toast-go {
  flex: none; font-size: 14px; color: var(--text-faint);
  transition: color 0.14s ease, transform 0.14s ease;
}
/* The same medal as the unlock card and the collection page, at toast size - a
   badge should be the same object wherever it turns up. */
.dp-badge-toast-medal {
  flex: none; width: 46px; height: 46px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  border: 2px solid var(--bt);
  background:
    radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--bt) 28%, transparent) 0%, transparent 70%),
    var(--raised);
}
.dp-badge-toast-medal i { font-size: 24px; color: var(--bt); }
.dp-badge-toast-txt { min-width: 0; }
.dp-badge-toast-kicker {
  font-size: 10px; font-weight: 800; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--bt);
}
.dp-badge-toast-name {
  font-size: 16px; font-weight: 900; color: var(--text); line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dp-badge-toast-coins {
  margin-left: auto; flex: none;
  font-size: 14px; font-weight: 800; color: var(--primary);
  font-variant-numeric: tabular-nums;
}
/* No coins on this one, so nothing has pushed the caret to the right yet. */
.dp-badge-toast-txt + .dp-badge-toast-go { margin-left: auto; }
@keyframes dpBadgeToastIn {
  0%   { opacity: 0; transform: translateX(-24px) scale(0.96); }
  100% { opacity: 1; transform: none; }
}

/* ---- unlock card (rare and up) --- */
.dp-badge-pop {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  opacity: 0; transition: opacity 0.2s ease;
}
.dp-badge-pop.in { opacity: 1; }
.dp-badge-pop-scrim { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.66); }
.dp-badge-pop-card {
  position: relative;
  width: min(400px, 100%);
  padding: 22px 22px 18px;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--bt);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.03) inset;
  transform: scale(0.9) translateY(8px);
  transition: transform 0.28s cubic-bezier(0.22, 1.3, 0.36, 1);
  max-height: calc(100vh - 40px); overflow-y: auto;
}
.dp-badge-pop.in .dp-badge-pop-card { transform: none; }
.dp-badge-pop-tier {
  font-size: 10px; font-weight: 800; letter-spacing: 1.6px; text-transform: uppercase;
  color: var(--bt);
}
/* The medal. The glow is a radial behind the glyph rather than a box-shadow so
   it reads as light coming off the icon instead of a ring around a circle. */
.dp-badge-pop-medal {
  margin: 16px auto 14px;
  width: 112px; height: 112px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%,
      color-mix(in srgb, var(--bt) 34%, transparent) 0%,
      transparent 68%),
    var(--raised);
  border: 2px solid var(--bt);
  animation: dpBadgeMedal 0.6s cubic-bezier(0.22, 1.4, 0.36, 1);
}
.dp-badge-pop-medal i { font-size: 54px; color: var(--bt); }
/* A painting: the whole thing, square, big enough that the frame it was
   painted with is part of what you see. No circle, no ring - the picture has
   its own edge. The tier still shows in the label above it. */
.dp-badge-pop-medal.art {
  width: 208px; height: 208px; margin: 14px auto 12px;
  border-radius: 12px; border: none; background: none;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
}
.dp-badge-pop-medal.art img { width: 100%; height: 100%; object-fit: contain; border-radius: 12px; }
/* Who has it: overlapped faces and a count, under the picture. */
.dp-badge-pop-who {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  margin: 0 0 10px; font-size: 12px; font-weight: 700; color: var(--text-muted);
}
.dp-badge-pop-faces { display: flex; align-items: center; }
.dp-badge-pop-faces img, .dp-badge-pop-faces span, .dp-badge-pop-faces b {
  width: 26px; height: 26px; border-radius: 50%; object-fit: cover;
  border: 2px solid var(--surface); margin-left: -8px; background: var(--raised);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 800; color: var(--text-body); font-style: normal;
}
.dp-badge-pop-faces > :first-child { margin-left: 0; }
.dp-badge-pop-faces b { font-size: 10px; color: var(--text-muted); }
@media (max-width: 620px) {
  .dp-badge-pop-medal.art { width: 168px; height: 168px; }
}
@keyframes dpBadgeMedal {
  0%   { transform: scale(0.3) rotate(-18deg); opacity: 0; }
  60%  { transform: scale(1.08) rotate(3deg); opacity: 1; }
  100% { transform: none; }
}
.dp-badge-pop-name { font-size: 24px; font-weight: 900; color: var(--text); line-height: 1.2; }
.dp-badge-pop-blurb {
  margin-top: 7px; font-size: 13.5px; line-height: 1.5; color: var(--text-muted);
}
.dp-badge-pop-coins {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 12px; padding: 6px 12px;
  background: var(--raised); border-radius: var(--radius-pill);
  font-size: 13px; font-weight: 800; color: var(--primary);
  font-variant-numeric: tabular-nums;
}
.dp-badge-pop-prog { margin-top: 14px; text-align: left; }
.dp-badge-pop-prog-top {
  display: flex; justify-content: space-between;
  font-size: 11px; font-weight: 700; color: var(--text-faint);
  margin-bottom: 5px;
}
.dp-badge-pop-bar {
  height: 6px; border-radius: 3px; background: var(--raised); overflow: hidden;
}
.dp-badge-pop-bar span {
  display: block; height: 100%; background: var(--bt);
  transition: width 0.6s ease;
}
/* The other badges from the same round. Icons, not cards - the point is "these
   happened too", and the collection page is one tap away for the detail. */
.dp-badge-pop-more { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.dp-badge-pop-more-h {
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--text-faint); margin-bottom: 8px;
}
.dp-badge-pop-more-row {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px; margin-bottom: 6px;
  background: var(--raised); border-radius: 9px;
  border-left: 3px solid var(--bt);
  text-align: left; cursor: pointer;
  transition: background 0.14s ease;
}
.dp-badge-pop-more-row:hover { background: var(--soft-strong); }
.dp-badge-pop-more-row:focus-visible { outline: 2px solid var(--bt); outline-offset: 1px; }
.dp-badge-pop-more-row:last-child { margin-bottom: 0; }
.dp-badge-pop-more-row i { font-size: 19px; color: var(--bt); flex: none; }
.dp-badge-pop-more-row .nm {
  font-size: 13px; font-weight: 800; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dp-badge-pop-more-row .cn {
  margin-left: auto; flex: none;
  font-size: 12px; font-weight: 800; color: var(--primary);
  font-variant-numeric: tabular-nums;
}
.dp-badge-pop-go {
  display: block; width: 100%; margin-top: 16px; padding: 11px;
  border: 0; border-radius: var(--radius);
  background: var(--button); color: var(--on-button);
  font-size: 14px; font-weight: 800; cursor: pointer;
}
.dp-badge-pop-go:hover { background: var(--button-hover); }
.dp-badge-pop-link {
  display: block; margin-top: 10px;
  font-size: 12px; font-weight: 700; color: var(--text-faint); text-decoration: none;
}
.dp-badge-pop-link:hover { color: var(--text); }

/* ---- the collection page --- */
.dp-badge-hero {
  display: flex; flex-wrap: wrap; gap: 14px; align-items: center;
  padding: 16px 18px; margin-bottom: 18px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
}
.dp-badge-hero-n {
  font-size: 30px; font-weight: 900; color: var(--text);
  font-variant-numeric: tabular-nums; line-height: 1;
}
.dp-badge-hero-l { font-size: 12px; color: var(--text-muted); margin-top: 3px; }
.dp-badge-hero-say {
  font-size: 13px; line-height: 1.55; color: var(--text-muted);
  max-width: 52ch; margin: 0;
}
.dp-badge-hero-say b { color: var(--text); font-weight: 800; }
.dp-badge-hero-sep { width: 1px; align-self: stretch; background: var(--line); }
.dp-badge-hero-flair { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.dp-badge-sec { margin-bottom: 26px; }
.dp-badge-sec-head {
  display: flex; align-items: center; gap: 12px; margin-bottom: 4px;
}
.dp-badge-sec-head i { font-size: 20px; color: var(--primary); }
.dp-badge-sec-head h3 { margin: 0; font-size: 16px; font-weight: 800; color: var(--text); }
.dp-badge-sec-count {
  margin-left: auto; flex: none;
  font-size: 12px; font-weight: 800; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.dp-badge-sec-blurb {
  font-size: 12.5px; line-height: 1.5; color: var(--text-muted);
  margin: 0 0 10px; max-width: 70ch;
}
.dp-badge-sec-bar { height: 5px; border-radius: 3px; background: var(--raised); overflow: hidden; margin-bottom: 14px; }
.dp-badge-sec-bar span { display: block; height: 100%; background: var(--primary); }
.dp-badge-sec-bar.done span { background: var(--success); }

.dp-badge-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
}
.dp-badge-card {
  position: relative;
  padding: 14px 13px 13px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-align: center;
  transition: transform 0.14s ease, border-color 0.14s ease;
  /* A column so the foot can be pushed to the bottom. Grid already stretches
     every card in a row to the tallest of them, and without this the "nobody
     has this yet" line floated at whatever height its own blurb ended at - so a
     row of five cards had five different rules running across it. */
  display: flex; flex-direction: column;
}
.dp-badge-card.owned { border-color: color-mix(in srgb, var(--bt) 55%, transparent); }
.dp-badge-card:hover { transform: translateY(-2px); }
/* Locked cards are dimmed, not hidden. Seeing what you have not got yet is the
   entire mechanism - a collection page that only shows what you own is a
   trophy shelf, and a trophy shelf does not make anyone play. */
.dp-badge-card.locked { opacity: 0.55; }
.dp-badge-medal {
  width: 58px; height: 58px; margin: 0 auto 10px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--raised);
  border: 2px solid var(--line);
}
.dp-badge-card.owned .dp-badge-medal {
  border-color: var(--bt);
  background:
    radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--bt) 26%, transparent) 0%, transparent 70%),
    var(--raised);
}
.dp-badge-medal i { font-size: 27px; color: var(--text-faint); }
.dp-badge-card.owned .dp-badge-medal i { color: var(--bt); }
.dp-badge-name { font-size: 13.5px; font-weight: 800; color: var(--text); }
.dp-badge-tier {
  display: inline-block; margin-top: 4px;
  font-size: 9.5px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase;
  color: var(--bt);
}
.dp-badge-blurb {
  margin-top: 7px; margin-bottom: 10px;
  font-size: 11.5px; line-height: 1.45; color: var(--text-muted);
}
.dp-badge-foot {
  margin-top: auto; padding-top: 9px; border-top: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: 10.5px; font-weight: 700; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.dp-badge-foot .earned { color: var(--success); }
/* Progress on a counter badge - "412 / 1,000" with a bar under it. Only drawn
   for badges that HAVE a total, which is what makes it worth showing at all:
   a bar that could be anywhere between 0 and 1 is decoration. */
.dp-badge-prog { margin-top: 9px; }
.dp-badge-prog-bar { height: 4px; border-radius: 2px; background: var(--raised); overflow: hidden; }
.dp-badge-prog-bar span { display: block; height: 100%; background: var(--bt); }
.dp-badge-prog-n {
  margin-top: 4px; font-size: 10.5px; font-weight: 700; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
/* The little board a pattern badge draws of itself. Eight columns of dots, with
   the badge's tiles lit - it says "these four squares" in a way no sentence
   about corners does. */
.dp-badge-mini {
  display: grid; gap: 2px; width: 74px; margin: 8px auto 0;
  grid-template-columns: repeat(8, 1fr);
}
.dp-badge-mini i {
  aspect-ratio: 1; border-radius: 1.5px; background: var(--raised);
}
.dp-badge-mini i.on { background: var(--bt); }
/* The same board on the unlock card, where there is room to actually read the
   shape. Wider gaps and rounder cells at this size - at 74px they would close
   up into a blur, at 168px they read as tiles on a board. */
.dp-badge-mini.lg { width: 168px; gap: 4px; }
.dp-badge-mini.lg i { border-radius: 3px; }
.dp-badge-mini.lg i.on { box-shadow: 0 0 8px color-mix(in srgb, var(--bt) 55%, transparent); }

/* The tiles as numbers, under the drawing of them. In the badge's own tier
   colour and tabular, so a column of pattern cards lines its numbers up. Wraps
   rather than scrolls - six numbers is the largest shape there is and two rows
   of them is fine, a card that scrolls sideways is not. */
.dp-badge-nums {
  margin: 7px auto 0; max-width: 100%;
  font-size: 10.5px; font-weight: 800; line-height: 1.5;
  letter-spacing: 0.4px; color: var(--bt);
  font-variant-numeric: tabular-nums;
}
.dp-badge-nums.lg { margin-top: 11px; font-size: 13px; letter-spacing: 0.6px; }

/* A picture inside a medal - the Masterpiece gallery. Unowned, it goes grey and
   dark so the card still reads as locked while showing exactly which picture is
   missing.

   SQUARE, not the circle every other medal wears. These are paintings: they
   were drawn square, inside a frame that is part of the picture, and a circle
   cut the frame off on all four sides and took the corners of the painting with
   it. The circle is right for an icon, which is a shape floating in a disc;
   it is wrong for a picture that already has its own edge. Same call the big
   card made (.dp-badge-pop-medal.art) - this just brings the small preview into
   line with it. */
.dp-badge-medal.art, .dp-badge-toast-medal.art, .bd-recent-medal.art {
  border-radius: 10px; border: none; background: none; padding: 0;
}
.dp-badge-medal.art img, .dp-badge-toast-medal.art img, .bd-recent-medal.art img {
  border-radius: 10px;
}
.dp-badge-card.owned .dp-badge-medal.art {
  border: none;
  box-shadow: 0 0 0 2px var(--bt), 0 4px 14px rgba(0, 0, 0, 0.4);
}
.dp-badge-medal img, .dp-badge-pop-medal img, .dp-badge-toast-medal img, .bd-recent-medal img {
  width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block;
}
.dp-badge-card.locked .dp-badge-medal img { filter: grayscale(1) brightness(0.45); }
.dp-badge-medal, .dp-badge-pop-medal, .dp-badge-toast-medal, .bd-recent-medal { overflow: hidden; }

/* Sits between the description and the coins, boxed off so it reads as a
   picture of the board rather than more of the sentence above it. */
.dp-badge-pop-shape {
  margin-top: 13px; padding: 12px;
  background: var(--field); border: 1px solid var(--line); border-radius: 10px;
  /* Column, not row: the numbers under this are a flex sibling of the board,
     and a centring row put them alongside it - which shoved the board off
     centre and squeezed six numbers into what was left. */
  display: flex; flex-direction: column; align-items: center;
}

/* Flair picker - which emblem rides next to your name in chat. */
.dp-flair-row { display: flex; flex-wrap: wrap; gap: 10px; }
.dp-flair-opt {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 13px 9px 10px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); cursor: pointer;
  font-size: 13px; font-weight: 700; color: var(--text);
}
.dp-flair-opt i { font-size: 18px; color: var(--bt); }
.dp-flair-opt.on { border-color: var(--bt); background: var(--soft-strong); }
.dp-flair-opt[disabled] { opacity: 0.45; cursor: default; }

/* The emblem worn next to a name in chat. Sized off the line rather than fixed
   so it sits on the text baseline at any chat font size. */
.dp-chat-flair { font-size: 1.05em; margin-right: 3px; vertical-align: -1px; color: var(--bt); }

/* The rail is off-canvas from here down, so a toast has no rail to clear and
   goes back to the screen edge. Same breakpoint as the rail itself: two numbers
   for one thing is how they end up disagreeing. */
@media (max-width: 900px) {
  .dp-badge-toasts,
  body.dp-shell.rail-thin .dp-badge-toasts { left: 12px; right: 12px; bottom: 12px; }
  .dp-badge-toast { width: auto; padding: 11px 12px 11px 12px; gap: 10px; }
  .dp-badge-toast-medal { width: 40px; height: 40px; }
  .dp-badge-toast-medal i { font-size: 21px; }
  .dp-badge-toast-name { font-size: 14.5px; }
}

@media (max-width: 620px) {
  .dp-badge-grid { grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); }
  .dp-badge-hero-flair { margin-left: 0; width: 100%; }
  /* Tighter, and the count sits beside the hexagon instead of under it. */
  .dp-badge-hero { padding: 12px 14px; gap: 12px; border-radius: var(--radius); }
  .dp-badge-hero-n { font-size: 22px; }
  .dp-badge-hero-l { font-size: 11px; }
}

/* Motion is a celebration, not a requirement. */
@media (prefers-reduced-motion: reduce) {
  .dp-badge-toast,
  .dp-badge-pop-medal { animation: none; }
  .dp-badge-pop-card { transition: none; transform: none; }
}

/* --- the level hexagon --------------------------------------------------------
   A player's picture, clipped to a hexagon, with a ring that fills as they work
   through their level and a chip carrying the number. Built by
   DPShell.hexAvatar so every surface draws the same object; everything here is
   sized off --hex-size, so one component covers 22px in chat and 96px on the
   account page without a second set of rules.

   The five rank bands supply --hex-from / --hex-to. They stopped being names
   the day levels landed and became exactly this: which two colours a player
   wears, bronze through diamond. */
.dp-hex {
  position: relative; flex: 0 0 auto;
  display: inline-block; vertical-align: middle;
  width: var(--hex-size); height: var(--hex-size);
  line-height: 0;
}
/* Same polygon as the SVG path, in percentages. Two descriptions of one shape
   is a risk, but the alternative is an SVG <clipPath> per instance, and this
   one only has to agree at the six corners. */
.dp-hex-img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  background: var(--raised);
}
.dp-hex-blank {
  display: flex; align-items: center; justify-content: center;
  font-size: calc(var(--hex-size) * 0.4); font-weight: 800; line-height: 1;
  color: var(--text-faint);
}
/* Sits over the picture, slightly larger than it, so the ring reads as a frame
   around the photo rather than a line drawn on top of it. */
.dp-hex-ring {
  position: absolute; inset: calc(var(--hex-size) * -0.06);
  width: calc(var(--hex-size) * 1.12); height: calc(var(--hex-size) * 1.12);
  overflow: visible; pointer-events: none;
}
.dp-hex-track,
.dp-hex-arc {
  fill: none;
  stroke-width: 7;
  stroke-linecap: round; stroke-linejoin: round;
}
/* The empty part of the ring. Visible enough to read as a track waiting to be
   filled - at --soft-strong it disappeared against the card behind it, and a
   player at 10% looked like one with no ring at all rather than one who had
   just started. */
.dp-hex-track { stroke: rgba(255, 255, 255, 0.13); }
:root[data-theme="light"] .dp-hex-track,
body.light .dp-hex-track { stroke: rgba(0, 0, 0, 0.13); }
/* The dash is set inline as "<progress> 100" against pathLength=100, so this
   only has to say how it moves. */
.dp-hex-arc { transition: stroke-dasharray 0.5s ease; }
/* The level. Its own hexagon, bottom-right, wearing the band's gradient - the
   same two colours as the ring, so the number and the frame are obviously one
   thing. */
.dp-hex-lv {
  position: absolute; right: calc(var(--hex-size) * -0.1); bottom: calc(var(--hex-size) * -0.04);
  min-width: calc(var(--hex-size) * 0.42); height: calc(var(--hex-size) * 0.42);
  padding: 0 calc(var(--hex-size) * 0.06);
  display: flex; align-items: center; justify-content: center;
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  background: linear-gradient(150deg, var(--hex-from), var(--hex-to));
  color: #10131a; font-weight: 900; line-height: 1;
  font-size: calc(var(--hex-size) * 0.26);
  font-variant-numeric: tabular-nums;
}
/* Room for the chip and the ring, which both sit outside the picture. Without
   this the chip is clipped by whatever the avatar is packed into. */
.dp-hex:not(.nochip) { margin-right: calc(var(--hex-size) * 0.08); }

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

/* --- the XP strip above a game -------------------------------------------------
   Your face, your level, and a bar that moves while you play. Deliberately
   short: it sits between the top bar and the game on a phone, where the whole
   point is that all three fit at once, so every pixel it takes is one the game
   does not get. */
.dp-xp {
  position: relative;
  display: flex; align-items: center; gap: 14px;
  padding: 11px 16px; margin-bottom: 14px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.dp-xp[hidden] { display: none; }
/* The strip when nobody is logged in. Same box, same place - a guest should
   see the site's furniture, not a hole where it goes - but it carries a notice
   instead of a level, and it is tinted so it does not read as a bar that has
   simply not filled up yet.

   An amber of its own rather than the brand accent: this is the one row on the
   page telling somebody they are about to spend an evening earning nothing, and
   the accent is what every other piece of chrome here already wears. The theme
   has no warning token - --danger is the red a bust wears, which is too loud
   for a notice nobody needs to act on immediately - so the value is named once
   here and used from the variable below. */
.dp-xp.guest {
  --guest-amber: #F0B429;
  background: color-mix(in srgb, var(--guest-amber) 9%, var(--surface));
  border-color: color-mix(in srgb, var(--guest-amber) 38%, var(--line));
}
.dp-xp-guest { display: flex; align-items: center; gap: 13px; width: 100%; min-width: 0; }
.dp-xp-guest > i { flex: none; font-size: 26px; color: var(--guest-amber); }
.dp-xp-guest-t { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dp-xp-guest-t b { font-size: 13.5px; font-weight: 800; color: var(--text); }
.dp-xp-guest-t span { font-size: 12px; line-height: 1.45; color: var(--text-muted); }
.dp-xp-guest-go { flex: none; padding: 9px 16px; font-size: 13px; }
/* On a phone it stacks and the button goes full width: three columns at 360px
   leaves the sentence in a two-word ribbon nobody reads. */
@media (max-width: 620px) {
  .dp-xp-guest { flex-wrap: wrap; gap: 10px; }
  .dp-xp-guest > i { font-size: 22px; }
  .dp-xp-guest-t { flex: 1 1 60%; }
  .dp-xp-guest-go { width: 100%; justify-content: center; }
}
.dp-xp-face { flex: none; line-height: 0; }
.dp-xp-main { flex: 1 1 auto; min-width: 0; }
.dp-xp-top { display: flex; align-items: baseline; gap: 9px; }
.dp-xp-lv { font-size: 15.5px; font-weight: 900; color: var(--text); flex: none; }
/* The rank name, in the rank's own colour, between the level and the numbers.
   It is the one thing the strip has room for on a desktop and not on a phone. */
.dp-xp-rank {
  flex: 1 1 auto; min-width: 0;
  color: var(--lv-from, var(--primary));
  font-size: 11px; font-weight: 800; letter-spacing: 0.7px; text-transform: uppercase;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dp-xp-rank:empty { display: none; }
.dp-xp-amt {
  flex: none; margin-left: auto;
  font-size: 12px; color: var(--text-faint); font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Thick enough to be a bar rather than a rule, and NOT clipping its own
   contents - the +XP tag lives in here so it can be positioned against the
   bar's own width, and overflow:hidden would eat it. The fill rounds itself
   instead. */
.dp-xp-track {
  position: relative;
  height: 12px; margin-top: 9px; border-radius: 6px;
  background: var(--raised);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35);
}
.dp-xp-fill {
  display: block; height: 100%; width: 0; border-radius: 6px;
  /* The level's own two colours, the same pair the hexagon ring beside it is
     drawn in. Set from JS on .dp-xp; the fallback is for the split second
     before the first /me/progress lands. */
  background: linear-gradient(90deg, var(--lv-from, var(--primary)), var(--lv-to, var(--primary)));
  box-shadow: 0 0 10px -2px var(--lv-to, var(--primary));
  transition: width 0.45s cubic-bezier(0.22, 1, 0.36, 1), filter 0.18s ease;
}
/* The brighten on a tick. A single XP is half a pixel of width on a 200-XP
   level - without this the bar has visibly not moved. */
.dp-xp.tick .dp-xp-fill { filter: brightness(1.45) saturate(1.15); }

/* The +XP. A chip that pops out of the bar at the head of the fill - JS puts
   it at whatever percent the fill had reached before this round moved it, so
   it lands where the bar was and the bar grows out from under it.
   
   It sits ON the bar rather than above it. Above was the obvious place until
   the fill gets near the end of a level, which is exactly where the bar spends
   the rounds anybody is watching it - and there it lands squarely on top of
   the "199 / 200 XP". Centred on a 12px track it overhangs by four pixels
   either side and can collide with nothing.
   
   It also does not travel. A chip that pops and holds reads as the bar having
   emitted it; the same chip drifting upward reads as a notification, and this
   fires on every single round. */
.dp-xp-float {
  position: absolute; top: 50%;
  padding: 2px 8px; border-radius: var(--radius-pill);
  /* Dark chip, level-coloured text - NOT a level-coloured chip. It sits on the
     fill, so a chip painted in the fill's own colour is invisible: the top rank
     bands are near-white and it disappeared into them completely. Dark on the
     bright fill, and the coloured text and ring against the empty track when
     the bar is early in a level - it reads either way round. */
  background: var(--bg); color: var(--lv-from, var(--primary));
  border: 1px solid var(--lv-from, var(--primary));
  font-size: 11.5px; font-weight: 900; line-height: 1.3; white-space: nowrap;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 2px 12px -3px #000, 0 0 0 3px var(--bg);
  pointer-events: none;
  animation: dpXpFloat 1.2s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes dpXpFloat {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.6); }
  16%  { opacity: 1; transform: translate(-50%, -50%) scale(1.12); }
  28%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  70%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.06); }
}

/* Level up borrows the badge card wholesale - the same moment deserves the same
   object - and swaps the badge's medal for the level's own icon.

   Which is a hexagon filled with the rank's two colours and a dark number in
   it: the same clip-path, the same 150deg gradient and the same #10131a text
   as the chip in chat and the rows on the ladder, just large enough to be the
   subject of a card. A player who levels up mid-round is shown the exact object
   that is about to turn up next to their name. */
.dp-levelup-hex {
  position: relative;
  margin: 18px auto 16px;
  width: 118px; height: 130px;
  display: flex; align-items: center; justify-content: center;
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  background: linear-gradient(150deg, var(--lv-from), var(--lv-to));
  animation: dpBadgeMedal 0.6s cubic-bezier(0.22, 1.4, 0.36, 1);
}
/* The size comes from shell.js, by digit count - four digits is level 1000,
   which is a long way off but arrives, and a hexagon is at its narrowest
   exactly where the text sits. */
.dp-levelup-hex > span {
  font-weight: 900; line-height: 1;
  color: #10131a; font-variant-numeric: tabular-nums;
  letter-spacing: -1px;
}
/* The glow behind it. A separate element is not needed - the hexagon is
   clipped, so a box-shadow on it would be clipped away too; this paints the
   light on the card underneath instead. */
.dp-levelup .dp-badge-pop-card::before {
  content: ''; position: absolute; left: 50%; top: 78px;
  width: 200px; height: 200px; margin: -100px 0 0 -100px;
  border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--lv-from) 30%, transparent) 0%, transparent 66%);
}
/* The rank name reads in the rank's colour, so the word and the hexagon above
   it are visibly the same fact. */
.dp-levelup-band { color: var(--lv-from); font-weight: 800; }

@media (max-width: 620px) {
  /* Shorter on a phone, where this competes with the game for the fold: the
     rank name goes, the hexagon comes down to 34px (set in shell.js, at the
     same breakpoint), and everything tightens by a couple of pixels. */
  .dp-xp { padding: 7px 11px; gap: 10px; margin-bottom: 10px; }
  .dp-xp-lv { font-size: 12.5px; }
  .dp-xp-rank { display: none; }
  .dp-xp-amt { font-size: 10.5px; }
  .dp-xp-track { height: 9px; margin-top: 6px; border-radius: 5px; }
  .dp-xp-fill { border-radius: 5px; }
  .dp-xp-float { font-size: 10px; padding: 2px 7px; }
}

/* --- "we filled this in for you" -----------------------------------------------
   Sits directly under the XP strip when a game is opened from a challenge card
   or a pattern badge, and says what was set and why. It exists because arriving
   at a board with six tiles already marked, and no explanation, reads as a bug
   rather than a favour.

   Tinted rather than plain surface: it is a message about this visit, not
   another permanent shelf like the strip above it, and it can be closed. */
.dp-setup {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 12px 9px 11px; margin-bottom: 14px;
  border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--primary) 36%, var(--line));
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--primary) 13%, var(--surface)), var(--surface) 62%);
  animation: dpSetupIn 0.32s cubic-bezier(0.22, 1.2, 0.36, 1);
}
@keyframes dpSetupIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .dp-setup { animation: none; } }

.dp-setup-ico {
  flex: none; width: 30px; height: 30px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; color: var(--primary);
  background: color-mix(in srgb, var(--primary) 20%, transparent);
}
/* Both lines truncate rather than wrap. A badge name and a list of six numbers
   are each one line long or they are not worth the height - and a banner that
   grows to three lines on a phone is pushing the board off the fold, which is
   the one thing the game page will not spare. */
.dp-setup-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.dp-setup-txt b,
.dp-setup-txt span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dp-setup-txt b    { font-size: 13.5px; font-weight: 800; color: var(--text); }
.dp-setup-txt span { font-size: 11.5px; color: var(--text-faint); }

/* Hidden until the board drifts off the shape, so on arrival there is exactly
   one button here and it closes the thing. */
.dp-setup-redo {
  flex: none; display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 11px; border-radius: 999px; cursor: pointer;
  font-size: 12px; font-weight: 800; font-family: inherit;
  color: var(--primary); background: color-mix(in srgb, var(--primary) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--primary) 42%, transparent);
}
.dp-setup-redo:hover { background: color-mix(in srgb, var(--primary) 26%, transparent); }
.dp-setup-redo[hidden] { display: none; }

.dp-setup-x {
  flex: none; width: 26px; height: 26px; border-radius: 8px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; color: var(--text-faint);
  background: none; border: 1px solid transparent;
}
.dp-setup-x:hover { color: var(--text); border-color: var(--line); background: var(--raised); }

@media (max-width: 620px) {
  .dp-setup { padding: 7px 9px; gap: 9px; margin-bottom: 10px; }
  .dp-setup-ico { width: 26px; height: 26px; font-size: 14px; border-radius: 8px; }
  .dp-setup-txt b { font-size: 12.5px; }
  .dp-setup-txt span { font-size: 10.5px; }
  /* The word goes, the arrow stays. At this width the label was taking the room
     the numbers underneath it needed. */
  .dp-setup-redo { padding: 6px 8px; font-size: 0; gap: 0; }
  .dp-setup-redo i { font-size: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .dp-xp-fill { transition: none; }
  /* Still shown, and still for as long - it is the only feedback that a round
     paid anything. It just appears and goes instead of popping. */
  .dp-xp-float { animation: dpXpFade 1.2s linear forwards;
    transform: translate(-50%, -50%); }
  @keyframes dpXpFade { 0%, 78% { opacity: 1; } 100% { opacity: 0; } }
}

/* ---- autobet ---------------------------------------------------------------
   The shared autoplay block, built by DPShell.autobet into whatever host a game
   hands it. Limbo and worms style their own copies of this in their own pages;
   these are the same shapes, named so the two cannot collide while both exist. */
/* The slip separates its groups by 18px - see .dp-slip-label and
   .dp-slip-tools, which both use it. Without this the Manual / Auto pair sat
   flush against whatever row was above it and the two read as one crowded
   block of buttons. */
.dp-ab { margin-top: 18px; }
.dp-ab-seg { margin-bottom: 12px; }
.dp-ab-body[hidden] { display: none; }
.dp-ab-lab {
  display: block; margin-bottom: 6px;
  font-size: 11px; font-weight: 800; letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--text-faint);
}
/* The field takes what is left after the quick buttons, which are sized by
   their own digits - "100" is wider than "10" and forcing them equal leaves
   air around the short ones. */
.dp-ab-count { display: flex; gap: 6px; align-items: stretch; }
.dp-ab-count .dp-bet-input { flex: 1 1 auto; min-width: 0; }
.dp-ab-count .dp-bet-chip { flex: 0 0 auto; }
.dp-ab-cap {
  margin-top: 6px; font-size: 11px; color: var(--text-faint);
}

.dp-ab-more {
  margin-top: 12px; padding: 0; border: 0; background: none; cursor: pointer;
  display: flex; align-items: center; gap: 5px;
  font-size: 12px; font-weight: 700; color: var(--text-muted);
  font-family: inherit;
}
.dp-ab-more:hover { color: var(--text); }
.dp-ab-more i { font-size: 13px; }
.dp-ab-adv { margin-top: 10px; }
.dp-ab-adv[hidden] { display: none; }
.dp-ab-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; }
.dp-ab-field { position: relative; min-width: 0; }
.dp-ab-field .dp-bet-input { width: 100%; min-width: 0; }
.dp-ab-field label {
  display: block; margin-bottom: 4px;
  font-size: 10.5px; font-weight: 700; color: var(--text-faint);
}
/* Inside the field, against the right edge, vertically centred on the input
   rather than on the field - the label above it is part of the field's height. */
.dp-ab-unit {
  position: absolute; right: 10px; bottom: 0; height: 38px;
  display: flex; align-items: center;
  font-size: 12px; font-weight: 700; color: var(--text-faint); pointer-events: none;
}
.dp-ab-hint { font-size: 11px; line-height: 1.5; color: var(--text-faint); }

/* How the run is going. Three numbers, fixed columns so they do not shuffle
   sideways as the digits change under them. */
.dp-ab-run {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line);
}
.dp-ab-run[hidden] { display: none; }
.dp-ab-run > div { min-width: 0; }
.dp-ab-run-l {
  font-size: 10px; font-weight: 800; letter-spacing: 0.8px; text-transform: uppercase;
  color: var(--text-faint);
}
.dp-ab-run-v {
  margin-top: 2px; font-size: 15px; font-weight: 900; color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dp-ab-run-v.up { color: var(--success); }
.dp-ab-run-v.down { color: var(--danger); }
