/* =============================================================================
   orbpoll.css — 설문·게임(Poll & Game) 전용 스타일. 앱(index.html — orbrium.css 바로 다음)과 프로젝트 전용 셸
   페이지(GET /api/v1/tool/polls|games/{id}/show — pui.css·bootstrap-icons 다음, preset/tool/service/orbpoll_show.py
   STYLESHEETS)가 같은 파일을 싣는다. orbrium.css 에는 티켓 코드 팔레트 절에 덧붙인 .orb-poll-doc 만 남는다(앱 전용 지면).
   색은 bs·pui 토큰만, 길이는 rem — 루트 zoom 때문에 vh/vw·뷰포트 폭 @media 는 쓰지 않는다(폭 질의가 필요하면 @container).
   @media 는 파일 끝 prefers-reduced-motion 한 블록뿐(사용자 설정 질의).
   ============================================================================= */

/* -----------------------------------------------------------------------------
   Poll & Game — 홈 피드의 설문·게임 띠와 만들기 버튼 (orbpoll.js OrbPoll)
   -----------------------------------------------------------------------------
   자리는 호스트 골격(home.js _homeBoardHtml)이 .feed-page 안에 둔다: [검색] /
   [컴포저 | 만들기 버튼](.home-feed-composer-row) / [타일 띠]([data-poll-strip]) /
   [목록]. */

/* 컴포저 줄 — 벤더 컴포저 버튼(.feed-composer-open, 자기 안에 mb-3)이 왼쪽을 채우고
   만들기 버튼이 오른쪽에 선다. 간격은 피드 카드 간격(mb-3 = 1rem)과 같은 --home-gap.
   세로 정렬은 위 맞춤 — 버튼이 제 높이를 갖고 있어 stretch가 필요 없고, 컴포저가
   비동기로 그려지는 첫 프레임에 빈 칸이 버튼을 늘리지도 않는다. */
.home-feed-composer-row {
    display: flex;
    align-items: flex-start;
    gap: var(--home-gap, 1rem);
}
.home-feed-composer-row > [data-feed-composer] {
    flex: 1 1 auto;
    min-width: 0;
}
.home-feed-composer-row > [data-poll-create] {
    flex: 0 0 auto;
}

/* 만들기 버튼 — 컴포저 카드와 같은 높이의 정사각. 컴포저 높이 = 아바타 3rem(pui.feed.js
   _avatar 'lg') + .card-body 상하 패딩(--bs-card-spacer-y) + 테두리 둘(--bs-card-border-width)
   이고 두 변수는 .card 자신에 선언돼 있어(pui.css) 이 버튼(.card)의 제 값으로 풀린다.
   아래 여백 1rem은 벤더 컴포저의 mb-3와 같은 값 — 두 카드의 아래변이 맞는다. 쉼 상태
   그림자 없음·hover 들림은 컴포저(.feed-composer-open)와 같은 격. 아이콘 색은 벤더
   .text-primary와 같은 짝이다 — 테마 런타임(pui.theme.js)이 --bs-primary를 다크에서도
   원색으로 두어 어두운 카드에 묻히므로, 다크는 벤더 다크 규칙(pui.css「Dark mode semantic
   text colors」)의 primary 값 rgba(--pui-info, 0.9)로 바꾼다. */
.orb-poll-create {
    --orb-poll-create-size: calc(3rem + 2 * var(--bs-card-spacer-y, 1rem) + 2 * var(--bs-card-border-width, 1px));
    width: var(--orb-poll-create-size);
    height: var(--orb-poll-create-size);
    margin-bottom: 1rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.25rem;
    padding: 0.25rem;
    cursor: pointer;
    color: var(--bs-body-color);
    box-shadow: none;
}
.orb-poll-create > i {
    font-size: 1.5rem;
    line-height: 1;
    color: var(--bs-primary);
}
[data-bs-theme="dark"] .orb-poll-create > i {
    color: rgba(var(--pui-info), 0.9);
}
.orb-poll-create-label {
    display: flex;
    flex-direction: column;
    font-size: 0.6875rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
}

/* 종류 틴트 — 칩·막대·픽커 아이콘이 한 벌의 변수를 본다(설문=primary, 룰렛=warning,
   사다리=success). 글자색은 bs의 text-emphasis(밝은·어두운 모드에서 각각 읽히는 값). */
.orb-poll-tone-poll {
    --orb-poll-tone-rgb: var(--bs-primary-rgb);
    --orb-poll-tone-text: var(--bs-primary-text-emphasis);
}
.orb-poll-tone-roulette {
    --orb-poll-tone-rgb: var(--bs-warning-rgb);
    --orb-poll-tone-text: var(--bs-warning-text-emphasis);
}
.orb-poll-tone-ladder {
    --orb-poll-tone-rgb: var(--bs-success-rgb);
    --orb-poll-tone-text: var(--bs-success-text-emphasis);
}
/* 틴트 알약 바탕 — 종류 칩·문항 번호(빌더·응답자 화면)·게임 합계 배지·결과 칩·리포트 개수 배지가 같은 바탕·글자색. 꼴은 각자 규칙. */
.orb-poll-kind,
.orb-poll-q-no,
.orb-poll-rq-no,
.orb-poll-game-badge,
.orb-poll-result-slot,
.orb-poll-rp-badge {
    background: rgba(var(--orb-poll-tone-rgb), 0.14);
    color: var(--orb-poll-tone-text);
}

/* 띠 — 가로 스크롤 스냅 행. 자리([data-poll-strip])는 0건이면 hidden이라 높이·여백이
   없고, 있으면 아래 여백 1rem(피드 카드 간격)으로 목록과 떨어진다. 스크롤바는 숨긴다
   (scrollbar-width none + ::-webkit-scrollbar display none — 휠·터치·키보드 스크롤은
   그대로, overflow-x:hidden은 포커스 갇힘 함정이라 쓰지 않는다). hover 들림(0.1875rem)과
   그림자가 overflow 상자에 잘리지 않게 안쪽 여백 0.75rem을 두고 같은 값의 음수 margin으로
   자리를 되돌린다(스냅 기준도 그만큼 안쪽). 양끝 페이드는 마스크(배경 무관) — 색 채널은
   무시되고 알파만 쓰이므로 여기 검정은 테마색이 아니다. 더 볼 것이 있는 쪽만 바래게
   JS(_updateFade)가 data-fade-start/end를 세운다 — 왼쪽 끝에서 첫 타일이 바래지 않는다. */
[data-poll-strip] {
    margin-bottom: 1rem;
}
.orb-poll-strip {
    --orb-poll-fade-l: 0rem;
    --orb-poll-fade-r: 0rem;
    display: flex;
    gap: 0.75rem;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x proximity;
    scroll-padding-inline: 0.75rem;
    padding: 0.75rem;
    margin: -0.75rem;
    scrollbar-width: none;
    -webkit-mask-image: linear-gradient(to right,
        transparent 0, #000 var(--orb-poll-fade-l),
        #000 calc(100% - var(--orb-poll-fade-r)), transparent 100%);
    mask-image: linear-gradient(to right,
        transparent 0, #000 var(--orb-poll-fade-l),
        #000 calc(100% - var(--orb-poll-fade-r)), transparent 100%);
}
.orb-poll-strip::-webkit-scrollbar { display: none; }
.orb-poll-strip[data-fade-start] { --orb-poll-fade-l: 2.5rem; }
.orb-poll-strip[data-fade-end]   { --orb-poll-fade-r: 2.5rem; }

/* 타일 — 정사각 9rem 카드. 위에서부터 종류 칩 / 제목 2줄 / (남는 자리) / 하단 행(상태
   알약 + 참여 수), 바닥에 남은 시간 막대. 종료는 채도를 낮춘다. 들림은 공용 .hover-lift. */
.orb-poll-tile {
    flex: 0 0 9rem;
    width: 9rem;
    aspect-ratio: 1 / 1;
    scroll-snap-align: start;
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    padding: 0.625rem;
    overflow: hidden;
    cursor: pointer;
    text-align: start;
    color: var(--bs-body-color);
}
.orb-poll-tile.is-ended {
    filter: saturate(0.3);
    opacity: 0.8;
}
.orb-poll-tile:focus-visible {
    outline: 0.125rem solid rgb(var(--bs-primary-rgb));
    outline-offset: 0.125rem;
}
.orb-poll-kind {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    max-width: 100%;
    padding: 0.125rem 0.5rem;
    border-radius: var(--bs-border-radius-pill);
    font-size: 0.6875rem;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
}
.orb-poll-kind > i { font-size: 0.75rem; }
.orb-poll-title {
    font-size: 0.8125rem;
    font-weight: 600;
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}
.orb-poll-foot {
    margin-top: auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.25rem;
    min-width: 0;
    font-size: 0.6875rem;
}
.orb-poll-status {
    padding: 0.0625rem 0.4375rem;
    border-radius: var(--bs-border-radius-pill);
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.orb-poll-status.is-open {
    background: rgba(var(--bs-success-rgb), 0.14);
    color: var(--bs-success-text-emphasis);
}
.orb-poll-count {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    flex-shrink: 0;
    color: var(--bs-secondary-color);
}
/* 아바타(OrbPollView.avatarHtml — 앱·셸 공용) — 피드 .feed-avatar(색상각 --feed-avatar-hue·사진 층 .feed-avatar-photo·원 자르기 — pui.css
   Feed 절)에 꼴과 크기 사다리만 얹는다. sm·lg 는 피드 사다리(pui.feed.js _avatar)와 같은 값, xs 는 타일 얼굴. */
.orb-poll-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    border-radius: 50%;
    line-height: 1;
}
.orb-poll-avatar-xs { width: 1.5rem; height: 1.5rem; font-size: 0.625rem; }
.orb-poll-avatar-sm { width: 2rem; height: 2rem; font-size: 0.75rem; }
.orb-poll-avatar-lg { width: 3rem; height: 3rem; font-size: 1rem; }
/* 당첨자 얼굴(결과 있는 게임 타일, 최대 3) — 제목과 하단 행 사이, xs 아바타(_faceHtml)가 0.5rem씩 겹치고 카드 면색 링으로 갈린다. */
.orb-poll-tile-winners {
    display: flex;
    align-items: center;
}
.orb-poll-tile-winners > .feed-avatar {
    margin-inline-start: -0.5rem;
    box-shadow: 0 0 0 0.125rem var(--bs-card-bg, var(--bs-body-bg));
}
.orb-poll-tile-winners > .feed-avatar:first-child { margin-inline-start: 0; }
.orb-poll-progress {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 0.1875rem;
    background: rgba(var(--bs-body-color-rgb), 0.08);
}
.orb-poll-progress > span {
    display: block;
    height: 100%;
    background: rgb(var(--orb-poll-tone-rgb));
    transition: width var(--pui-transition-speed) ease-out;
}

/* 로비 타일(orbpoll.js renderLobbyTiles) — 로비 공개 파일 열(index.html .lobby-files)의 14rem 그리드 선두 묶음. 그리드 규칙은
   index.html .lobby-files-grid(같은 클래스를 함께 단다 — 트랙 14rem 고정·gap 1rem), 여기는 위 띠 타일 규칙에 덧씌우는 로비
   셀렉터만(복제 없음): 띠의 9rem 고정 폭·스냅을 풀어 셀(14rem 정사각, aspect-ratio 그대로)에 맞추고 여백·글자만 키운다.
   루트가 앵커(a.card — href = 셸 URL)라 밑줄을 지운다(글자색은 .orb-poll-tile 의 body-color 가 a 보다 특이도가 높아 그대로).
   아래에 파일 그리드가 서면 1rem(그리드 gap 과 같은 리듬) 띄우고, 묶음만 서면 띄우지 않는다. */
.lobby-polls { margin-bottom: 1rem; }
.lobby-polls:last-child { margin-bottom: 0; }
.lobby-polls > .orb-poll-tile {
    flex: none;
    width: auto;
    scroll-snap-align: none;
    gap: 0.5rem;
    padding: 0.875rem;
    text-decoration: none;
}
.lobby-polls .orb-poll-title { font-size: 1rem; }
.lobby-polls .orb-poll-foot { font-size: 0.75rem; }

/* 종류 선택(FloatPage md) — 설문 / 룰렛 / 사다리 정사각 카드 셋. */
.orb-poll-picker {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 1rem;
}
.orb-poll-pick {
    aspect-ratio: 1 / 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.5rem;
    cursor: pointer;
    font-weight: 600;
    color: var(--bs-body-color);
}
.orb-poll-pick > i {
    font-size: 2.5rem;
    line-height: 1;
    color: var(--orb-poll-tone-text);
}

/* 참여 창(FloatPage lg, orbpoll.js _openPollView · _openGameView) — 루트는 세로 흐름(설문: 머리 / 설명 / 진행 막대 / 문항,
   게임: 머리 / 설명 / 무대 / 메타 / 참여자 칩 / 결과 패널 — 아래 「게임 참여 창」 절)이고 간격은 gap 하나로 맞춘다. 모달 본문
   (.float-page .modal-body, 패딩 1rem, overflow-y auto)이 통째로 스크롤한다. */
.orb-poll-view {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
/* 머리 — [아바타 lg(.orb-poll-avatar — 앱만)] [제목 + 메타 행] [관리 ⋮]. 메타 행은 칩·작성자·상태 알약·카운트다운·응답/참여 수·
   (설문) 제출함 배지가 한 줄에 서고 좁으면 줄을 바꾼다(설문·게임 같은 머리 — _headHtml). */
.orb-poll-view-head {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
}
.orb-poll-view-head-text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
}
.orb-poll-view-title {
    margin: 0;
    font-size: 1.125rem;
    font-weight: 600;
    line-height: 1.3;
    overflow-wrap: anywhere;
}
.orb-poll-view-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.375rem 0.5rem;
    font-size: 0.8125rem;
}
.orb-poll-view-author {
    max-width: 14rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--bs-secondary-color);
}
/* 카운트다운(마감까지 남은 시간)·제출함 배지 — 상태 알약과 같은 꼴, 각각 warning·info 틴트. 숫자 폭을 고정해
   초 단위 틱에서 글자가 흔들리지 않는다. */
.orb-poll-countdown,
.orb-poll-submitted {
    display: inline-flex;
    align-items: center;
    padding: 0.0625rem 0.4375rem;
    border-radius: var(--bs-border-radius-pill);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.orb-poll-countdown {
    background: rgba(var(--bs-warning-rgb), 0.16);
    color: var(--bs-warning-text-emphasis);
    font-weight: 600;
}
.orb-poll-submitted {
    background: rgba(var(--bs-info-rgb), 0.14);
    color: var(--bs-info-text-emphasis);
}
.orb-poll-view-tools {
    flex-shrink: 0;
    align-self: flex-start;
}
/* 진행 막대 「필수 문항 n/m」 — 스크롤 상자(.modal-body) 위에 붙는다(sticky). 좌우 음수 여백 = 그 상자의 패딩(1rem)이라
   끝까지 닿고, 배경은 모달 면색(.float-page .modal-content와 같은 식)이라 밑의 문항이 비치지 않는다. 전부 차면 primary. */
.orb-poll-view-progress {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: 0 -1rem;
    padding: 0.5rem 1rem;
    background: rgba(var(--bs-body-bg-rgb), var(--pui-bg-opacity, 1));
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    color: var(--bs-secondary-color);
}
.orb-poll-view-progress-bar {
    flex: 1 1 auto;
    height: 0.375rem;
    border-radius: var(--bs-border-radius-pill);
    background: rgba(var(--bs-body-color-rgb), 0.08);
    overflow: hidden;
}
.orb-poll-view-progress-bar > span {
    display: block;
    width: 0;
    height: 100%;
    border-radius: inherit;
    background: var(--bs-secondary-color);
    transition: width var(--pui-transition-speed) ease-out, background-color var(--pui-transition-speed);
}
.orb-poll-view-progress.is-complete {
    color: var(--bs-primary-text-emphasis);
}
.orb-poll-view-progress.is-complete > .orb-poll-view-progress-bar > span {
    background: rgb(var(--bs-primary-rgb));
}
/* 선검증 표식 — 어긋난 문항 카드의 테두리 + 카드 바닥 문구(_markCard가 붙이고 답을 고치면 걷는다). */
.orb-poll-rq.is-invalid {
    border-color: var(--bs-form-invalid-border-color, var(--bs-danger));
}
.orb-poll-rq-msg {
    font-size: 0.75rem;
    color: var(--bs-form-invalid-color, var(--bs-danger-text-emphasis));
}

/* --- 게임 참여 창(orbpoll.js _openGameView) — 같은 .orb-poll-view 세로 흐름: 머리 / 설명 / 무대(무대 모듈이 자기 루트를 세운다) /
   결과 전 메타 한 줄 / 참여자 칩 줄 / 결과 패널. 틴트는 루트의 .orb-poll-tone-{kind}. */
/* 「수동 추첨」 알약 — 카운트다운 자리의 중립 꼴(마감 없음 = 돌리기만). */
.orb-poll-countdown.is-manual {
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
    font-weight: 500;
}
.orb-poll-view-stage {
    min-height: 4rem;
}
.orb-poll-game-meta {
    margin-top: -0.5rem;
    font-size: 0.75rem;
    text-align: center;
    color: var(--bs-secondary-color);
    font-variant-numeric: tabular-nums;
}
/* 참여자 칩 줄 — 라벨 + 알약 칩(아바타 sm + 이름 말줄임). 내 칩은 틴트 테두리·굵게. 「+N」은 같은 알약 꼴의 버튼. 알약 꼴은 룰렛
   항목별 요약 칩(.orb-poll-summary-chip)과 같은 규칙 — 폭·안쪽 여백·글자색만 각자. */
.orb-poll-entrants {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.375rem;
}
.orb-poll-entrants-label {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    margin-inline-end: 0.25rem;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--bs-secondary-color);
}
.orb-poll-entrant,
.orb-poll-summary-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-pill);
    background: var(--bs-body-bg);
    font-size: 0.8125rem;
    line-height: 1.3;
}
.orb-poll-entrant {
    max-width: 12rem;
    padding: 0.125rem 0.625rem 0.125rem 0.125rem;
    color: var(--bs-body-color);
}
.orb-poll-entrant-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.orb-poll-entrant.is-me {
    border-color: rgb(var(--orb-poll-tone-rgb));
    background: rgba(var(--orb-poll-tone-rgb), 0.1);
    font-weight: 600;
}
.orb-poll-entrant-more {
    min-height: 2.25rem;
    padding: 0.125rem 0.625rem;
    cursor: pointer;
    font-weight: 600;
    color: var(--bs-secondary-color);
}
.orb-poll-entrant-more:hover,
.orb-poll-entrant-more:focus-visible {
    border-color: rgb(var(--orb-poll-tone-rgb));
    color: var(--orb-poll-tone-text);
}
/* 결과 패널 — 처음 나타날 때 아래에서 떠오른다(감소 동작은 즉시 — 파일 끝 @media). 제목 줄 + 사다리 행 격자 / 룰렛 항목별 요약 칩 줄 + 사람별
   행 격자(사다리 행과 같은 마크업). 룰렛 색은 바퀴와 같은 색상각(--hue)·휠 토큰·색상각 색(아래 게임 무대 절 — 이 상자에도 정의)으로만. */
.orb-poll-result {
    animation: orb-poll-result-in 400ms ease-out;
}
@keyframes orb-poll-result-in {
    from { opacity: 0; transform: translateY(0.5rem); }
    to   { opacity: 1; transform: none; }
}
.orb-poll-result-title {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    margin: 0 0 0.5rem;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--orb-poll-tone-text);
}
.orb-poll-result-who {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    min-width: 0;
    flex: 1 1 auto;
}
.orb-poll-result-who > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* 룰렛 항목별 요약 — 범례와 같은 순서·바퀴와 같은 색의 알약 칩(색 점 · 라벨 말줄임 · n명). 0명 칩은 흐리게(.is-zero), 꽝 칩(항목·자동)은 중립 점(.is-blank). */
.orb-poll-roulette-summary {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
    margin: 0 0 0.75rem;
    padding: 0;
    list-style: none;
}
.orb-poll-summary-chip {
    max-width: 14rem;
    padding: 0.125rem 0.625rem;
}
.orb-poll-summary-chip.is-zero { opacity: 0.45; }
/* 색 점 — 요약 칩·범례 행(아래 게임 무대 절)이 같은 꼴, 크기만 각자. 꽝 칩·꽝 행은 중립 면. */
.orb-poll-summary-dot,
.orb-poll-legend-dot {
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--orb-poll-hue-fill);
    box-shadow: inset 0 0 0 1px rgba(var(--bs-body-color-rgb), 0.18);
}
.orb-poll-summary-dot {
    width: 0.625rem;
    height: 0.625rem;
}
.orb-poll-summary-chip.is-blank .orb-poll-summary-dot,
.orb-poll-legend-item.is-blank .orb-poll-legend-dot { background: var(--bs-secondary-bg); }
.orb-poll-summary-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.orb-poll-summary-chip.is-blank .orb-poll-summary-label { color: var(--bs-secondary-color); }
.orb-poll-summary-count {
    flex-shrink: 0;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
/* 결과 행 — 사다리(열 순)·룰렛(참여 순) 공용 「아바타 이름 → 결과 칩」. 꽝은 회색, 룰렛 당첨 칩은 그 조각 색(.has-hue), 결과 없는 옛 참여는
   「—」 중립(.is-none), 내 행은 틴트 바탕(.is-me — 바깥 그림자로 넓혀 격자 정렬은 그대로). */
.orb-poll-ladder-result,
.orb-poll-roulette-result {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    gap: 0.375rem 1rem;
}
.orb-poll-result-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
    font-size: 0.875rem;
}
.orb-poll-result-row > i {
    flex-shrink: 0;
    color: var(--bs-secondary-color);
}
.orb-poll-result-slot {
    flex-shrink: 0;
    max-width: 50%;
    padding: 0.125rem 0.625rem;
    border-radius: var(--bs-border-radius-pill);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.orb-poll-result-slot.has-hue {
    background: var(--orb-poll-hue-fill);
    color: var(--orb-poll-hue-ink);
}
.orb-poll-result-row.is-blank { color: var(--bs-secondary-color); }
.orb-poll-result-row.is-blank .orb-poll-result-slot {
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
    font-weight: 500;
}
.orb-poll-result-row.is-none { color: var(--bs-secondary-color); }
.orb-poll-result-row.is-none .orb-poll-result-slot {
    background: none;
    color: var(--bs-secondary-color);
    font-weight: 500;
}
.orb-poll-result-row.is-me {
    border-radius: var(--bs-border-radius);
    background: rgba(var(--orb-poll-tone-rgb), 0.1);
    box-shadow: 0 0 0 0.25rem rgba(var(--orb-poll-tone-rgb), 0.1);
}
.orb-poll-result-row.is-me .orb-poll-result-who { font-weight: 600; }

/* --- 설문·게임 빌더(FloatPage xl/lg, orbpoll.js _openPollBuilder/_openGameBuilder) — 좌 고정폭 입력 열 +
   우 유동 열(설문 = 문항 카드 스택, 게임 = 설정 카드 하나). 모달 본문(.float-page .modal-body, overflow-y auto)이
   통째로 스크롤한다. 폭은 rem(루트 zoom). */
.orb-poll-builder {
    --orb-poll-side: 24rem;
    display: flex;
    align-items: flex-start;
    gap: 1.5rem;
}
.orb-poll-side {
    flex: 0 0 var(--orb-poll-side);
    width: var(--orb-poll-side);
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.orb-poll-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
/* 세그먼트 묶음(마감 3택·문항 유형 3택) 위 작은 제목 — 플로팅 라벨이 없는 컨트롤의 이름표. */
.orb-poll-field-label {
    margin-bottom: 0.375rem;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--bs-secondary-color);
}
/* 세그먼트 라디오(마감 3택·문항 유형 3택·게임 종류 2택) — 균등 분할 + 넘침 말줄임(cloud.js 색상 세그먼트와
   같은 규칙, 인라인 대신 클래스). */
.orb-poll-close .btn-group > .btn,
.orb-poll-q-type > .btn,
.orb-poll-game-kind > .btn {
    flex: 1 1 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.orb-poll-close-rel {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.75rem;
}
.orb-poll-close-rel > .form-floating {
    flex: 1 1 0;
    min-width: 0;
}
.orb-poll-close-abs {
    margin-top: 0.75rem;
}

/* 설명 에디터 — 타이포·글줄 경계선은 호스트의 announce-doc 차용(메모·티켓과 같은 차용, 영구인 이유와 끊어지는
   경로·싱크 뒤 grep 방어는 orbrium.css 티켓 생성 폼 절). 셸(.announce-form-shell)은 입히지 않는다 — 모달 폭을 1190px로
   못 박는다. 셸이 공급하던 --announce-column을 여기서 준다: 에디터 상자(= 좌측 열 폭) 안에서 글이 실제로 흐르는
   폭 = 열 폭 − .pui-md 테두리 1px×2 − 스크롤바 예약 0.5rem − 글 여백 1.5rem×2. 그러면 벤더 기하 규칙
   (.announce-doc .milkdown .ProseMirror max-width = column + 3rem)이 상자 안쪽에 정확히 맞고 경계선 한 쌍이
   글 여백 안에 선다. 코드 팔레트는 셸 축이라 티켓 절(.ticket-doc, .ticket-reply-doc, .orb-poll-doc)이 함께
   공급한다 — 값은 한 자리에만 둔다. 높이는 PUIMarkdown이 인라인(16rem)으로 잡는다. */
.orb-poll-doc {
    --announce-column: calc(var(--orb-poll-side) - 3.5rem - 2 * var(--bs-border-width));
}

/* 잠금 배지(수정 ∧ 응답·참여 있음 — 설문 스택 / 룰렛 카드 위 오른쪽 하나). 구조 컨트롤은 disabled로 식별된다(설명문 없음).
   룰렛 합계 배지(「합계 n%」·「꽝 n%」, 항목 이름표 줄 오른쪽)도 같은 알약 — 색만 종류 틴트, 꽝은 회색, 합 > 100은 위험색
   (is-over — 선검증 _validator가 본문의 .is-invalid를 일괄로 지우므로 그 클래스와 갈라 둔다). */
.orb-poll-lock {
    display: flex;
    justify-content: flex-end;
}
.orb-poll-lock-badge,
.orb-poll-game-badge {
    display: inline-flex;
    align-items: center;
    padding: 0.25rem 0.625rem;
    border-radius: var(--bs-border-radius-pill);
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
}
.orb-poll-lock-badge {
    background: rgba(var(--bs-warning-rgb), 0.16);
    color: var(--bs-warning-text-emphasis);
}
.orb-poll-game-badge.is-blank {
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
}
.orb-poll-game-badge.is-over {
    background: rgba(var(--bs-danger-rgb), 0.16);
    color: var(--bs-danger-text-emphasis);
}
.orb-poll-game-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.375rem 0.5rem;
    margin-bottom: 0.375rem;
}
.orb-poll-game-head > .orb-poll-field-label {
    margin-bottom: 0;
}
.orb-poll-game-sum {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0.375rem;
}

/* 문항 카드 스택 */
.orb-poll-q-list {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.orb-poll-q-card {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 1rem;
}
.orb-poll-q-card.sortable-ghost,
.orb-poll-opt-row.sortable-ghost {
    opacity: 0.4;
}
.orb-poll-q-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.orb-poll-q-handle,
.orb-poll-opt-handle {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    cursor: grab;
    color: var(--bs-secondary-color);
}
.orb-poll-q-handle:active,
.orb-poll-opt-handle:active {
    cursor: grabbing;
}
/* 문항 번호 알약 — 빌더 카드(.orb-poll-q-no)·응답자 화면(.orb-poll-rq-no — 리포트 문항 머리도 같은 클래스)이 같은 꼴. */
.orb-poll-q-no,
.orb-poll-rq-no {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    min-width: 1.75rem;
    height: 1.75rem;
    padding: 0 0.5rem;
    border-radius: var(--bs-border-radius-pill);
    font-size: 0.8125rem;
    font-weight: 700;
}
.orb-poll-q-type {
    margin-left: auto;
    max-width: 24rem;
    flex: 1 1 auto;
}
/* 선택지 행 — [핸들][모양][input][×]. 말풍선(.invalid-feedback, 벤더가 absolute로 띄움)의 기준 상자. */
.orb-poll-opts {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
}
.orb-poll-opt-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.orb-poll-opt-row > .form-control {
    flex: 1 1 auto;
    min-width: 0;
}
.orb-poll-opt-mark {
    flex-shrink: 0;
    font-size: 0.875rem;
    color: var(--bs-secondary-color);
}
.orb-poll-opt-del {
    flex-shrink: 0;
    font-size: 0.625rem;
}
/* 룰렛 항목 행 — [핸들][라벨][(꽝) 표식][확률 %][꽝 토글][×]. 확률 칸은 고정폭 input-group(말풍선 기준 상자 — .input-group이
   position relative), 꽝 행은 라벨 칸을 흐리게 + 이름 칸 뒤 「(꽝)」 표식(늘 그려 두고 꽝 행에서만 보인다 — 토글은 행 클래스만 바꾼다).
   잠긴 목록(참여자 있음 — 순서도 잠금)은 핸들도 쉰다. */
.orb-poll-seg-weight {
    flex: 0 0 6.5rem;
    width: 6.5rem;
}
.orb-poll-seg-blank {
    flex-shrink: 0;
}
.orb-poll-seg-row.is-blank > [data-role="s-label"] {
    background-color: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
}
.orb-poll-seg-mark {
    display: none;
    flex-shrink: 0;
    font-size: 0.875rem;
    color: var(--bs-secondary-color);
    white-space: nowrap;
}
.orb-poll-seg-row.is-blank > .orb-poll-seg-mark {
    display: inline;
}
.orb-poll-opts.is-locked .orb-poll-opt-handle {
    cursor: default;
    opacity: 0.5;
}
/* 고스트 행(「+ 선택지」)·「+ 문항 추가」 — 점선 테두리의 조용한 버튼. */
.orb-poll-opt-add,
.orb-poll-q-add {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding: 0.375rem 0.75rem;
    border: var(--bs-border-width) dashed var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    background: transparent;
    color: var(--bs-secondary-color);
    font-size: 0.875rem;
    transition: color var(--pui-transition-speed), border-color var(--pui-transition-speed), background-color var(--pui-transition-speed);
}
.orb-poll-q-add {
    padding: 0.75rem;
    font-weight: 600;
}
.orb-poll-opt-add:hover:not(:disabled),
.orb-poll-q-add:hover:not(:disabled) {
    border-color: rgb(var(--orb-poll-tone-rgb));
    color: var(--orb-poll-tone-text);
    background: rgba(var(--orb-poll-tone-rgb), 0.06);
}
.orb-poll-opt-add:disabled,
.orb-poll-q-add:disabled {
    opacity: 0.5;
    cursor: default;
}
/* 바닥 줄 — 필수 스위치 + 유형별 부가(복수: 최소/최대, 주관식: 여러 줄·글자 수·작성자 공개) + 오른쪽 끝 복제·삭제. */
.orb-poll-q-foot {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
}
.orb-poll-q-foot > .form-check {
    margin-bottom: 0;
}
.orb-poll-num {
    flex: 0 0 auto;
    width: 7.5rem;
}
.orb-poll-q-tools {
    margin-left: auto;
}

/* --- 응답자 화면(renderQuestions — 빌더 미리보기 · 앱 참여 창 · 셸). 틴트는 호스트 루트의 .orb-poll-tone-{kind}(설문 = primary). */
.orb-poll-rq-list {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.orb-poll-rq {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 1rem;
    min-width: 0;
}
.orb-poll-rq-head {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    font-weight: 600;
}
.orb-poll-rq-no { align-self: center; }
.orb-poll-rq-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}
.orb-poll-rq-req {
    flex-shrink: 0;
    color: var(--bs-danger-text-emphasis);
    font-weight: 700;
}
.orb-poll-rq-opts {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
/* 큰 옵션 행 — 행 전체가 눌린다(label). 선택은 테두리·배경 틴트 + 체크 살짝 커짐. 체크 상자의 여백은 마크업 m-0(벤더 margin-top 해제),
   전환은 벤더 .form-check-input 의 transition(!important — 색·테두리·그림자)이 정한다(커짐은 즉시). */
.orb-poll-rq-opt {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: 0;
    padding: 0.625rem 0.875rem;
    border: var(--bs-border-width) solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    cursor: pointer;
    transition: border-color var(--pui-transition-speed), background-color var(--pui-transition-speed);
}
.orb-poll-rq-opt:hover {
    background: var(--bs-tertiary-bg);
}
.orb-poll-rq-opt:has(.form-check-input:checked) {
    border-color: rgb(var(--orb-poll-tone-rgb));
    background: rgba(var(--orb-poll-tone-rgb), 0.08);
}
.orb-poll-rq-opt > .form-check-input {
    cursor: pointer;
}
.orb-poll-rq-opt > .form-check-input:checked {
    transform: scale(1.15);
}
.orb-poll-rq-opt-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}
.orb-poll-rq:disabled .orb-poll-rq-opt {
    cursor: default;
    opacity: 0.7;
}
.orb-poll-rq textarea[data-role="answer"] {
    resize: none;
    overflow: hidden;
}
.orb-poll-rq-count {
    margin-top: -0.375rem;
    text-align: end;
    font-size: 0.75rem;
    color: var(--bs-secondary-color);
}

/* --- 설문 리포트(FloatPage xl, orbpoll.js _openReport) — 머리(제목 + 스탯 타일 셋) / 문항 카드 스택(막대 목록·답 카드) /
   접힘 절(응답자 표·정의에 없는 답). 모달 본문(.float-page .modal-body)이 통째로 스크롤한다. 막대 폭은 JS가 inline으로
   두고 전환은 여기 — 감소 동작 환경은 파일 끝 @media 가 끈다. 색은 종류 틴트
   변수(.orb-poll-tone-poll)·bs 토큰만. */
.orb-poll-report {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}
.orb-poll-rp-head {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.orb-poll-rp-title {
    margin: 0;
    font-size: 1.125rem;
    font-weight: 600;
    line-height: 1.3;
    overflow-wrap: anywhere;
}
/* 스탯 타일 — 같은 폭 셋. 라벨 작게 / 값 크게(tabular-nums — 라이브 갱신에 자리가 흔들리지 않는다) / 보조 줄 작게(비어도
   높이를 지켜 셋의 키가 같다). 상태 타일의 값은 알약(.orb-poll-status)이라 글자 크기를 알약 꼴로 되돌린다. */
.orb-poll-rp-stats {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.75rem;
}
.orb-poll-rp-stat {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 0.875rem 1rem;
    min-width: 0;
}
.orb-poll-rp-stat-label {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--bs-secondary-color);
}
.orb-poll-rp-stat-value {
    display: flex;
    align-items: center;
    min-height: 2.125rem;
    font-size: 1.75rem;
    font-weight: 700;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}
.orb-poll-rp-stat-value > .orb-poll-status {
    padding: 0.1875rem 0.75rem;
    font-size: 0.875rem;
    font-weight: 600;
}
.orb-poll-rp-stat-sub {
    min-height: 1.125rem;
    font-size: 0.75rem;
    color: var(--bs-secondary-color);
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}
/* 문항 카드 — 머리 행(번호 알약은 응답자 화면과 같은 .orb-poll-rq-no, 라벨이 남는 폭, 유형 칩, 응답/건너뜀 작은 글자). */
.orb-poll-rp-questions {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.orb-poll-rp-q {
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
    padding: 1rem;
    min-width: 0;
}
.orb-poll-rp-q-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    font-weight: 600;
}
.orb-poll-rp-q-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}
.orb-poll-rp-q-meta {
    display: inline-flex;
    gap: 0.75rem;
    font-size: 0.75rem;
    font-weight: 500;
    white-space: nowrap;
    color: var(--bs-secondary-color);
    font-variant-numeric: tabular-nums;
}
.orb-poll-rp-q-meta b {
    font-weight: 700;
    color: var(--bs-body-color);
}
/* 막대 목록 — 행 = [라벨 | 막대 | 건수·백분율] 격자라 행끼리 막대 시작점이 맞는다. 라벨 열은 상한 폭에서 줄을 바꾸고 막대가
   남는 폭을 다 쓴다. 기본 막대는 틴트 반투명, 최댓값 행(is-top)은 틴트 원색 + 라벨·건수 굵게, 삭제된 선택지 묶음 행
   (is-unknown)은 회색. 폭 전환은 감속 곡선 600ms — 처음 0 → 값, 라이브 갱신은 현재 폭 → 새 폭. */
.orb-poll-rp-bars {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.orb-poll-rp-bar-row {
    display: grid;
    grid-template-columns: minmax(6rem, 16rem) minmax(0, 1fr) 6rem;
    align-items: center;
    gap: 0.75rem;
    font-size: 0.875rem;
}
.orb-poll-rp-bar-label {
    min-width: 0;
    overflow-wrap: anywhere;
}
.orb-poll-rp-bar {
    display: block;
    height: 1.25rem;
    border-radius: var(--bs-border-radius-pill);
    background: rgba(var(--bs-body-color-rgb), 0.06);
    overflow: hidden;
}
.orb-poll-rp-bar > span {
    display: block;
    width: 0;
    height: 100%;
    border-radius: inherit;
    background: rgba(var(--orb-poll-tone-rgb), 0.45);
    transition: width 600ms cubic-bezier(0.22, 1, 0.36, 1), background-color var(--pui-transition-speed);
}
.orb-poll-rp-bar-row.is-top .orb-poll-rp-bar > span {
    background: rgb(var(--orb-poll-tone-rgb));
}
.orb-poll-rp-bar-row.is-top .orb-poll-rp-bar-label,
.orb-poll-rp-bar-row.is-top .orb-poll-rp-bar-num > b {
    font-weight: 700;
    color: var(--orb-poll-tone-text);
}
.orb-poll-rp-bar-row.is-unknown {
    color: var(--bs-secondary-color);
}
.orb-poll-rp-bar-row.is-unknown .orb-poll-rp-bar > span {
    background: rgba(var(--bs-secondary-color-rgb), 0.35);
}
.orb-poll-rp-bar-num {
    display: inline-flex;
    justify-content: flex-end;
    align-items: baseline;
    gap: 0.375rem;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.orb-poll-rp-bar-num > b { font-weight: 600; }
.orb-poll-rp-bar-num > span {
    font-size: 0.75rem;
    color: var(--bs-secondary-color);
}
/* 답 카드 — 본문(줄바꿈 보존) + 작성자 공개 문항만 발 줄(아바타 sm · 응답자 표시명 · 시각). 면은 3차 배경(입력 카드와 구분). */
.orb-poll-rp-texts {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.orb-poll-rp-text {
    padding: 0.75rem 0.875rem;
    border: var(--bs-border-width) solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    background: var(--bs-tertiary-bg);
}
.orb-poll-rp-text-body {
    font-size: 0.9375rem;
    overflow-wrap: anywhere;
}
.orb-poll-rp-text-foot {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.5rem;
    font-size: 0.75rem;
    color: var(--bs-secondary-color);
}
.orb-poll-rp-who {
    font-weight: 600;
    color: var(--bs-body-color);
    overflow-wrap: anywhere;
}
.orb-poll-rp-when {
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
/* 접힘 절 — 토글(알약 버튼: 셰브론 · 이름 · 개수 배지) + 본문(표 / 목록). aria-expanded가 상태라 셰브론이 그것을 보고 돈다.
   is-muted(정의에 없는 답)는 회색이고 개수 배지만 크다. */
.orb-poll-rp-fold {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.orb-poll-rp-toggle {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    gap: 0.5rem;
    padding: 0.375rem 0.875rem;
    border: var(--bs-border-width) solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-pill);
    background: transparent;
    color: var(--bs-body-color);
    font-size: 0.875rem;
    font-weight: 600;
    transition: background-color var(--pui-transition-speed);
}
.orb-poll-rp-toggle:hover { background: var(--bs-tertiary-bg); }
.orb-poll-rp-toggle:focus-visible {
    outline: 0.125rem solid rgb(var(--bs-primary-rgb));
    outline-offset: 0.125rem;
}
.orb-poll-rp-chevron {
    font-size: 0.75rem;
    transition: transform var(--pui-transition-speed);
}
.orb-poll-rp-toggle[aria-expanded="true"] .orb-poll-rp-chevron { transform: rotate(90deg); }
.orb-poll-rp-badge {
    display: inline-flex;
    align-items: center;
    padding: 0 0.5rem;
    border-radius: var(--bs-border-radius-pill);
    font-size: 0.8125rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.orb-poll-rp-fold.is-muted .orb-poll-rp-toggle { color: var(--bs-secondary-color); }
.orb-poll-rp-fold.is-muted .orb-poll-rp-badge {
    background: rgba(var(--bs-body-color-rgb), 0.08);
    color: var(--bs-secondary-color);
    font-size: 1rem;
}
/* 응답자 표 — 수십 행 규모의 단순 표(PUITable 아님). 머리는 작고 회색, 수치 열은 tabular. */
.orb-poll-rp-table {
    margin-bottom: 0;
    font-size: 0.875rem;
}
.orb-poll-rp-table th {
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    color: var(--bs-secondary-color);
}
.orb-poll-rp-table td {
    overflow-wrap: anywhere;
    font-variant-numeric: tabular-nums;
}
/* 정의에 없는 답 목록 — 행 = 문항 id(code) · 값(응답자는 늘 가린다). */
.orb-poll-rp-orphan-list {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: 0.8125rem;
    color: var(--bs-secondary-color);
}
.orb-poll-rp-orphan-list > li {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.75rem;
}
.orb-poll-rp-orphan-values { overflow-wrap: anywhere; }

/* -----------------------------------------------------------------------------
   게임 무대(orbpoll.view.js OrbPollView.stage — 룰렛 회전 · 사다리 걷기). 앱 참여 창과 셸 페이지가 같은 DOM 을 그린다.
   색은 조각·칩마다 JS 가 넘기는 색상각(--hue)과 아래 휠 토큰으로만 — 아바타 .feed-avatar 와 같은 62%/85%/25% 결이라 사다리
   경로·도착 슬롯이 그 사람 아바타(칩의 .orb-poll-avatar — 같은 색상각)와 같은 색이고(룰렛 조각은 항목 순 색상각), 다크는 명도를 맞바꾼다.
   룰렛 꽝 조각은 색상각 없이 중립 토큰(.is-blank). 길이는 rem(사다리 SVG 는 viewBox 1단위 = 1rem, JS 가 폭·높이를 rem 으로 못 박는다). 회전 요소
   (.orb-poll-wheel-rot)에는 공용 pulse(transform) 를 얹지 않는다 — transform 애니메이션은 포인터·칩 아바타뿐. 단계 표식
   (.is-drawn → .is-revealed → .is-flowing)·한 열 보기(.is-solo)는 루트 클래스, 지연은 요소의 --delay. 감소 동작은 파일 끝 @media
   (전환·공회전·키프레임 전부 끔). 휠 토큰은 결과 패널(.orb-poll-result — 룰렛 요약 칩·결과 칩이 바퀴와 같은 색)에도 같은 값으로 둔다. */
.orb-poll-stage,
.orb-poll-result {
    --orb-poll-wheel-sat: 62%;
    --orb-poll-wheel-l: 85%;
    --orb-poll-wheel-fg-l: 25%;
    --orb-poll-wheel-win-l: 70%;
    --orb-poll-path-l: 48%;
}
.orb-poll-stage {
    position: relative;
    width: 100%;
}
[data-bs-theme="dark"] .orb-poll-stage,
[data-bs-theme="dark"] .orb-poll-result {
    --orb-poll-wheel-l: 25%;
    --orb-poll-wheel-fg-l: 85%;
    --orb-poll-wheel-win-l: 40%;
    --orb-poll-path-l: 60%;
}
/* 색상각 색 — --hue 를 다는 요소(룰렛 호·범례 행·요약 칩·결과 칩 / 사다리 칩·경로·도착 슬롯)마다 선언해 그 요소의 --hue 로 풀리고, 안쪽
   요소가 물려받아 쓴다(사용자 속성의 var() 는 선언한 요소에서 계산된다 — 무대 루트에 두면 --hue 가 없는 자리에서 풀린다). 명도는 위 휠
   토큰이라 다크 전환을 따른다. fill = 면 · ink = 면 위 글자 · win = 당첨 강조 면 · line = 사다리 선. */
.orb-poll-wheel-slice,
.orb-poll-legend-item,
.orb-poll-summary-chip,
.orb-poll-result-slot,
.orb-poll-ladder-chip,
.orb-poll-ladder-path,
.orb-poll-ladder-slot {
    --orb-poll-hue-fill: hsl(var(--hue, 210), var(--orb-poll-wheel-sat), var(--orb-poll-wheel-l));
    --orb-poll-hue-ink: hsl(var(--hue, 210), var(--orb-poll-wheel-sat), var(--orb-poll-wheel-fg-l));
    --orb-poll-hue-win: hsl(var(--hue, 210), 80%, var(--orb-poll-wheel-win-l));
    --orb-poll-hue-line: hsl(var(--hue, 210), 70%, var(--orb-poll-path-l));
}

/* 컨페티 캔버스 — 무대 상자 안(absolute inset 0)이라 FloatPage 밖으로 안 나간다. 연출 중에만 존재. */
.orb-poll-confetti {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 2;
}

/* --- 룰렛 — 가운데 정사각 휠(최대 28rem) / 그 아래 범례(칩이 가로로 흐르고 넘치면 여러 줄 — 참여 창·셸 같은 배치). 휠은 살짝 떠 있는
   원판: 회전 그룹(조각 + 림 + 핀)만 돌고, 원판 그림자(svg 상자의 box-shadow)·광택 오버레이·허브·포인터는 고정이라 면이 돌아도 빛과
   그림자는 제자리다. 그림자·광택은 테마 토큰(--bs-black-rgb·--bs-white-rgb·--bs-body-color-rgb)의 rgba 만, 세기는 아래 깊이 토큰 —
   다크는 그림자를 짙게·하이라이트를 옅게. 림·허브 칼라 = 종류 틴트(--orb-poll-tone-rgb — 룰렛 warning), 허브 핀 = 중립 금속, 포인터·
   착지 강조 = --bs-danger-rgb(「선택된 곳」 전용 — 동적 테마 danger, 셸은 pui.css 기본값). 포인터 자리로 위에 0.75rem, 원판 그림자 자리로
   휠 아래 간격 2rem. */
.orb-poll-stage-roulette {
    --orb-poll-wheel-lift: 0.1;     /* 가까운 그림자(원판·허브) */
    --orb-poll-wheel-cast: 0.22;    /* 먼 그림자(원판이 떠 있는 높이)·포인터 그림자 */
    --orb-poll-wheel-shine: 0.22;   /* 고정 광원 하이라이트(좌상단) */
    --orb-poll-wheel-shade: 0.12;   /* 반대편·가장자리 쉐이딩 */
    --orb-poll-wheel-bevel: 0.55;   /* 위 가장자리 반사·허브 돔 광택 */
    padding-top: 0.75rem;
}
[data-bs-theme="dark"] .orb-poll-stage-roulette {
    --orb-poll-wheel-lift: 0.35;
    --orb-poll-wheel-cast: 0.55;
    --orb-poll-wheel-shine: 0.08;
    --orb-poll-wheel-shade: 0.28;
    --orb-poll-wheel-bevel: 0.16;
}
.orb-poll-roulette {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2rem;
}
.orb-poll-wheel {
    position: relative;
    width: 100%;
    max-width: 28rem;
    aspect-ratio: 1;
}
/* 원판 그림자 — svg 상자(정사각, 반지름 50% = 림 바깥 원)의 box-shadow. svg 자체는 돌지 않아(도는 것은 안쪽 g) 그림자는 고정이고
   한 번만 그려진다. 가까운 그림자 + 아래로 멀리 번지는 그림자 = 바닥에서 살짝 뜬 원판. */
.orb-poll-wheel-svg {
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible;
    border-radius: 50%;
    box-shadow: 0 0.375rem 0.75rem rgba(var(--bs-black-rgb), var(--orb-poll-wheel-lift)),
                0 1.25rem 1.75rem -0.5rem rgba(var(--bs-black-rgb), var(--orb-poll-wheel-cast));
}
/* 회전 그룹 — 결과 전 공회전(1바퀴 60초), 돌리기는 JS 가 inline transform + transition(10초 — 절대 시간, 테마 속도와 무관) 으로.
   transform-box 가 view-box 라 원점이 viewBox 중심 — 조각 path 도 그 중심(translate(100 100))에 놓여야 제자리에서 돈다. 공회전 지연은
   idle() 이 멈춘 각에서 끊김 없이 잇도록 그 각의 몫(0~1, --orb-poll-wheel-idle-from)만큼 당긴다. */
.orb-poll-wheel-rot {
    transform-box: view-box;
    transform-origin: 50% 50%;
    will-change: transform;
}
.orb-poll-wheel.is-idle .orb-poll-wheel-rot {
    animation: orb-poll-wheel-idle 60s linear infinite;
    animation-delay: calc(var(--orb-poll-wheel-idle-from, 0) * -60s);
}
@keyframes orb-poll-wheel-idle { to { transform: rotate(360deg); } }
/* 조각 — 당첨 항목 = hsl(색상각, 휠 토큰), 꽝(항목·자동 — .is-blank) = 중립 면. 조각 사이 얇은 간격은 바탕색 선. 착지 강조 = 당첨
   .is-hit(채도↑·명도↓) / 꽝 .is-blank-hit(면 그대로) + 둘 다 danger 테두리(JS 가 그 조각을 조각 무리의 맨 위로 올려 이웃 조각에 안
   가린다 — 바깥 호 쪽 절반은 림 밑). */
.orb-poll-wheel-path {
    fill: var(--orb-poll-hue-fill);
    stroke: var(--bs-body-bg);
    stroke-width: 1;
    stroke-linejoin: round;
    transition: fill calc(var(--pui-transition-speed) * 0.75) ease, stroke calc(var(--pui-transition-speed) * 0.75) ease,
                stroke-width calc(var(--pui-transition-speed) * 0.75) ease;
}
.orb-poll-wheel-slice.is-blank .orb-poll-wheel-path { fill: var(--bs-secondary-bg); }
.orb-poll-wheel-slice.is-hit .orb-poll-wheel-path { fill: var(--orb-poll-hue-win); }
.orb-poll-wheel-slice.is-hit .orb-poll-wheel-path,
.orb-poll-wheel-slice.is-blank-hit .orb-poll-wheel-path {
    stroke: rgb(var(--bs-danger-rgb));
    stroke-width: 2;
}
.orb-poll-wheel-label {
    fill: var(--orb-poll-hue-ink);
    font-weight: 600;
    dominant-baseline: middle;
    pointer-events: none;
    user-select: none;
}
.orb-poll-wheel-slice.is-blank .orb-poll-wheel-label { fill: var(--bs-secondary-color); }
.orb-poll-wheel-slice.is-hit .orb-poll-wheel-label,
.orb-poll-wheel-slice.is-blank-hit .orb-poll-wheel-label { font-weight: 800; }
/* 림(베젤) — 조각 바깥 띠(반지름 R..100, 굵기·반지름은 JS 속성), 종류 틴트 금속 — 음영은 위 광택 오버레이가 고정 광원으로 준다.
   핀 — 림 위 호 경계마다 작은 흰 못(회전 그룹 안이라 조각과 함께 돈다). 감속 꼬리에서 포인터가 넘을 때 튕기는 그 경계다. */
.orb-poll-wheel-rim {
    fill: none;
    stroke: rgb(var(--orb-poll-tone-rgb));
}
.orb-poll-wheel-peg {
    fill: rgb(var(--bs-white-rgb));
    stroke: rgba(var(--bs-black-rgb), 0.3);
    stroke-width: 0.5;
}
/* 범례 — 휠 아래 가운데, 칩(색 점 · 항목명(말줄임) · n%)이 가로로 흐르고 넘치면 다음 줄(줄 수 제한 없음). 상자 폭은 휠보다 조금
   넓게까지(40rem) — 넓은 창에서 한 줄이 휠 폭을 한참 넘어 퍼지지 않는다. 순서 = 항목 순서(바퀴 위 위치와 무관 — 바퀴는 항목을 흩고
   꽝 틈을 여러 호로 끼운다), 호와 같은 키(data-segment / data-segment-auto — 자동 꽝 행은 하나)·같은 강조 클래스. */
.orb-poll-legend-box {
    width: 100%;
    max-width: 40rem;
    min-width: 0;
}
.orb-poll-legend {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.25rem 0.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: 0.8125rem;
}
.orb-poll-legend-item {
    flex: 0 1 auto;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
    max-width: 100%;
    padding: 0.1875rem 0.625rem;
    border-radius: var(--bs-border-radius-pill);
    line-height: 1.3;
    transition: background-color calc(var(--pui-transition-speed) * 0.75) ease, box-shadow calc(var(--pui-transition-speed) * 0.75) ease;
}
.orb-poll-legend-dot {
    width: 0.75rem;
    height: 0.75rem;
}
.orb-poll-legend-label {
    flex: 1 1 auto;
    min-width: 0;
    max-width: 14rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.orb-poll-legend-pct {
    flex: 0 0 auto;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--bs-secondary-color);
}
.orb-poll-legend-item.is-blank .orb-poll-legend-label { color: var(--bs-secondary-color); }
.orb-poll-legend-item.is-hit {
    background: var(--orb-poll-hue-fill);
    color: var(--orb-poll-hue-ink);
    font-weight: 600;
}
.orb-poll-legend-item.is-hit .orb-poll-legend-dot { background: var(--orb-poll-hue-win); }
.orb-poll-legend-item.is-hit .orb-poll-legend-pct { color: inherit; }
.orb-poll-legend-item.is-blank-hit {
    background: var(--bs-secondary-bg);
    font-weight: 600;
}
/* 착지 행 — 바퀴의 착지 조각 테두리와 같은 danger 고리(칩 안쪽 — 이웃 칩과 겹치지 않는다). */
.orb-poll-legend-item.is-hit,
.orb-poll-legend-item.is-blank-hit { box-shadow: inset 0 0 0 0.125rem rgb(var(--bs-danger-rgb)); }
/* 광택 — 원판 위 고정 레이어(돌지 않는다): 좌상단 하이라이트 → 우하단 쉐이딩(고정 광원) + 가장자리로 갈수록 살짝 어두운 볼록면 +
   위 가장자리 반사·아래 가장자리 그늘(inset — 림의 베벨). 포인터 이벤트는 아래 조각으로 흘린다(조각 툴팁). */
.orb-poll-wheel-gloss {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: linear-gradient(160deg, rgba(var(--bs-white-rgb), var(--orb-poll-wheel-shine)) 0%, rgba(var(--bs-white-rgb), 0) 42%,
                                        rgba(var(--bs-black-rgb), 0) 58%, rgba(var(--bs-black-rgb), var(--orb-poll-wheel-shade)) 100%),
                radial-gradient(closest-side, rgba(var(--bs-black-rgb), 0) 78%, rgba(var(--bs-black-rgb), var(--orb-poll-wheel-shade)) 100%);
    box-shadow: inset 0 0.125rem 0.1875rem rgba(var(--bs-white-rgb), var(--orb-poll-wheel-bevel)),
                inset 0 -0.1875rem 0.375rem rgba(var(--bs-black-rgb), var(--orb-poll-wheel-shade));
    pointer-events: none;
}
/* 허브 — 휠 중앙 24% 고정 캡. 동심원 셋이 같은 상자의 inset 으로 서서 중심이 기하적으로 맞는다(글리프 여백 없음): 칼라(종류 틴트
   금속 — 림과 같은 결, 원판 위로 그림자) → 돔(본문 바탕 + 24갈래 햇살 결 + 좌상단 광택, 아래 가장자리 그늘로 볼록) → 핀(중립 금속 —
   본문 글자색 반 + 본문 바탕 반이라 라이트·다크 모두 중간 회색, 칼라와 같은 160° 광택 + 좌상단 반사점. danger 는 「선택된 곳」 전용이라
   쓰지 않는다 — 가운데가 포인터보다 시선을 끌지 않게). */
.orb-poll-wheel-hub {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 24%;
    aspect-ratio: 1;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    background: linear-gradient(160deg, rgba(var(--bs-white-rgb), var(--orb-poll-wheel-bevel)) 0%, rgba(var(--bs-white-rgb), 0) 45%,
                                        rgba(var(--bs-black-rgb), 0) 55%, rgba(var(--bs-black-rgb), var(--orb-poll-wheel-shade)) 100%),
                rgb(var(--orb-poll-tone-rgb));
    box-shadow: 0 0.25rem 0.625rem rgba(var(--bs-black-rgb), var(--orb-poll-wheel-cast)),
                0 0.0625rem 0.125rem rgba(var(--bs-black-rgb), var(--orb-poll-wheel-lift));
    pointer-events: none;
}
.orb-poll-wheel-hub-cap,
.orb-poll-wheel-hub-pin {
    position: absolute;
    border-radius: 50%;
}
.orb-poll-wheel-hub-cap {
    inset: 11%;
    background: radial-gradient(circle at 34% 28%, rgba(var(--bs-white-rgb), var(--orb-poll-wheel-bevel)) 0, rgba(var(--bs-white-rgb), 0) 52%),
                repeating-conic-gradient(rgba(var(--bs-body-color-rgb), 0.06) 0 7.5deg, rgba(var(--bs-body-color-rgb), 0) 7.5deg 15deg),
                var(--bs-body-bg);
    box-shadow: inset 0 -0.1875rem 0.375rem rgba(var(--bs-black-rgb), var(--orb-poll-wheel-shade)),
                0 0 0 1px rgba(var(--bs-black-rgb), var(--orb-poll-wheel-lift));
}
.orb-poll-wheel-hub-pin {
    inset: 40%;
    background: radial-gradient(circle at 36% 30%, rgba(var(--bs-white-rgb), 0.7) 0, rgba(var(--bs-white-rgb), 0) 55%),
                linear-gradient(160deg, rgba(var(--bs-white-rgb), var(--orb-poll-wheel-bevel)) 0%, rgba(var(--bs-white-rgb), 0) 45%,
                                        rgba(var(--bs-black-rgb), 0) 55%, rgba(var(--bs-black-rgb), var(--orb-poll-wheel-shade)) 100%),
                linear-gradient(rgba(var(--bs-body-color-rgb), 0.5), rgba(var(--bs-body-color-rgb), 0.5)),
                var(--bs-body-bg);
    box-shadow: 0 0.0625rem 0.1875rem rgba(var(--bs-black-rgb), var(--orb-poll-wheel-cast)),
                inset 0 -0.0625rem 0.125rem rgba(var(--bs-black-rgb), 0.25);
}
/* 포인터 — 상단 중앙 물방울(고정): ::after 머리 원(축 — 가운데 흰 리벳, 좌상단 광택) + ::before 꼬리 삼각형(머리 원 접선에서 끝으로 —
   clip-path 위 두 점 = 접점, 좌우 면을 밝게/어둡게 나눈 모서리). 색 = danger(동적 테마), 그림자는 바깥 요소의 drop-shadow(clip-path 와
   filter 를 한 요소에 두면 그림자가 잘린다). 축(머리 원 중심) 기준으로 감속 꼬리에서 핀을 넘을 때마다 .is-tick(150ms — 길이는 여기 한 곳,
   JS 는 animationend 로 끝을 알고 다음 틱을 허락한다), 착지하면 .is-bounce(크게 한 번) — transform 애니메이션은 포인터에만. 꼬리 끝 = 휠
   위에서 1.375rem(림을 지나 조각 안). */
.orb-poll-wheel-pointer {
    position: absolute;
    left: 50%;
    top: -0.75rem;
    width: 1.375rem;
    height: 2.125rem;
    transform: translateX(-50%);
    transform-origin: 50% 0.6875rem;
    filter: drop-shadow(0 0.1875rem 0.1875rem rgba(var(--bs-black-rgb), var(--orb-poll-wheel-cast)));
    z-index: 1;
}
.orb-poll-wheel-pointer::before,
.orb-poll-wheel-pointer::after {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
}
.orb-poll-wheel-pointer::before {
    height: 100%;
    background: linear-gradient(90deg, rgba(var(--bs-white-rgb), var(--orb-poll-wheel-shine)) 0 50%, rgba(var(--bs-black-rgb), var(--orb-poll-wheel-shade)) 50% 100%),
                rgb(var(--bs-danger-rgb));
    clip-path: polygon(6% 47.8%, 94% 47.8%, 50% 100%);
}
.orb-poll-wheel-pointer::after {
    aspect-ratio: 1;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(var(--bs-white-rgb), 0.9) 0 17%, rgba(var(--bs-white-rgb), 0) 19%),
                radial-gradient(circle at 34% 28%, rgba(var(--bs-white-rgb), var(--orb-poll-wheel-bevel)) 0, rgba(var(--bs-white-rgb), 0) 60%),
                rgb(var(--bs-danger-rgb));
}
.orb-poll-wheel-pointer.is-tick { animation: orb-poll-pointer-tick 150ms ease-out; }
@keyframes orb-poll-pointer-tick {
    0%   { transform: translateX(-50%) rotate(0); }
    30%  { transform: translateX(-50%) rotate(-12deg); }
    100% { transform: translateX(-50%) rotate(0); }
}
.orb-poll-wheel-pointer.is-bounce { animation: orb-poll-pointer-bounce 600ms cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes orb-poll-pointer-bounce {
    0%   { transform: translateX(-50%) rotate(0); }
    30%  { transform: translateX(-50%) translateY(0.25rem) rotate(-16deg); }
    60%  { transform: translateX(-50%) rotate(9deg); }
    100% { transform: translateX(-50%) rotate(0); }
}

/* --- 사다리 — [칩 행] / [SVG] / [슬롯 행]. 안쪽 .orb-poll-ladder 폭 = 열 수 × 간격(rem, JS) — 상자보다 넓으면 래퍼가 가로 스크롤.
   재생 단계의 전환 길이는 아래 사용자 속성 한 곳 — JS(play)가 읽어 ① 가로줄 구간·안전망을 잡고, ②·③ 은 마지막 요소의 transitionend 로
   넘어간다. 요소마다의 지연 보폭(--delay)은 JS 몫. 값은 ms 로 둔다(JS 가 숫자로 읽는다). */
.orb-poll-stage-ladder {
    --orb-poll-ladder-rung-ms: 280ms;    /* 가로줄 한 층이 그려지는 시간 */
    --orb-poll-ladder-flip-ms: 420ms;    /* 슬롯 한 칸이 뒤집히는 시간 */
    --orb-poll-ladder-flow-ms: 1400ms;   /* 경로 한 줄이 위→아래로 흐르는 시간 */
}
.orb-poll-ladder-scroll {
    width: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    padding-bottom: 0.25rem;
}
.orb-poll-ladder { margin: 0 auto; }
.orb-poll-ladder-row {
    display: grid;
    align-items: end;
}
.orb-poll-ladder-top { padding-bottom: 0.25rem; }
.orb-poll-ladder-bottom {
    align-items: start;
    padding-top: 0.25rem;
}
.orb-poll-ladder-svg {
    display: block;
    overflow: visible;
}
/* 세로줄 — 참여자 0명이면(.is-empty) 자리 표시 점선 두 줄. */
.orb-poll-ladder-rail {
    stroke: var(--bs-border-color);
    stroke-width: 0.125;
    stroke-linecap: round;
}
.orb-poll-stage-ladder.is-empty .orb-poll-ladder-rail { stroke-dasharray: 0.4 0.4; }
/* 가로줄 — 길이(--len = 열 간격)만큼 대시를 두고 offset 으로 숨겼다가 .is-drawn 에 들어온다(층 × 60ms 지연 = --delay). */
.orb-poll-ladder-rung {
    stroke: var(--bs-secondary-color);
    stroke-width: 0.125;
    stroke-linecap: round;
    stroke-dasharray: var(--len);
    stroke-dashoffset: var(--len);
    transition: stroke-dashoffset var(--orb-poll-ladder-rung-ms) ease-out var(--delay, 0ms);
}
.orb-poll-stage.is-drawn .orb-poll-ladder-rung { stroke-dashoffset: 0; }
/* 경로 — 열 색, 전체 길이(--len)만큼 대시, .is-flowing 에 위→아래로 흐른다(1.4초, 열 × 120ms = --delay). 한 열 보기에서 다른 경로는 흐려짐. */
.orb-poll-ladder-path {
    fill: none;
    stroke: var(--orb-poll-hue-line);
    stroke-width: 0.3;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: var(--len);
    stroke-dashoffset: var(--len);
    opacity: 0.9;
    transition: stroke-dashoffset var(--orb-poll-ladder-flow-ms) cubic-bezier(0.4, 0, 0.2, 1) var(--delay, 0ms), opacity 300ms ease, stroke-width 300ms ease;
}
.orb-poll-stage.is-flowing .orb-poll-ladder-path { stroke-dashoffset: 0; }
.orb-poll-stage.is-solo .orb-poll-ladder-path.is-dim { opacity: 0.12; }
.orb-poll-stage.is-solo .orb-poll-ladder-path.is-active {
    opacity: 1;
    stroke-width: 0.4;
}
/* 참여자 칩 — 아바타 sm(.orb-poll-avatar — 칩의 색상각) + 라벨(말줄임). 결과 뒤에만 버튼(role=button) — 한 열 보기. 새 참여는 원이 톡 하고 선다. */
.orb-poll-ladder-chip {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.125rem;
    min-width: 0;
    padding: 0.125rem;
    border-radius: 0.5rem;
    outline: none;
}
.orb-poll-ladder-chip[role="button"] { cursor: pointer; }
.orb-poll-ladder-chip[role="button"]:focus-visible { box-shadow: 0 0 0 0.125rem rgba(var(--orb-poll-tone-rgb), 0.5); }
.orb-poll-ladder-chip > .orb-poll-avatar { transition: box-shadow calc(var(--pui-transition-speed) * 0.5) ease; }
.orb-poll-ladder-chip.is-new > .orb-poll-avatar { animation: orb-poll-pop 360ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }
@keyframes orb-poll-pop {
    from { transform: scale(0); }
    to   { transform: scale(1); }
}
.orb-poll-stage.is-solo .orb-poll-ladder-chip[aria-pressed="true"] > .orb-poll-avatar { box-shadow: 0 0 0 0.1875rem var(--orb-poll-hue-line); }
.orb-poll-ladder-name {
    max-width: 100%;
    font-size: 0.75rem;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--bs-secondary-color);
}
/* 결과 슬롯 — 앞면 「?」 / 뒷면 라벨(또는 꽝 회색). .is-revealed 에 뒤집힌다(슬롯 × 40ms = --delay). 도착 열 색으로 강조(.is-hit). */
.orb-poll-ladder-slot {
    min-width: 0;
    padding: 0 0.125rem;
    perspective: 20rem;
}
.orb-poll-ladder-slot-card {
    position: relative;
    height: 2rem;
    transform-style: preserve-3d;
    transition: transform var(--orb-poll-ladder-flip-ms) cubic-bezier(0.4, 0, 0.2, 1) var(--delay, 0ms);
}
.orb-poll-stage.is-revealed .orb-poll-ladder-slot-card { transform: rotateX(180deg); }
.orb-poll-ladder-slot-face {
    position: absolute;
    inset: 0;
    display: block;
    padding: 0 0.375rem;
    border: 1px solid var(--bs-border-color);
    border-radius: 0.5rem;
    background: var(--bs-tertiary-bg);
    font-size: 0.75rem;
    font-weight: 600;
    line-height: calc(2rem - 2px);
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    backface-visibility: hidden;
    transition: border-color 300ms ease, box-shadow 300ms ease, background-color 300ms ease, opacity 300ms ease;
}
.orb-poll-ladder-slot-face.is-back {
    transform: rotateX(180deg);
    background: var(--bs-body-bg);
}
.orb-poll-ladder-slot.is-blank .orb-poll-ladder-slot-face.is-back {
    color: var(--bs-secondary-color);
    background: var(--bs-tertiary-bg);
    font-weight: 500;
}
.orb-poll-ladder-slot.is-hit .orb-poll-ladder-slot-face.is-back {
    border-color: var(--orb-poll-hue-line);
    box-shadow: 0 0 0 0.125rem hsla(var(--hue, 210), 70%, var(--orb-poll-path-l), 0.25);
    background: var(--orb-poll-hue-fill);
    color: var(--orb-poll-hue-ink);
}
.orb-poll-stage.is-solo .orb-poll-ladder-slot.is-hit:not(.is-active) .orb-poll-ladder-slot-face.is-back { opacity: 0.35; }

/* =============================================================================
   셸 페이지(orbpoll.page.js — GET /api/v1/tool/polls|games/{id}/show). 앱 없이 서는 외부 진입 지면: #orb-root 안에
   .orb-page(폭 min(60rem, 100%) 가운데) = 브랜드 머리 / 종이(.orb-page-paper — 참여 창과 같은 .orb-poll-view 세로 흐름이라 머리·설명·
   문항·무대·칩 줄·결과 패널 규칙을 그대로 쓴다) / 종이 안 상태 카드(중립·완료)·인라인 알림·액션 줄. 토큰은 bs·pui 것만 —
   테마는 페이지 JS 가 html[data-bs-theme] 을 정한다(앱 저장 모드 → OS 설정). 진행 막대(.orb-poll-view-progress)는 셸에 없다.
   ============================================================================= */
.orb-page {
    width: min(60rem, 100%);
    margin: 0 auto;
    padding: 1.5rem 1rem 3rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
/* 브랜드 머리 — 로고(높이 2rem) + 서비스 이름. 둘 다 없으면 JS 가 머리를 세우지 않는다. */
.orb-page-brand {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-height: 2rem;
}
.orb-page-brand img {
    height: 2rem;
    width: auto;
}
.orb-page-brand-title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--bs-secondary-color);
}
/* 종이 — 카드 한 장(테마 면색·테두리·옅은 그림자). */
.orb-page-paper {
    padding: 1.25rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-lg);
    background: var(--bs-body-bg);
    box-shadow: var(--bs-box-shadow-sm);
}
.orb-page-title {
    font-size: 1.375rem;
}
/* 상태 카드(중립 · 완료) — 가운데 세로 묶음. 완료 카드의 체크는 틴트색. */
.orb-page-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    padding: 3rem 1rem;
    text-align: center;
}
.orb-page-empty-title {
    margin: 0;
    font-size: 1.25rem;
    font-weight: 600;
}
.orb-page-empty-text {
    margin: 0;
    color: var(--bs-secondary-color);
}
.orb-page-done > .bi {
    font-size: 3rem;
    line-height: 1;
    color: var(--orb-poll-tone-text);
}
/* 액션 줄 — 위 경계선, 오른쪽 정렬. 사유 알약은 왼쪽 끝(margin auto). */
.orb-page-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 0.75rem;
    margin: 0;
    padding-top: 1rem;
    border-top: 1px solid var(--bs-border-color);
}
.orb-page-reason {
    margin-inline-end: auto;
    font-size: 0.8125rem;
}

/* =============================================================================
   감소 동작(prefers-reduced-motion) — 이 파일의 전환·키프레임 전부를 여기 한 블록이 끈다(앱·셸 같은 규칙, OS 설정을 바꾸면 곧바로
   따른다). 뷰포트 질의가 아니라 사용자 설정 질의라 루트 zoom 과 무관하다(벤더 pui.css 와 같은 관용구 — 선택자는 원 규칙 그대로라
   파일 끝에서 이긴다). JS 연출(룰렛 회전·사다리 재생·컨페티)은 OrbPollView.reducedMotion() 이 따로 건너뛴다. 새 전환·키프레임을
   더하면 여기에도 적는다.
   ============================================================================= */
@media (prefers-reduced-motion: reduce) {
    .orb-poll-progress > span,
    .orb-poll-view-progress-bar > span,
    .orb-poll-opt-add,
    .orb-poll-q-add,
    .orb-poll-rq-opt,
    .orb-poll-rp-bar > span,
    .orb-poll-rp-toggle,
    .orb-poll-rp-chevron,
    .orb-poll-wheel-path,
    .orb-poll-legend-item,
    .orb-poll-ladder-rung,
    .orb-poll-ladder-path,
    .orb-poll-ladder-chip > .orb-poll-avatar,
    .orb-poll-ladder-slot-card,
    .orb-poll-ladder-slot-face {
        transition: none;
    }
    .orb-poll-result,
    .orb-poll-wheel.is-idle .orb-poll-wheel-rot,
    .orb-poll-wheel-pointer.is-tick,
    .orb-poll-wheel-pointer.is-bounce,
    .orb-poll-ladder-chip.is-new > .orb-poll-avatar {
        animation: none;
    }
}

