/* Neutral lấy nguyên từ branding/tokens.css -- đó là chuẩn cho mọi site d4t0,
   và branding ghi rõ nhóm này là "brand-level, do not vary per project".
   Trước đây file này dùng bảng xám lạnh riêng (#fafafa/#18181b/#e4e4e7) trong
   khi trang tạo (create-tokens.css) dùng bảng ấm đúng chuẩn -- cùng một class
   .topbar-logo render ra hai màu khác nhau tuỳ trang.

   TÊN token giữ nguyên (--paper/--ink/--line, ~130 chỗ tham chiếu), chỉ GIÁ
   TRỊ đổi. Đối chiếu tên ghi ngay cạnh từng dòng. */
:root {
  /* Corner radius, Fibonacci, same scale as spacing so that
     inner = outer - padding lands on another step.
     See branding/tokens.css. */
  --radius-xs: 5px;
  --radius-sm: 8px;
  --radius-md: 13px;
  --radius-lg: 21px;
  --radius-pill: 999px;
  --radius-circle: 50%;

  color-scheme: light dark;

  /* Colors */
  --paper: #fbfbf9; /* branding --bg */
  --surface: #fcfcfb; /* branding --surface */
  --ink: #1c1917; /* branding --text -- 16.88:1 trên --paper */
  --ink-2: #6f6b64; /* branding --text-muted -- 5.11:1 trên --paper */
  --logo-color: #293133; /* branding --logo-color -- 12.35:1 trên --paper */
  /* branding chỉ định nghĩa MỘT mức chữ phụ, nên --ink-3 trỏ vào cùng giá trị
     thay vì tự nghĩ ra mức thứ hai ("Never invent a colour", branding/CLAUDE.md).
     Hai mức cũ (#62626b / #71717a) chênh nhau 1.15:1 -- gần như không phân biệt
     được bằng mắt. Nếu sau này cần tách thật, đó là việc phải nêu ra với chuẩn
     branding chứ không tự đặt ở đây. */
  --ink-3: #6f6b64;
  --line: #e7e4de; /* branding --border */
  /* Nền chip. Dùng chung giá trị với --fill của create-tokens.css để hai
     stylesheet không còn hai sắc xám. #6f6b64 trên nền này đạt 4.69:1. */
  --fill: #f3f1ec;
  /* KHÔNG có trong branding/tokens.css -- token riêng của project này, dùng cho
     nền nút disabled và vài viền đậm hơn hairline.
     #d4d4d8 là xám LẠNH (R-B -4), và sau khi cả bảng về họ ấm thì nó thành thứ
     duy nhất lệch tông -- thấy rõ nhất ở nút "Gửi bình chọn" lúc chưa chọn gì,
     một khối xám xanh to nằm giữa trang giấy ấm. Xoay sang ấm ở ĐÚNG độ sáng
     cũ (1.428 so với 1.427 trên --paper), nên không có tỉ số tương phản nào
     đổi. Bản tối giữ nguyên: ở đó cả bảng vốn lạnh, #3f3f46 (-7) đang khớp. */
  --line-strong: #d7d4ce;
  /* branding --control-border. Viền là thứ DUY NHẤT nhận ra một control ở đây
     (nền --surface chỉ 1.006:1 so với trang), nên áp ngưỡng 3:1 của WCAG
     1.4.11 chứ không dùng --line (1.22:1, chỉ hợp cho hairline trang trí).
     3.33:1 trên --paper. */
  --control-border: #7c796f;
  /* Text/icon colour on a filled --seal button. Split from plain white so
     dark mode can flip it: there the accent is light, and white on it is
     3.41:1. */
  --on-seal: #ffffff;
  /* Họ accent đỏ của branding (branding/README.md "Colour"). Tên --seal giữ
     nguyên vì 22 chỗ đang dùng và nó mô tả đúng vai trò ở app này (con dấu). */
  --seal: #c9362b;
  --seal-soft: #fbeae8;
  --seal-hover: #b22e24;
  /* #16a34a was only 2.99:1 inside the "Đang mở" pill. 4.92:1. */
  /* = branding --success / --success-weak. */
  --success: #127a38;
  --success-soft: #eaf7ee;


  /* Type */
  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* Trên Apple, -apple-system lấy đúng San Francisco — font hệ thống, không
     tải gì thêm, và là font được tinh chỉnh cho chính màn hình đó. */
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Spacing -- thang Fibonacci của branding/tokens.css (xấp xỉ tỉ lệ φ≈1.618),
     thay cho lưới 4pt cũ. Đây chính là thang mà create-tokens.css đã dùng, nên
     đổi ở đây là hai trang về chung một nhịp.
     --sp-5 (40px) và --sp-8 (64px) không có chỗ nào dùng nên bỏ luôn. */
  --sp-1: 8px; /* branding --space-1 */
  --sp-2: 13px; /* --space-2, trước là 16px */
  --sp-3: 21px; /* --space-3, trước là 24px */
  --sp-4: 34px; /* --space-4, trước là 32px */
  --sp-6: 55px; /* --space-5, trước là 48px */

  /* Radius */
/* = branding --radius */

  /* Motion */
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --dur-fast: 140ms;
  --dur: 220ms;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Cùng nguồn với khối sáng: branding/tokens.css, khối dark của nó. */
    --paper: #0f1115; /* branding --bg */
    --surface: #171a21; /* branding --surface */
    --ink: #eef0f4; /* branding --text -- 16.56:1 */
    --ink-2: #a2a3a8; /* branding --text-muted -- 7.51:1 */
    --logo-color: #eef0f4; /* branding --logo-color, khối dark */
    --ink-3: #a2a3a8; /* một mức, như khối sáng */
    --line: #2c2e33; /* branding --border */
    --fill: #212325; /* --ink-2 trên nền này đạt 6.00:1 */
    --line-strong: #3f3f46;
    /* Lifted off #C9362B, which goes muddy on a dark ground -- same hue,
       enough luminance to clear AA against --paper as text and to read as
       the same red on a bar fill. */
    --seal: #f0574a;
    --seal-soft: #2e1a18;
    --seal-hover: #ff6d60;
    --success: #4ade80;
    --control-border: #797d86; /* branding --control-border (dark), 3.92:1 */
    /* The accent is light in dark mode, so a filled button takes dark text
       rather than white -- white on #f0574a is 3.41:1, this is 5.63:1. The
       same inversion Material 3 and Tailwind's dark themes use. */
    --on-seal: #1a0b09;
    --success-soft: #16281c;
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-ui);
  /* Trang trộn hai họ chữ: stack hệ thống cho mọi câu chữ, và --font-mono cho
     dữ liệu thật (bộ đếm ngược, số phiếu, mã poll). Hai họ này có x-height
     khác nhau, nên số mono nằm giữa câu tiếng Việt trông lệch cỡ so với chữ
     xung quanh. from-font lấy tỉ lệ x-height của font đầu tiên rồi áp cho các
     font con, nên phần mono khớp lại đúng cỡ mắt nhìn.
     Không kèm @supports: trình duyệt không hỗ trợ sẽ vẽ đúng như hiện nay,
     tức là fallback vốn đã là trạng thái cũ, thêm block rỗng chỉ tốn byte. */
  font-size-adjust: from-font;
  background: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  /* 100vh on iOS Safari counts the collapsing address bar, so a "full
     height" page is taller than the visible area and the footer sits below
     the fold until you scroll. dvh tracks the real viewport. */
  min-height: 100dvh;
  /* Stops iOS from silently scaling text up in landscape. */
  -webkit-text-size-adjust: 100%;
}

h1,
h2,
h3,
p {
  margin: 0;
}

button {
  font-family: inherit;
}

a {
  color: var(--seal);
}

.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
a:focus-visible,
/* tabindex="-1" is excluded on purpose: those elements are never reachable by
   Tab, they are only focused programmatically to move a screen reader to new
   content (the dialog headings, the question after a vote). Drawing the
   focus ring there paints a red box around a heading nobody navigated to,
   which reads as an error state. */
[tabindex]:not([tabindex="-1"]):focus-visible {
  outline: 2px solid var(--seal);
  outline-offset: 2px;
}

/* Headings and panels we focus from script only, to move a screen reader to
   newly revealed content. They are not keyboard-reachable, so a ring around
   them marks nothing the user did -- it just looks like an error box drawn
   around a title. Suppressing it here is safe precisely because nothing can
   Tab to these. */
[tabindex="-1"]:focus {
  outline: none;
}

/* ============================================================== topbar */

.topbar {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  padding: 16px 20px;
  padding-left: max(20px, env(safe-area-inset-left));
  padding-right: max(20px, env(safe-area-inset-right));
}

.topbar-brand {
  display: flex;
  align-items: center;
  text-decoration: none;
  color: var(--ink);
}

/* The mark is a masked shape, not an <img>, so it takes var(--logo-color) and
   follows light/dark without shipping two files. */
.topbar-logo {
  display: block;
  /* 36×36 — cỡ chung của cả repo từ 22/08/2026 (branding/chrome.css). Trước là
     26px, một ngoại lệ riêng của project này; nay không còn ngoại lệ nào. */
  width: 36px;
  height: 36px;
  /* --logo-color, KHÔNG phải --ink-2 (chốt 22/08/2026: một màu mark cho cả
     d4t0.com). Bản trước dùng --ink-2 với lý do "đen tuyền kéo mắt quá" — lý do
     đó vẫn đúng, nhưng nay nằm trong chính token: #293133 là near-black đã hạ
     bớt, không phải #000. Đừng trỏ ngược về --ink-2: mark đổi màu theo chữ thì
     mỗi lần chỉnh bậc chữ là một lần logo đổi theo mà không ai định làm thế. */
  background-color: var(--logo-color);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-image: url("/logo.svg");
  /* KHÔNG xoay ở đây: logo.svg sinh ra từ branding/build-icons.py ĐÃ xoay 45°
     sẵn (LOGO_ROTATE). Thêm rotate(45deg) nữa là ra 90° — đúng lỗi đã dính ở 5
     site khác hôm 21/08. */
  mask-image: url("/logo.svg");
}

.topbar-wordmark {
  color: var(--ink-2);
  text-decoration: none;
  font-size: 14px;
  font-weight: 600;
}

.topbar-wordmark:hover {
  color: var(--ink);
}

/* --- hamburger: 2 bars morphing into an X, Apple.com style ------------ */

.menu-btn {
  position: relative;
  width: 36px;
  height: 36px;
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  padding: 0;
}

.menu-bar {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 16px;
  height: 1.5px;
  background: var(--ink);
  border-radius: 2px;
  /* Centred with margin, not translate(-50%,-50%): combining translate with
     the open state's rotate() puts the pivot off the bar's true centre and
     the X comes out as a lopsided chevron. Known repo gotcha (CLAUDE.md #2). */
  margin: -0.75px 0 0 -8px;
  transition:
    transform var(--dur) var(--ease),
    opacity var(--dur) var(--ease);
}

.menu-bar--top {
  transform: translateY(-3px);
}

.menu-bar--bottom {
  transform: translateY(3px);
}

.menu-btn[aria-expanded="true"] .menu-bar--top {
  transform: rotate(45deg);
}

.menu-btn[aria-expanded="true"] .menu-bar--bottom {
  transform: rotate(-45deg);
}

.menu-panel {
  position: absolute;
  top: 56px;
  right: 20px;
  z-index: 50;
  min-width: 220px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: 0 12px 32px rgb(0 0 0 / 12%);
}

/* Pairs with the [hidden] attribute above -- setting display:flex on a class
   that can also carry [hidden] silently overrides the UA default and makes
   "hidden" elements visible. Known repo gotcha (CLAUDE.md #1). */
.menu-panel[hidden] {
  display: none;
}

.menu-panel-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 10px 12px;
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
}

.menu-panel-item:hover {
  background: var(--paper);
}

.menu-panel-item.active {
  background: var(--seal-soft);
  color: var(--seal);
}

.menu-panel-action {
  color: var(--seal);
}

.menu-panel-divider {
  height: 1px;
  background: var(--line);
  margin: 6px 4px;
}


/* ================================================================ layout */

main {
  flex: 1;
  width: 100%;
  margin: 0 auto;
  /* --sp-4 ở trên chứ không --sp-2: topbar đã có sẵn 16px đệm dưới, cộng 32px
     nữa là 48px — đủ để tiêu đề 26px đậm không dính vào thanh điều hướng. */
  padding: var(--sp-4) 20px var(--sp-6);
  padding-left: max(20px, env(safe-area-inset-left));
  padding-right: max(20px, env(safe-area-inset-right));
}

.col-narrow {
  max-width: 440px;
}

.col-normal {
  max-width: 520px;
}

.col-wide {
  max-width: 720px;
}

.footer {
  /* margin-top:auto pushes it to the bottom of the flex column; auto on the
     sides centres the block itself. Both are needed -- padding alone left the
     text stranded at the far edge of a wide viewport while every other
     element sat in a centred column.
     
     Bề rộng và padding vẫn khớp .topbar và cột của <main> qua .shell--* bên
     dưới — căn giữa không bỏ được luật container: một dòng căn giữa trong footer
     tràn viền, nằm dưới một trang có max-width, sẽ lệch khỏi tâm quang học của
     trang trên màn rộng. */
  margin: auto auto 0;
  width: 100%;
  max-width: 720px;
  background: var(--paper);
  /* Căn giữa (22/08/2026, chung cả repo — branding/chrome.css). Trước là căn
     trái theo luật cũ của CLAUDE.md; luật ấy đã đổi, không còn biến thể trái. */
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  /* 21px = --sp-3, cùng một nấc thang với --s3 mà .vc-foot bên trang tạo dùng,
     để footer hai bên cao bằng nhau (CLAUDE.md "Footer": giống hệt ở mọi
     trang). Trước đây bên này 20px còn bên kia 21/34. */
  padding: var(--sp-3) 20px;
  padding-left: max(20px, env(safe-area-inset-left));
  padding-right: max(20px, env(safe-area-inset-right));
  padding-bottom: calc(var(--sp-3) + env(safe-area-inset-bottom));
}

.footer p {
  margin: 0;
  color: var(--ink-3);
  font-size: 13px;
}

/* :not(.lang-switch) — viên thuốc đổi ngôn ngữ là một CONTROL, không phải chữ
   trong câu footer, nên nó phải giữ màu riêng. Không loại trừ thì `.footer a`
   (0,1,1) đè `.lang-switch` (0,1,0) và viên thuốc lấy luôn màu chữ footer;
   đó là lý do nó vẫn đen đậm dù rule của nó ghi --ink-3. */
.footer a:not(.lang-switch) {
  color: inherit;
  text-decoration: none;
}

.footer a:not(.lang-switch):hover {
  color: var(--ink);
}

/* ============================================================ components */

.btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 52px;
  border-radius: var(--radius-md);
  border: none;
  background: var(--seal);
  color: var(--on-seal);
  font-size: 16px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition:
    transform var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease);
}

.btn-primary:hover:not(:disabled) {
  background: var(--seal-hover);
}

.btn-primary:active:not(:disabled) {
  transform: scale(0.98);
}

.btn-primary:disabled {
  background: var(--line-strong);
  color: var(--ink-3);
  cursor: not-allowed;
}

.btn-inline {
  width: auto;
  height: 44px;
  padding: 0 var(--sp-3);
  white-space: nowrap;
}

.btn-ghost {
  border: none;
  background: transparent;
  color: var(--ink-2);
  font-size: 14px;
  font-weight: 500;
  padding: 8px 4px;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.btn-ghost:hover {
  color: var(--ink);
}

.btn-link {
  border: none;
  background: transparent;
  color: var(--seal);
  font-size: inherit;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
}

.eyebrow {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: var(--sp-2);
}

.page-title {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.015em;
}

.page-sub {
  margin-top: var(--sp-1);
  color: var(--ink-2);
  font-size: 15px;
  line-height: 1.5;
}

/* Kept as a spacer, not a rule: the gap alone separates the question from
   the options, and one less line is one less thing to look at. */

.form-error {
  margin-top: var(--sp-2);
  color: var(--seal);
  font-size: 14px;
}

.form-error[hidden] {
  display: none;
}

.locked-note {
  margin-top: var(--sp-3);
  padding: var(--sp-2);
  background: var(--surface);
  border-radius: var(--radius-md);
  color: var(--ink-2);
  font-size: 14px;
}

/* ========================================================= create screen */

/* =================================== form fields ======================= */

.field {
  display: block;
  margin-top: var(--sp-3);
}

.field-label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
  margin-bottom: 6px;
}

.field input {
  width: 100%;
  height: 48px;
  padding: 0 var(--sp-2);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-md);
  font-size: 16px;
  font-family: var(--font-ui);
  color: var(--ink);
  background: var(--surface);
  outline: none;
}

.field input:focus {
  border-color: var(--ink);
}

/* ======================================================== vote / results */

.poll-question {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.3;
  color: var(--ink);
  /* Paired across the vote -> results swap so the question morphs in place
     instead of the screen cutting. Only one copy is ever in the tree. */
  view-transition-name: poll-question;
}

.poll-meta {
  margin-top: var(--sp-1);
  font-size: 13px;
  color: var(--ink-3);
}

.poll-meta .n {
  font-weight: 600;
  color: var(--ink-2);
}


.panel[hidden] {
  display: none;
}

/* Lá phiếu mượn đúng hình dáng của form lúc tạo: MỘT tấm thẻ, các dòng ngăn
   nhau bằng hairline — thay vì mỗi lựa chọn là một thẻ viền riêng trôi nổi
   cách nhau 8px. Người tạo nhìn thấy form thế nào thì người bỏ phiếu nhìn
   thấy y như vậy; form chính là bản xem trước.
   overflow:hidden để nền của dòng đầu/cuối bị cắt theo góc bo của thẻ. */
.options-list {
  margin-top: var(--sp-4);
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow: hidden;
}

.option-card {
  position: relative;
  display: block;
}

/* A real radio/checkbox, visually hidden -- not a div pretending to be one,
   so keyboard, screen readers and form semantics all keep working.

   Clipped rather than `opacity: 0` (which is what the mockup used): Chrome
   prunes a 1x1 opacity-0 input from the accessibility tree outright, and the
   ballot showed up with no selectable controls at all when checked with a
   real a11y-tree read. Clipping keeps the element rendered -- and therefore
   exposed and focusable -- while taking it off screen. */
.option-card input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.option-card .card-face {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0 var(--sp-2);
  min-height: 52px;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  font-size: 16px;
  transition: background var(--dur-fast) var(--ease);
}

.option-card:last-child .card-face {
  border-bottom: 0;
}

.option-card .dot {
  width: 18px;
  height: 18px;
  border-radius: var(--radius-circle);
  border: 1.5px solid var(--line-strong);
  flex-shrink: 0;
  position: relative;
  transition: border-color var(--dur-fast) var(--ease);
}

.option-card input[type="checkbox"] ~ .card-face .dot {
  border-radius: var(--radius-xs);
}

.option-card .dot::after {
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: var(--radius-circle);
  background: var(--seal);
  transform: scale(0);
  transition: transform var(--dur-fast) var(--ease);
}

.option-card input[type="checkbox"] ~ .card-face .dot::after {
  border-radius: 2px;
}

.option-card input:checked ~ .card-face {
  background: var(--seal-soft);
}

.option-card input:checked ~ .card-face .dot {
  border-color: var(--seal);
}

.option-card input:checked ~ .card-face .dot::after {
  transform: scale(1);
}

/* outline-offset ÂM: thẻ bao ngoài có overflow:hidden, một vòng focus vẽ ra
   phía ngoài sẽ bị cắt cụt ở dòng đầu và dòng cuối. Vẽ thụt vào trong thì
   luôn thấy đủ bốn cạnh. */
.option-card input:focus-visible ~ .card-face {
  outline: 2px solid var(--seal);
  outline-offset: -2px;
}

.sticky-cta {
  position: sticky;
  bottom: calc(var(--sp-2) + env(safe-area-inset-bottom));
  margin-top: var(--sp-4);
}

.live-badge,
.closed-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: var(--sp-2);
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.live-badge {
  color: var(--seal);
}

.closed-badge {
  color: var(--ink-3);
}

.live-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-circle);
  background: var(--seal);
  animation: pulse 1.8s ease-in-out infinite;
}

@keyframes pulse {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.4;
    transform: scale(0.8);
  }
}

/* Khoảng hở này chính là cái ngăn chip số phiếu với bảng kết quả: .panel bao
   ngoài không có padding lẫn border, nên margin ở đây thoát ra ngoài (margin
   collapsing) và trở thành khoảng cách nhìn thấy. --sp-4 chứ không --sp-3:
   24px làm chip dính vào dòng kết quả đầu, và cũng khớp với khoảng hở mà ô
   lựa chọn đang có ở trạng thái chưa bỏ phiếu (33px). */
#results-list {
  margin-top: var(--sp-4);
}

.result-row {
  margin-bottom: var(--sp-2);
}

.result-row .r-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-2);
  margin-bottom: 6px;
  font-size: 15px;
}

.result-row .r-label {
  font-weight: 500;
}

.result-row.mine .r-label::after {
  content: " ✓";
  color: var(--success);
}

.result-row .r-stat {
  font-size: 13px;
  color: var(--ink-2);
  white-space: nowrap;
  /* Bỏ .mono nhưng GIỮ tabular-nums: cái giữ các con số thẳng cột là thuộc
     tính này chứ không phải họ chữ mono. Mono chỉ thêm phần "trông như đoạn
     code" -- số 0 gạch chéo của SF Mono nằm giữa chữ "phiếu" là rõ nhất. */
  font-variant-numeric: tabular-nums;
}

.result-row .r-stat .pct {
  font-weight: 700;
  color: var(--ink);
  /* 4px là quá sát: "1 phiếu" và "100%" dính thành một khối chữ khó tách. */
  margin-left: 10px;
}

.bar-track {
  height: 10px;
  background: var(--line);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

/* Thanh có chuyển động, nhưng phải là loại không cướp sự chú ý: một vệt sáng
   mảnh trượt chậm qua phần đã tô. Chỉ transform chạy (translateX của ::after),
   nên GPU lo, không layout lại; và vệt sáng nằm trong overflow:hidden của
   track nên không bao giờ tràn ra ngoài bo góc.
   Thanh 0% không có gì để sáng, nên tắt hẳn — nếu không sẽ có một vệt nhấp
   nháy trên một thanh rỗng, trông như lỗi. */
.bar-fill {
  position: relative;
  height: 100%;
  background: var(--seal);
  border-radius: var(--radius-pill);
  overflow: hidden;
  transition: width 700ms cubic-bezier(0.22, 0.68, 0.16, 1);
}

.bar-fill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    transparent 20%,
    rgb(255 255 255 / 28%) 50%,
    transparent 80%
  );
  transform: translateX(-100%);
  animation: bar-sheen 2600ms ease-in-out infinite;
}

.bar-fill[style*="width:0%"]::after,
.bar-fill[style*="width: 0%"]::after {
  display: none;
}

@keyframes bar-sheen {
  0% {
    transform: translateX(-100%);
  }
  /* Nghỉ hơn nửa chu kỳ giữa hai lần quét: quét liên tục thành sọc chạy, gây
     rối; quét thưa thì mắt chỉ thấy thanh "còn sống". */
  60%,
  100% {
    transform: translateX(100%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .bar-fill::after {
    animation: none;
  }
}


/* Icon trong nút: cỡ theo chữ, màu theo currentColor nên tự đúng ở cả hai
   theme và cả trạng thái .pill-btn--danger. */
.btn-ico {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.pill-btn {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 44px;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
}

.pill-btn:hover {
  border-color: var(--line-strong);
}

/* ============================================================= dashboard */

/* Chỉ còn tiêu đề sau khi gỡ nút "+ Bình chọn mới" — không cần flex hai cột
   nữa. Điều hướng tới trang tạo đã có sẵn ở navbar. */
.dash-head {
  margin-bottom: var(--sp-4);
}

.dash-head h1 {
  font-size: 22px;
  font-weight: 700;
}

.poll-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* Thẻ hai dòng: trên là danh tính poll (câu hỏi + trạng thái + đồng hồ), dưới
   là số liệu và hành động. Bản một dòng nhồi bảy thứ cạnh nhau và câu hỏi bị
   cắt còn vài chữ — thứ quan trọng nhất của mỗi thẻ lại là thứ hẹp nhất. */
.poll-item {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-2);
  /* --sp-3 chứ KHÔNG --sp-1: khe giữa hai thẻ phải lớn hơn mọi khe bên trong
     một thẻ, nếu không thì luật gần-xa bị đảo và mắt gom cả danh sách thành
     một dải liền. Ở --sp-1 thì khe giữa hai poll khác nhau là 8px trong khi
     khe từ tiêu đề xuống chip meta của CHÍNH nó đã là 16.3px -- tức hai thẻ
     dính nhau hơn là nội dung của mỗi thẻ dính với nhau. */
  margin-bottom: var(--sp-3);
  border-radius: var(--radius-md);
  background: var(--surface);
  /* Hairline BẮT BUỘC, không phải trang trí: --surface chỉ chênh --paper
     1.009:1, tức nền không tự vẽ ra được cái thẻ. Trước đây --surface là
     #ffffff trên #fafafa (1.024:1) nên còn thấy lờ mờ; sau khi lấy bảng màu
     branding thì chênh lệch giảm một nửa và thẻ biến mất hẳn. Dùng viền chứ
     không dùng bóng: .options-list và các nút quanh đây đều vẽ bằng --line,
     thêm một lớp bóng là thêm ngôn ngữ thứ hai. */
  border: 1px solid var(--line);
}

/* Thẻ là LƯỚI HAI CỘT ở desktop: cột trái xếp tiêu đề trên chip meta, cột phải
   là cụm nút trải suốt hai hàng.

   Trước đây nút nằm chung hàng với tiêu đề (.p-top là một flex row), và vì cụm
   nút cao 34px còn dòng tiêu đề chỉ 17.5px nên hàng đó bị kéo cao lên, đẩy chip
   meta ra xa tiêu đề: nhịp cột trái thành 13.5 / 24.5 / 14 -- khe giữa tiêu đề
   và chính metadata của nó lại rộng gần gấp đôi lề trên và lề dưới.
   Tách cột thì chiều cao của nút không còn dính gì tới khoảng cách tiêu
   đề-meta, và nhịp về 13.5 / 8 / 14. */
.poll-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  column-gap: var(--sp-2);
}

/* .p-top chỉ còn là cái bọc trong HTML, không còn vai trò bố cục. */
.p-top {
  display: contents;
}

.p-top .p-q {
  grid-column: 1;
  grid-row: 1;
}

.poll-item .p-meta {
  grid-column: 1;
  grid-row: 2;
}

.poll-item .row-actions {
  grid-column: 2;
  grid-row: 1 / span 2;
  /* center chứ không để mặc định (start theo align-items của lưới): cụm nút
     trải suốt hai hàng nhưng chỉ cao 34.5px, trong khi cột trái (tiêu đề +
     chip meta) cao 43.5px. Dính lên đỉnh thì đỉnh khớp nhau nhưng ĐÁY lệch
     9.5px, và cả cụm trông như trôi lên so với khối chữ bên cạnh. */
  align-self: center;
}

/* Câu hỏi ăn hết chỗ thừa; nhóm nút bám phải. */
.p-top .p-q {
  flex: 1 1 auto;
}

/* MỘT dòng meta duy nhất: trạng thái · số phiếu · đồng hồ. Chữ nhỏ và nhạt —
   đây là chú thích cho câu hỏi ở trên, không phải nội dung ngang hàng với nó.
   Không viên thuốc, không nền riêng: ba mẩu cùng cấp thì trông cùng cấp. */
.p-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 6px;
  font-size: 13px;
  color: var(--ink-3);
}

/* Cùng màu chữ meta. --line-strong chỉ đạt 1.48:1 — mờ tới mức không đọc ra
   là một dấu ngăn, mà nó lại là thứ duy nhất tách ba mẩu thông tin. */
/* Gạch dọc mảnh thay cho dấu chấm giữa: cùng vai trò ngăn cách nhưng không
   trôi theo baseline của font như ký tự "|", và cao đúng bằng chữ. */
.p-sep {
  width: 1px;
  height: 10px;
  flex-shrink: 0;
  /* Màu trung tính chứ KHÔNG currentcolor: chip đang mở tô chữ xanh, và vạch
     ngăn thừa hưởng màu đó thì nó nhuốm xanh trong khi nhiệm vụ của nó chỉ là
     ngăn ba mẩu chữ khác màu nhau. */
  background: var(--ink-3);
  opacity: 0.4;
}

/* Nền chip dùng chung cho cả hai mẩu meta, để chúng trông cùng loại. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  background: var(--fill);
  white-space: nowrap;
}

/* Trước đây chip này phải tự ghi đè nền đậm hơn .chip một nấc, vì nó nằm trên
   nền trang còn chip bảng điều khiển nằm trên thẻ trắng. Với bảng màu branding
   thì --fill đọc rõ trên cả hai (1.089 và 1.096), nên cái ghi đè đó biến mất.
   Chỉ còn lại phần chữ, và gap thì cứ để nguyên 5px của .chip -- KHÔNG đặt
   gap:0 rồi trông chờ dấu cách trong chuỗi, vì flex container cắt sạch khoảng
   trắng giữa các item và "0" sẽ dính liền vào "người". */
.poll-meta .poll-count {
  color: var(--ink-2);
}

.p-status {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

/* Chấm màu thay cho viên thuốc: đủ để phân biệt mở/đóng, không cần một khối
   nền để nói điều đó. */
.p-status .dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-circle);
  background: currentcolor;
}

.p-status.open {
  color: var(--success);
}

.p-status.closed {
  color: var(--ink-2);
}

.poll-item .p-q {
  min-width: 0;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.poll-item .p-q:hover {
  color: var(--seal);
}

/* Bề rộng tối thiểu chung cho cả hai trạng thái: "Đã đóng" dài hơn "Đang mở",
   và nếu ô co theo chữ thì hàng poll đã đóng lệch hết các cột phía sau. */
.status-pill {
  min-width: 76px;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}

.status-pill.open {
  background: var(--success-soft);
  color: var(--success);
}

.status-pill.closed {
  background: var(--line);
  color: var(--ink-2);
}

.p-date {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Số phiếu là con số đáng nhìn nhất trong hàng meta, nên nó — và chỉ nó —
   được một cái chip. Không dùng font mono: mono giữa một câu tiếng Việt đọc
   ra như đoạn code; tabular-nums mới là thứ thật sự cần, để con số không nhảy
   bề ngang khi đếm tăng. */
/* Nền lấy từ .chip. KHÔNG dùng --paper: #fafafa trên thẻ trắng là chênh 2%,
   mắt không thấy có chip nào cả. */
.p-votes {
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.empty-state {
  margin-top: var(--sp-6);
  text-align: center;
}

.empty-title {
  font-size: 17px;
  font-weight: 600;
}

.empty-sub {
  margin: var(--sp-1) 0 var(--sp-3);
  color: var(--ink-2);
  font-size: 14px;
}

/* ============================== the seal -- the one signature moment ==== */

.seal-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 100;
  opacity: 0;
}

.seal-overlay.playing {
  opacity: 1;
}

.seal-overlay svg {
  width: 120px;
  height: 120px;
  opacity: 0;
}

.seal-overlay.playing svg {
  animation: seal-stamp 550ms var(--ease) forwards;
}

.seal-ring,
.seal-check {
  stroke: var(--seal);
}

@keyframes seal-stamp {
  0% {
    transform: scale(2.6) rotate(-18deg);
    opacity: 0;
  }
  55% {
    transform: scale(0.92) rotate(-6deg);
    opacity: 1;
  }
  75% {
    transform: scale(1.04) rotate(-8deg);
    opacity: 1;
  }
  100% {
    transform: scale(1) rotate(-8deg);
    opacity: 0;
  }
}

.toast {
  position: fixed;
  left: 50%;
  bottom: var(--sp-4);
  transform: translateX(-50%);
  z-index: 120;
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: var(--paper);
  font-size: 14px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 20%);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }

  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* .p-date used to be hidden on narrow screens when it was a creation date --
   pure trivia. It now carries the delete-in countdown, which is the single
   most important thing about a row: the poll is going away. It stays. */

/* ================================== dashboard row actions ============== */

/* These were icon-only (⧉ ⊘ ✕) and nobody could be expected to know which
   glyph meant what -- "close the poll" and "delete the poll" in particular
   are one careless tap apart. Words, not glyphs. */
.text-btn {
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 500;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
}

.text-btn:hover {
  background: var(--line);
  color: var(--ink);
}

.text-btn--danger:hover {
  background: var(--seal-soft);
  color: var(--seal);
}

/* ============================ touch / iOS tuning ======================= */

/* iOS Safari zooms the whole page when a focused control's text is under
   16px, and then leaves it zoomed -- the create form was jumping on every
   tap into an option field. 16px everywhere is the only reliable fix; the
   visual difference from 15px is negligible. */
.option-row input[type="text"],
.adv-row select,
#bulk-input,
.field input,
#username,
#password {
  font-size: 16px;
}

@media (pointer: coarse) {
  /* 44px is Apple's minimum comfortable target. Applied only for touch
     input so the desktop layout keeps its tighter rhythm. */
  .text-btn,
  .btn-ghost,
  .btn-link {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  /* Apple asks for 44pt in both axes, and a short label like "Xoá" otherwise
     ends up a 41px-wide target. */
  .text-btn {
    min-width: 44px;
    justify-content: center;
  }

  .remove-option {
    width: 44px;
    height: 44px;
  }

  .menu-btn {
    width: 44px;
    height: 44px;
  }

  /* Câu hỏi là lối chính vào poll từ bảng điều khiển, nên nó cũng cần 44px.
     KHÔNG dùng min-height: hộp chữ một dòng chỉ cao 17.5px, ép lên 44px là
     chèn 13px khí chết trên và dưới — thẻ padding đều 16px mà nhìn thành 29
     trên / 16 dưới, và tiêu đề trôi lơ lửng. Lớp phủ ::after cho ngón tay đủ
     44px mà hộp nhìn thấy vẫn ôm sát chữ (cùng mẫu với logo/viên thuốc dưới
     cuối file). */
  .poll-item .p-q {
    position: relative;
  }

  .poll-item .p-q::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    height: 44px;
    margin-top: -22px;
  }

  /* The panel is a tap-through menu on touch, so give its rows real height. */
  .menu-panel-item {
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  /* The logo is a 24px mark but it is also the only way back to the hub, so
     give it a real target without changing how big the mark looks. */
  .topbar-brand {
    min-height: 44px;
    min-width: 44px;
    margin: -10px 0 -10px -10px;
    padding: 10px;
  }

  /* Same for the wordmark on the voter page's minimal header. */
  .topbar-wordmark {
    min-height: 44px;
    display: flex;
    align-items: center;
    margin: -10px -10px -10px 0;
    padding: 0 10px;
  }

  /* A <summary> is only as tall as its text (17px here). */
  details.advanced summary,
  .gate-existing summary {
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  .gate-existing summary {
    justify-content: center;
  }

  /* Kill the grey flash iOS paints over any tapped element -- these all have
     their own :hover/:active treatment already. */
  a,
  button,
  label,
  summary {
    -webkit-tap-highlight-color: transparent;
  }
}

/* The credentials dialog must never exceed the screen: on a 393x852 iPhone
   with the keyboard up there is very little room, and its "Tiếp tục" button
   is the only way forward. */
.saved-card {
  max-height: calc(100dvh - 2 * var(--sp-2) - env(safe-area-inset-bottom));
  overflow-y: auto;
}

.saved-overlay {
  padding-bottom: calc(var(--sp-2) + env(safe-area-inset-bottom));
}

/* Long option labels and pasted lines must wrap rather than force the page
   into a horizontal scroll -- a poll option can be a whole sentence. */
.option-card .card-face span:last-child,
.result-row .r-label,
.poll-item .p-q {
  overflow-wrap: anywhere;
}

@media (max-width: 680px) {
  /* Thang khoảng cách siết lại một bậc trên màn hình hẹp — SAO CHÉP đúng
     breakpoint và đúng giá trị mà .vc dùng bên create.css, để footer và mọi
     nhịp dọc của hai trang không lệch nhau trên điện thoại. Nhịp vẫn là
     Fibonacci, chỉ dịch xuống: 8/10/16/21. */
  :root {
    --sp-2: 10px;
    --sp-3: 16px;
    --sp-4: 21px;
  }
}

@media (max-width: 430px) {
  /* iPhone 14 Pro (393px) và Pro Max (430px). */

  /* Mép ngang hẹp lại để lọt thêm chữ: 20px x2 là 40px trong tổng 393px, tức
     một phần mười bề ngang màn hình chỉ để làm lề. 14px vẫn đủ thoáng và vẫn
     tôn trọng safe-area ở máy có tai thỏ khi xoay ngang. */
  .topbar,
  .footer,
  main {
    padding-left: max(14px, env(safe-area-inset-left));
    padding-right: max(14px, env(safe-area-inset-right));
  }

  /* The results row put label and "N phiếu NN%" on one line, which collided
     once labels got long. */
  .result-row .r-top {
    flex-wrap: wrap;
    gap: 2px var(--sp-1);
  }

  .poll-question {
    font-size: 23px;
  }

  #q-input {
    font-size: 23px;
  }

  .poll-actions-row .pill-btn {
    flex: 1 1 140px;
  }

  .quick-actions {
    flex-wrap: wrap;
  }

  .quick-actions .btn-primary,
  .quick-actions .pill-btn {
    flex: 1 1 100%;
  }

  .cred-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }

  .cred-row dd {
    text-align: left;
  }
}

/* ======================= desktop: nav inline, no hamburger ============= */

/* Above the breakpoint the hamburger disappears and the panel becomes a plain
   horizontal row. Requested for this project on 19/08/2026, back when it was a
   deviation from the repo default; repo-wide since 22/08/2026, which is why the
   breakpoint moved 768 -> 720 to match branding/chrome.css. Pure CSS -- the
   button and panel keep their ids, so menu.js needs no change and the mobile
   behaviour below the breakpoint is untouched.

   The [hidden] override is mandatory, not defensive: the panel ships with the
   hidden attribute set, and a bare `display:flex` here would lose to the UA's
   [hidden]{display:none} on first paint. This is gotcha #1 in CLAUDE.md and
   it has bitten this repo more than once. */
@media (min-width: 720px) {
  .menu-btn {
    display: none;
  }

  .menu-panel,
  .menu-panel[hidden] {
    display: flex;
    position: static;
    flex-direction: row;
    align-items: center;
    gap: 2px;
    min-width: 0;
    padding: 0;
    background: transparent;
    border: none;
    box-shadow: none;
  }

  .menu-panel-item {
    width: auto;
    padding: 8px 12px;
    white-space: nowrap;
  }

  .menu-panel-divider {
    width: 1px;
    height: 20px;
    margin: 0 6px;
    background: var(--line);
  }
}

/* ========================= created / share-link card =================== */

.created-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-2);
  padding-bottom: calc(var(--sp-2) + env(safe-area-inset-bottom));
  background: rgb(0 0 0 / 45%);
  animation: overlay-in 180ms var(--ease);
}

.created-overlay[hidden] {
  display: none;
}

@keyframes overlay-in {
  from {
    opacity: 0;
  }
}

.created-card {
  /* Mốc định vị cho dấu X ở góc. */
  position: relative;
  width: 100%;
  max-width: 420px;
  max-height: calc(100dvh - 2 * var(--sp-2) - env(safe-area-inset-bottom));
  overflow-y: auto;
  padding: var(--sp-4) var(--sp-3) var(--sp-3);
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px rgb(0 0 0 / 30%);
  text-align: center;
  animation: card-in 240ms var(--ease);
}

@keyframes card-in {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.97);
  }
}

/* A quiet tick, not the red seal. The seal is reserved for the moment a
   vote is cast (MASTER-PLAN §4: one accent, and nothing else competes with
   it) -- reusing it here would spend that moment on the creator instead of
   on the voter. */
/* Dấu X ở góc thẻ. position:absolute nên nó không đẩy tiêu đề lệch khỏi
   giữa — thẻ này vốn căn giữa toàn bộ nội dung. */
.created-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  color: var(--ink-3);
  border-radius: var(--radius-md);
}

.created-close:hover {
  color: var(--ink);
  background: var(--paper);
}

.created-close svg {
  width: 18px;
  height: 18px;
}

.created-check {
  width: 52px;
  height: 52px;
  margin: 0 auto var(--sp-2);
  border-radius: var(--radius-circle);
  background: var(--success-soft);
  color: var(--success);
  display: flex;
  align-items: center;
  justify-content: center;
}

.created-check svg {
  width: 26px;
  height: 26px;
  stroke-dasharray: 30;
  stroke-dashoffset: 30;
  animation: tick-draw 420ms var(--ease) 120ms forwards;
}

@keyframes tick-draw {
  to {
    stroke-dashoffset: 0;
  }
}

.created-card h2 {
  font-size: 21px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.created-sub {
  margin-top: 6px;
  font-size: 14px;
  color: var(--ink-2);
}

/* The link is the button. One large target, so on a phone there is no small
   icon to aim at -- the whole row copies. */
.link-copy {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  width: 100%;
  min-height: 56px;
  margin-top: var(--sp-3);
  padding: 0 var(--sp-2);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--paper);
  color: var(--ink);
  font-size: 15px;
  cursor: pointer;
  text-align: left;
  transition:
    border-color var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease),
    transform var(--dur-fast) var(--ease);
}

.link-copy:hover {
  border-color: var(--line-strong);
}

.link-copy:active {
  transform: scale(0.985);
}

.link-copy-url {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
}

.link-copy-icon {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  position: relative;
  color: var(--ink-3);
}

.link-copy-icon svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transition:
    opacity var(--dur-fast) var(--ease),
    transform var(--dur-fast) var(--ease);
}

.link-copy-icon .ico-done {
  opacity: 0;
  transform: scale(0.6);
}

/* Copied: the control itself reports the result. */
.link-copy.is-copied {
  border-color: var(--success);
  background: var(--success-soft);
}

.link-copy.is-copied .link-copy-icon {
  color: var(--success);
}

.link-copy.is-copied .ico-copy {
  opacity: 0;
  transform: scale(0.6);
}

.link-copy.is-copied .ico-done {
  opacity: 1;
  transform: scale(1);
}

.link-copy.is-failed {
  border-color: var(--seal);
  background: var(--seal-soft);
}

.link-copy-hint {
  margin-top: 8px;
  font-size: 12px;
  color: var(--ink-3);
  min-height: 1.4em;
  transition: color var(--dur-fast) var(--ease);
}

.link-copy-hint.ok {
  color: var(--success);
  font-weight: 600;
}

.link-copy-hint.warn {
  color: var(--seal);
}

/* Announced to screen readers only -- the colour change and icon swap say
   nothing to somebody who cannot see them.
   .sr-only dùng chung cách ẩn đó cho nội dung tĩnh (ví dụ <h1> của trang tạo,
   xem templates.ts) — clip chứ không display:none, vì display:none là biến
   mất khỏi cả cây a11y lẫn mắt công cụ tìm kiếm. */
.sr-live,
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Một hàng thay vì xếp chồng: hai nút cùng cấp, và dialog này vốn đã dài —
   trên iPhone nó phải cuộn. Dưới 360px thì tự xuống dòng qua flex-wrap. */
.created-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  margin-top: var(--sp-3);
}

/* flex-shrink:0 is load-bearing: .created-card is a scroll container with a
   max-height, and in a column flex layout that lets the browser shrink these
   buttons along the main axis -- "Xem bình chọn" collapsed to 19px tall on a
   393px viewport. min-height rather than height so a wrapped label still
   grows the button instead of overflowing it. */
.created-actions .btn-primary,
.created-actions .pill-btn {
  flex: 1 1 140px;
  width: auto;
  height: auto;
  min-height: 48px;
}

.created-again {
  margin-top: var(--sp-3);
  font-size: 13px;
  color: var(--ink-3);
  text-decoration: none;
}

.created-again:hover {
  color: var(--ink-2);
  text-decoration: underline;
}

@media (prefers-reduced-motion: reduce) {
  .created-overlay,
  .created-card,
  .created-check svg {
    animation: none;
  }

  .created-check svg {
    stroke-dashoffset: 0;
  }
}

/* ================================ loading buttons ====================== */

.btn-primary.is-loading,
.pill-btn.is-loading,
.btn-ghost.is-loading {
  gap: 10px;
  cursor: progress;
}

.spinner {
  /* inline-block, KHÔNG để mặc định inline: .vc-cta là display:block nên con
     của nó là inline, mà inline thì bỏ qua width/height — vòng tròn co lại
     còn đúng hai đường viền 2px và hiện ra như một chữ "I". Nút ở trang bình
     chọn là inline-flex nên không dính, và đó là lý do lỗi chỉ thấy ở nút
     "Tạo bình chọn". */
  display: inline-block;
  vertical-align: -2px;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  border-radius: var(--radius-circle);
  border: 2px solid currentColor;
  border-top-color: transparent;
  opacity: 0.9;
  animation: spin 640ms linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* A disabled primary button loses its white text, so the ring needs the
   muted colour to stay visible against the grey fill. */
.btn-primary:disabled .spinner {
  border-color: var(--ink-3);
  border-top-color: transparent;
}

@media (prefers-reduced-motion: reduce) {
  .spinner {
    /* Still moves -- a frozen spinner says "stuck", which is the opposite of
       the message. Just slow enough not to be a strobe. */
    animation-duration: 1.8s;
  }
}

/* ================================= inline auth gate ==================== */

.auth-gate-card {
  text-align: left;
}

.auth-gate-card h2 {
  font-size: 19px;
  text-align: center;
}

.auth-gate-card .created-sub {
  text-align: center;
}

.auth-gate-card .quick-warn {
  margin-top: var(--sp-2);
}

.gate-existing {
  margin-top: var(--sp-3);
  border-top: 1px solid var(--line);
  padding-top: var(--sp-2);
}

.gate-existing summary {
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-2);
  list-style: none;
  text-align: center;
}

.gate-existing summary::-webkit-details-marker {
  display: none;
}

.gate-existing summary:hover {
  color: var(--ink);
}

.gate-login-btn {
  margin-top: var(--sp-3);
}

/* Credentials handed back on the success card. Amber, not red: nothing has
   gone wrong, but this is the one thing on screen that cannot be recovered
   if it is ignored. */

/* ================================= the two clocks ====================== */

/* Expiry and closing are different events and must never read as one number.
   The expiry clock is the louder of the two -- it is the one where something
   disappears. */
.clocks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  margin-top: var(--sp-2);
}

.clock {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  font-size: 12px;
}

.clock-label {
  color: var(--ink-3);
}

/* Không dùng font mono ở đây, dù thẻ vẫn mang class .mono cho các chỗ khác:
   bản tiếng Việt giờ đọc là "6 ngày, 23 giờ" — một cụm từ, không phải một mã
   dữ liệu, và chữ mono biến nó thành đoạn code giữa câu (§3.2). tabular-nums
   vẫn giữ, đó mới là thứ cần thiết: chữ số cùng bề rộng nên đồng hồ đếm lùi
   không giật qua lại mỗi giây. */
.clock-value {
  font-family: var(--font-ui);
  font-weight: 600;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.clock--expiry .clock-value {
  color: var(--ink);
}

.clock.is-urgent .clock-value {
  color: var(--seal);
}

.clock.is-done .clock-value {
  color: var(--ink-3);
  font-weight: 500;
}

/* ================================== manage panel ======================= */

/* Chia sẻ + quản lý trong MỘT khối, ngăn bằng một hairline. Bản cũ là hàng
   chia sẻ nằm lọt trong panel kết quả cộng một thẻ nền riêng cho phần quản lý,
   tức thẻ lồng thẻ và hai mức viền cho hai việc cùng cấp. */
/* Phân tầng: lá phiếu / kết quả là việc chính của trang, mọi thứ dưới đây là
   việc phụ (chia sẻ, quản lý). Tách bằng KHOẢNG TRỐNG chứ không bằng thêm
   một đường kẻ hay một khung nữa — trang này vốn đi theo hướng ít nét.
   --sp-6 gần gấp đôi khoảng cách giữa các khối cùng cấp, đủ để mắt đọc ra
   "hết phần chính" mà không thành một cú ngắt. */
.poll-actions {
  margin-top: var(--sp-6);
}

/* Nút phụ nhẹ hơn nút chính một bậc: chữ nhạt hơn, viền nhạt hơn. Nút "Gửi
   bình chọn" là thứ duy nhất trên trang được tô đặc, nên nó vẫn là điểm nặng
   nhất dù các nút này nằm ngay dưới. */
.poll-actions-row .pill-btn {
  color: var(--ink-2);
}

.poll-actions-row .pill-btn:hover {
  color: var(--ink);
}

.poll-actions-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
}

/* flex-basis 0 chứ không auto: hai nút chia đôi hàng bằng nhau thay vì rộng
   theo độ dài nhãn ("Sao chép liên kết" dài gấp đôi "Chia sẻ"). Khi nút chia
   sẻ bị ẩn (desktop không có Web Share API) thì nút còn lại tự chiếm trọn. */
.poll-actions-row .pill-btn {
  flex: 1 1 0;
  min-height: 44px;
}

/* Không kẻ vạch ngăn: nút đóng/mở ngay bên dưới đã tự tách hai phần bằng
   khoảng trống và bằng chính hình dáng của nó. Thêm một đường kẻ nữa chỉ là
   thêm nét cho một trang vốn muốn ít nét. */
.poll-actions-owner {
  margin-top: var(--sp-2);
}

.poll-actions-label {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-2);
}

/* Phần chỉ-chủ-sở-hữu đóng lại mặc định: hai nút ở đây là đóng và XOÁ, không
   phải thứ cần phơi ra mỗi lần mở trang. Cùng kiểu đóng/mở với "Tuỳ chọn" ở
   trang tạo — một nút, một chevron xoay. */
.owner-toggle {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  text-align: left;
}

.owner-toggle .chev {
  color: var(--ink-3);
  transition: transform var(--dur-fast) var(--ease);
}

.owner-toggle[aria-expanded="true"] .chev {
  transform: rotate(180deg);
}

/* grid-template-rows 0fr -> 1fr: mở ra mượt mà không cần biết trước chiều cao,
   thứ mà max-height không làm được nếu không đoán một con số. */
.owner-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur) var(--ease);
}

.owner-body > .owner-body-inner {
  overflow: hidden;
  min-height: 0;
}

.owner-toggle[aria-expanded="true"] + .owner-body {
  grid-template-rows: 1fr;
}

.owner-body-inner > .poll-actions-row {
  padding-top: var(--sp-1);
}

/* Nút này đứng riêng một hàng, không xếp cùng "Đóng"/"Xoá": nó là hành động
   an toàn và là thứ người tạo cần nhất (mất liên kết quản lý là mất poll),
   nên không nên nằm sát một nút xoá. */
.owner-copy {
  width: 100%;
  margin-top: var(--sp-2);
  min-height: 44px;
}

/* Ba dòng meta (trực tiếp / số phiếu / đồng hồ) về cùng một hàng, xuống dòng
   khi hẹp. Trước đây mỗi thứ một dòng, chiếm ba dòng cho ba mẩu chữ ngắn. */
.poll-meta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-2);
  margin-top: var(--sp-2);
}

.poll-meta-row > * {
  margin: 0;
}

.pill-btn--danger {
  color: var(--seal);
  border-color: var(--seal-soft);
}

.pill-btn--danger:hover {
  background: var(--seal-soft);
  border-color: var(--seal);
}

.manage-note {
  margin-top: var(--sp-1);
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink-3);
}

/* ============================ manage link (share card) ================= */

/* Trước đây khối này mang một vạch hổ phách #d97706 bên trái và tiêu đề
   #b45309 — hai mã màu viết cứng, không nằm trong bảng màu, và không xuất hiện
   ở bất kỳ chỗ nào khác của app. Nó cũng đọc ra như một cảnh báo lỗi trong khi
   đây là màn hình thành công. Giờ dùng đúng thẻ trung tính như mọi khối khác;
   mức độ quan trọng để cho chữ nói, không để màu hét. */
.manage-link {
  margin-top: var(--sp-3);
  padding: var(--sp-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  text-align: left;
}

.manage-link[hidden] {
  display: none;
}

.manage-link-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}

.manage-link-url {
  margin-top: 6px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink);
  word-break: break-all;
}

.manage-link .pill-btn {
  width: 100%;
  min-height: 40px;
  margin-top: var(--sp-1);
}

.manage-link-note {
  margin-top: 8px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink-3);
}

.created-expiry {
  margin-top: var(--sp-2);
  font-size: 12px;
  color: var(--ink-3);
}

.created-expiry[hidden] {
  display: none;
}

/* ================================== locked poll ======================== */

.locked-card {
  text-align: center;
  padding-top: var(--sp-6);
}

.locked-icon {
  width: 56px;
  height: 56px;
  margin: 0 auto var(--sp-3);
  border-radius: var(--radius-circle);
  background: var(--surface);
  color: var(--ink-2);
  display: flex;
  align-items: center;
  justify-content: center;
}

.locked-icon svg {
  width: 26px;
  height: 26px;
}

.locked-card h1 {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.locked-card .page-sub {
  max-width: 34ch;
  margin-inline: auto;
}

.locked-card .field {
  text-align: left;
}

.locked-card .btn-primary {
  margin-top: var(--sp-3);
}

.locked-expiry {
  margin-top: var(--sp-4);
  font-size: 12px;
  color: var(--ink-3);
}

/* ================================ dashboard note ======================= */

.dash-note {
  margin: calc(var(--sp-4) * -1 + var(--sp-1)) 0 var(--sp-3);
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink-3);
}

.poll-list[hidden],
.empty-state[hidden] {
  display: none;
}

/* ============================ advanced: stacked row ==================== */

/* The password field needs a full-width input, which the label-left/control-
   right rhythm of .adv-row cannot give it. */

/* ========================= dashboard rows: narrow layout =============== */

/* Restored deliberately: this block was lost when the account-era styles
   were cut out around it, which left the question column collapsing to zero
   width on any viewport under the full 720px column.
   700px, not a phone breakpoint: three labelled buttons need ~240px, and
   below the full column they squeeze the question down to nothing. */
@media (max-width: 700px) {
  /* Thẻ đã là hai dòng ở mọi bề rộng, nên ở đây chỉ cần cho từng dòng tự
     xuống hàng khi hết chỗ — không dựng lại layout. */
  /* display:contents làm .p-top biến mất khỏi lưới bố cục, để câu hỏi và nhóm
     nút thành con trực tiếp của .poll-item — nhờ đó order xếp lại được ba khối
     mà không phải đổi cấu trúc HTML: tiêu đề, rồi meta, rồi nút.
     Trên điện thoại nút là thứ chạm vào, nên nó nằm dưới cùng — gần ngón cái
     và sau khi đã đọc xong poll đó là poll nào. */
  /* Trả .poll-item về flex một cột: lưới hai cột ở trên đặt grid-column /
     grid-row tường minh, mà order chỉ xếp lại được khi các khối cùng nằm trong
     MỘT dòng chảy. Không gỡ các đặt chỗ đó thì nút vẫn dính cột phải và
     order:3 không có tác dụng gì. */
  .poll-item {
    display: flex;
    flex-direction: column;
    /* stretch, KHÔNG kế thừa align-items:start của lưới desktop: trong flex
       column, "start" làm mỗi khối co lại vừa nội dung và dạt trái, nên cụm
       nút hết trải hết bề ngang và justify-content:flex-end của nó không còn
       gì để đẩy về phải. */
    align-items: stretch;
  }

  .p-top .p-q,
  .poll-item .p-meta,
  .poll-item .row-actions {
    grid-column: auto;
    grid-row: auto;
  }

  /* align-self:center của lưới desktop phải gỡ luôn: ở flex column thì trục
     chéo là NGANG, nên nó sẽ kéo cụm nút vào giữa thẻ thay vì để sát mép phải. */
  .poll-item .row-actions {
    align-self: stretch;
  }

  .p-top {
    display: contents;
  }

  .poll-item .p-q {
    order: 1;
    white-space: normal;
  }

  .p-meta {
    order: 2;
  }

  .poll-item .row-actions {
    order: 3;
    margin-top: var(--sp-1);
  }

  /* Nút xếp dọc (icon trên, nhãn dưới) nên chỉ siết đệm NGANG ở màn hình
     hẹp; đệm dọc giữ nguyên để ô không lùn hơn 44px. */
  .row-actions .text-btn {
    padding: 8px 2px;
  }

  /* Trên mobile cụm nút xuống dòng riêng, nên chỗ trống phía TRÊN nó lại là
     thứ tách nó khỏi hàng meta -- giữ lại. Chỉ chiều dưới mới là chỗ bị cộng
     nhầm vào padding đáy thẻ. */
  .row-actions {
    margin-top: 0;
  }
}

/* ======================= bulk option entry (restored) ================== */

/* This block was lost when the account-era styles were cut out around it --
   the "Nhập nhanh" panel rendered completely unstyled. Restored with the
   accessible control border rather than the decorative hairline. */

/* ===================== other rules lost in the same cut ================ */

.topbar--minimal {
  justify-content: space-between;
}

/* Turnstile only ever renders into an off-screen container (turnstile.js), so
   this slot exists purely to carry data-sitekey. */
.turnstile-slot,
.turnstile-slot[hidden] {
  display: none;
}

.result-row .r-count {
  font-variant-numeric: tabular-nums;
}

/* Paired with .clock--expiry; the closing clock is the quieter of the two. */
.clock--closing .clock-value {
  color: var(--ink-2);
}

/* The switch's visible track is 40x24 -- deliberately, it is a switch -- but
   the thing a finger has to hit should still be 44pt tall (Apple HIG). The
   padding extends the hit area without moving the track. */
@media (pointer: coarse) {
  .switch {
    height: 44px;
    display: flex;
    align-items: center;
  }

  .switch-track {
    top: 50%;
    bottom: auto;
    margin-top: -12px;
    height: 24px;
  }
}

/* ============================== language switch ======================== */

/* Moved out of the nav panel and into the footer (requested). A bordered pill
   rather than a bare link: it is the one control down here, and without an
   outline it reads as more footer text rather than something to press.
   This is the repo's flag-plus-label convention (CLAUDE.md "Nav menu"),
   relocated rather than redesigned.
   Bản redesign đổi sang cặp VI/EN không cờ với lập luận "cờ đại diện quốc gia
   chứ không phải ngôn ngữ". Đã trả lại dạng này theo yêu cầu — và nó cũng
   đúng quy ước CLAUDE.md đặt cho cả repo. */
/* Không viền: icon chữ-và-nét đã cho biết đây là công tắc ngôn ngữ, nên đường
   viền chỉ còn là một nét thừa ở footer. Nhãn "EN"/"VI" ở lại vì icon một mình
   không nói được nó sẽ chuyển SANG tiếng gì. */
.lang-switch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  padding: 3px 0;
  color: var(--ink-3);
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  transition:
    border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease);
}

.lang-switch:hover {
  color: var(--ink);
}

@media (pointer: coarse) {
  /* Vùng chạm dọc 44px do lớp phủ ::after lo (mục "vùng chạm" cuối file), nên
     ở đây chỉ nới đệm ngang cho ngón tay. */
  .lang-switch {
    padding: 8px 4px;
  }
}


/* ==================== khung trang: một mép trái duy nhất ================ */

/* Trước đây .topbar và .footer cùng cố định 720px, còn <main> lấy bề rộng
   theo từng trang (440 / 520 / 720). Header và footer khớp nhau nhưng KHÔNG
   khớp phần nội dung: ở viewport 1400px, mép trái của logo lệch mép trái của
   nội dung tới 140px, và càng rộng màn hình càng lệch. Cho cả ba lấy chung
   một bề rộng theo cột thật của trang thì lệch bằng không ở mọi viewport.

   Trang tạo bình chọn không đi qua đây — nó dùng .vc/goldenLayout, vốn đã
   chung một cột 610px cho cả ba từ đầu. */
.shell--narrow .topbar,
.shell--narrow .footer {
  max-width: 440px;
}

.shell--normal .topbar,
.shell--normal .footer {
  max-width: 520px;
}

.shell--wide .topbar,
.shell--wide .footer {
  max-width: 720px;
}


/* ================= vùng chạm: mở rộng mà không đổi hình vẽ ============= */

/* Logo 24px, link footer cao 15px và viên thuốc ngôn ngữ cao 36px đều nhỏ hơn
   ngưỡng 44pt của Apple HIG. Nhưng phóng to chính chúng thì hỏng thiết kế —
   logo 24px là quy ước toàn repo (CLAUDE.md), còn viên thuốc vừa được yêu cầu
   thu nhỏ lại. Nên mở rộng bằng một lớp phủ trong suốt: ngón tay nhận đủ
   44px, mắt vẫn thấy đúng kích thước cũ, và vì lớp phủ absolute nên không đẩy
   layout đi một pixel nào.

   Chỉ áp trên thiết bị cảm ứng: trỏ chuột chính xác thì không cần. */
@media (pointer: coarse) {
  .topbar-brand,
  .vc-mark,
  .lang-switch,
  .footer p a,
  .vc-foot p a {
    position: relative;
  }

  .topbar-brand::after,
  .vc-mark::after,
  .lang-switch::after,
  .footer p a::after,
  .vc-foot p a::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    height: 44px;
    margin-top: -22px;
  }

  /* Logo vuông 24px nên còn thiếu cả bề ngang, nới thêm mỗi bên 10px. */
  .topbar-brand::after,
  .vc-mark::after {
    left: -10px;
    right: -10px;
  }
}


/* ============================ pháo giấy khi gửi phiếu ================== */

.confetti {
  position: fixed;
  inset: 0;
  pointer-events: none;
  /* Trên cả overlay con dấu, dưới toast. */
  z-index: 60;
}

/* left/top 50% + translate(-50%) trong keyframe: gốc bay là tâm màn hình,
   trùng đúng chỗ con dấu đang đóng. */
.confetti i {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 8px;
  height: 12px;
  opacity: 0;
  will-change: transform, opacity;
  animation: confetti-fly 1100ms cubic-bezier(0.16, 0.84, 0.44, 1) var(--delay) forwards;
}

@keyframes confetti-fly {
  0% {
    transform: translate(-50%, -50%) scale(0.4) rotate(0deg);
    opacity: 1;
  }
  70% {
    opacity: 1;
  }
  100% {
    /* +40px ở trục y để cuối đường bay có cảm giác rơi xuống, không bay đều
       ra mọi phía như một vụ nổ phẳng. */
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy) + 40px)) scale(1)
      rotate(var(--rot));
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .confetti {
    display: none;
  }
}


/* ===================== tên người bình chọn (poll ký tên) ================ */

/* Ô nhập tên trên lá phiếu. Dùng chung hình dáng với .field input để nó không
   trông như một control lạ rơi vào giữa trang. */
.voter-name {
  margin-top: var(--sp-3);
}

.voter-name-label {
  display: block;
  margin-bottom: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
}

.voter-name-input {
  width: 100%;
  height: 48px;
  padding: 0 var(--sp-2);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-md);
  font-family: var(--font-ui);
  /* 16px: dưới ngưỡng này Safari iOS tự phóng to trang khi ô được focus. */
  font-size: 16px;
  color: var(--ink);
  background: var(--surface);
}

.voter-name-input:focus-visible {
  outline: none;
  border-color: var(--seal);
}

/* Danh sách tên dưới mỗi thanh kết quả. Chữ nhỏ và nhạt: đây là chú thích cho
   con số, không phải nội dung chính của dòng. */
.r-voters {
  margin-top: 6px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-3);
  overflow-wrap: anywhere;
}


/* Combo button: ba hành động thành MỘT khối có viền, ngăn nhau bằng hairline.
   Gọn hơn ba nút rời và đọc ra là "nhóm việc làm được với poll này".

   Lưu ý an toàn: bản trước cố ý chừa 8px giữa "Đóng" và "Xoá" để một thao tác
   không hoàn tác được không dính sát một thao tác hoàn tác được. Ở đây đường
   ngăn thay cho khoảng trống đó, và "Xoá" giữ màu cảnh báo riêng — vẫn có
   confirm() trước khi xoá. */
/* Icon ở trên, nhãn nhỏ ở dưới. Bỏ khung viền bao ngoài và các vạch ngăn:
   thẻ đã có viền riêng, lồng thêm một khung nữa bên trong là hai lớp hộp
   chồng nhau. Ba nút vẫn chia đều bề ngang nên vẫn thẳng cột. */
.row-actions {
  display: flex;
  align-items: stretch;
  /* 6px, KHÔNG phải --sp-1 (8px): ô đã tự có đệm ngang nên khoảng hở nhìn
     thấy giữa hai nhãn vẫn rộng hơn con số này. Vẫn giữ một khe thật thay vì
     dồn sát 0 -- "Xoá" đứng cạnh "Đóng", và một thao tác không hoàn tác được
     thì không nên dính vào hàng xóm. */
  gap: 6px;
  /* Cụm nút dồn về phải ở dòng thứ hai. Không kéo giãn hết bề ngang: đây là
     hành động phụ của thẻ, còn tiêu đề mới là thứ chiếm cột trái. */
  justify-content: flex-end;
  /* Ép nút thành ô vuông 64px trong khi nội dung (icon 20 + khe 5 + nhãn 15)
     chỉ cao 34.5px để lại ~15px trống MỖI BÊN bên trong nút. Chỗ trống đó là
     vùng chạm, không phải khoảng cách -- nhưng layout vẫn tính nó là chiều
     cao, nên nó cộng thêm vào mọi khoảng cách quanh cụm nút.

     Huỷ cả hai chiều (margin-block) chứ không chỉ chiều dưới: trên desktop
     cụm nút nằm CẠNH tiêu đề trong .p-top, và 15px dôi ra ở phía trên kéo
     .p-top cao lên 49px làm tiêu đề bị đẩy xuống giữa -- thẻ khai báo padding
     13px mà nhìn thành 29.8 trên / 14 dưới. Chỉ huỷ chiều dưới thì sửa được
     mobile nhưng bỏ sót desktop. */
  margin-block: -15px;
}

.row-actions .text-btn {
  /* Rộng theo nội dung, không flex:1 -- nếu chia đều cả hàng thì cụm không
     còn "dồn phải" nữa mà thành một dải trải ngang. min-width giữ ba ô bằng
     nhau và đủ chỗ cho vùng chạm. */
  flex: 0 0 auto;
  /* Ba ô rộng bằng nhau nên các icon cách đều nhau (70px).
     Nhưng hộp đều KHÔNG đủ để khoảng cách trông đều: nhãn rộng khác nhau thì
     phần trống hai bên mỗi nhãn cũng khác nhau, và khe giữa hai mực chữ =
     6px + trống phải của nhãn trái + trống trái của nhãn phải. Với "Sao chép"
     (53px) / "Đóng" (31px) / "Xoá" (22px) thì hai khe đó là 27.7 và 43.6.
     Đổi hộp cũng không chữa được, vì hiệu số bề rộng nhãn không đổi.
     Cách duy nhất là cho ba nhãn dài xấp xỉ nhau: "Link" (25px) / "Đóng" /
     "Xoá" cho 42 và 43.6 -- coi như đều.
     64px = bề rộng tự nhiên của nhãn dài nhất ("Sao chép"). Đặt đúng con số
     đó thì cả ba ô bằng nhau và icon cách đều tuyệt đối; để 58px thì ô đầu tự
     nới ra 64 còn hai ô kia đứng ở 58, và các icon cách nhau 67/64 -- lệch
     3px, đủ để nhìn ra khi ba icon nằm cạnh nhau.
     Cũng vẫn trên sàn 44px của Apple HIG cho vùng chạm. */
  min-width: 64px;
  /* Ô VUÔNG: bề cao tự nhiên chỉ 53px (8 + icon 20 + khe 5 + nhãn 15 + 8),
     nên phải ép bằng bề rộng. aspect-ratio chứ không height cố định: nếu sau
     này nhãn nào phải xuống hai dòng thì ô cao lên và tự rộng ra theo, vẫn
     vuông. min-height là sàn khi nội dung ngắn hơn. */
  aspect-ratio: 1;
  min-height: 64px;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  padding: 8px 4px;
  border-radius: var(--radius-sm);
  /* Nhãn được phép xuống dòng, nhưng ở khoảng trắng chứ không vỡ giữa chữ. */
  white-space: normal;
  text-align: center;
  line-height: 1.25;
  font-size: 12px;
}

/* Icon to hơn vì giờ nó là thứ nhận diện chính, không còn là dấu phụ trước
   chữ. */
.row-actions .btn-ico {
  width: 20px;
  height: 20px;
}

.row-actions .text-btn:hover {
  background: var(--paper);
}


/* Dòng nhắc còn mấy lần đổi phiếu. Nhỏ và nhạt: nó là chú thích cho cái nút
   ngay trên, không phải một thông báo. */
/* Khối đổi phiếu nằm dưới kết quả. Nút chỉ rộng bằng chữ, không kéo cả hàng:
   nó là một lối ra phụ, không phải hành động chính của màn hình này. */
.revote-box {
  margin-top: var(--sp-3);
}

.revote-box .pill-btn {
  flex: 0 0 auto;
  width: auto;
  padding: 0 var(--sp-3);
}

.revote-note {
  margin-top: var(--sp-1);
  font-size: 13px;
  color: var(--ink-3);
}


/* ==================== nút vừa sao chép xong ============================= */

/* Phản hồi nằm ngay trên nút vừa bấm. Giữ nguyên hình dáng nút, chỉ đổi màu
   chữ/viền sang xanh xác nhận — nút không nhảy kích thước vì flashCopied()
   khoá min-width bằng bề rộng lúc nhàn rỗi trước khi đổi nội dung. */
/* Kèm :hover — con trỏ vẫn đang nằm trên nút vừa bấm, và
   `.poll-actions-row .pill-btn:hover` cùng specificity sẽ kéo màu chữ về
   --ink nếu ở đây không nói rõ cả hai trạng thái. */
.pill-btn.is-copied,
.pill-btn.is-copied:hover,
.text-btn.is-copied,
.text-btn.is-copied:hover {
  color: var(--success);
  /* Chữ xanh trên nền xanh nhạt -- đúng cặp --success/--success-soft của
     branding, thay cho một sắc xanh riêng chỉ dùng làm viền. 4.92:1. */
  border-color: var(--success-soft);
  background: var(--success-soft);
}

.pill-btn.is-copied .btn-ico,
.text-btn.is-copied .btn-ico {
  color: inherit;
}


/* Nút chữ ở dashboard: icon dẫn đầu, chữ trầm. Câu hỏi là thứ duy nhất được
   phép đậm trong thẻ, nên nhóm nút phải lùi lại một bậc. */
.text-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--ink-2);
}

.text-btn:hover {
  color: var(--ink);
}

.text-btn .btn-ico {
  width: 15px;
  height: 15px;
}
