/* SPDX-License-Identifier: 0BSD */

/* RGAA (R-14.1), and only what the criteria actually require. No framework,
   no build step (§14): an adopting commune's IT department reads this file in
   one sitting.

   Five rules this file keeps and every later screen must keep too:
   - nothing carries meaning by colour alone (3.1) — states and message levels
     are written in words, and the colour is decoration on top of the word;
   - the focus indicator is never removed and never relies on colour alone (10.7);
   - sizes are in rem, so the page survives a 200 % zoom (10.4);
   - text keeps its AA contrast in both themes — the token values below are
     chosen for it and the ratio is noted where it is close;
   - light and dark are a token swap and nothing else. Every rule takes its
     colour from the variables on :root; the two blocks after :root redefine
     only those variables, keeping the same contrasts, so a dark viewer gets
     the same page in the other key. `prefers-color-scheme` is honoured, and
     `data-theme` on the root — set by static/js/theme.js from the viewer's
     choice — wins over it in either direction. */

/* Libre Franklin (headings and body text) and IBM Plex Mono (tracking codes,
   the ballot's rank selects) — vendored under static/fonts/ rather than
   linked from fonts.googleapis.com: §14's self-hosting posture rules out a
   third party seeing every visitor's IP on every page load, which a live
   Google Fonts request would. Latin subset only (French needs nothing else)
   is why seven small files cover both families in full. Each is SIL Open
   Font License 1.1, redistributable; see static/fonts/README.md. */
@font-face {
  font-family: "Libre Franklin";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/librefranklin-400.woff2") format("woff2");
}
@font-face {
  font-family: "Libre Franklin";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/librefranklin-400italic.woff2") format("woff2");
}
@font-face {
  font-family: "Libre Franklin";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/librefranklin-500.woff2") format("woff2");
}
@font-face {
  font-family: "Libre Franklin";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/librefranklin-600.woff2") format("woff2");
}
@font-face {
  font-family: "Libre Franklin";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/librefranklin-700.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibmplexmono-500.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/ibmplexmono-600.woff2") format("woff2");
}

:root {
  color-scheme: light;

  --ink: #1b1b1b;
  --ink-quiet: #4a4a4a;      /* 9.3:1 on --paper */
  --paper: #ffffff;
  --paper-quiet: #f4f4f1;
  --surface: #ffffff;        /* raised cards; separated by --rule-soft + shadow */
  --rule: #767676;           /* 4.5:1 on --paper */
  --rule-soft: #d9d9d3;      /* row hairlines — decorative, backed by the zebra */
  --link: #1a4fa0;           /* 7.4:1 on --paper */
  --focus: #b8003f;
  --accent: #000091;         /* bleu France; 16:1 on --paper, 16:1 for white on it */
  --accent-ink: #ffffff;
  --accent-soft: #e9e9fb;    /* accent wash behind --ink text — 15:1 */
  --ok: #1f6b2c;             /* 6:1 on --paper */
  --ok-soft: #e7f2e6;        /* ok wash — pairs with --ok the way --accent-soft pairs with --accent */
  --warn: #7a5c00;           /* 6.3:1 on --paper */

  --shadow: 0 1px 2px rgba(17, 17, 26, 0.05), 0 6px 20px -4px rgba(17, 17, 26, 0.10);
  --shadow-sm: 0 1px 2px rgba(17, 17, 26, 0.06);

  --radius: 0.85rem;
  --radius-sm: 0.5rem;
  --measure: 42rem;          /* comfortable reading width for prose */
  --page: 78rem;             /* the centred content column */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --ink: #ececef;          /* 15:1 on --paper */
    --ink-quiet: #a9abb2;    /* 6.5:1 on --paper */
    --paper: #17181b;
    --paper-quiet: #202227;
    --surface: #22242a;
    --rule: #7d7f87;         /* 4.2:1 on --paper */
    --rule-soft: #3b3d45;
    --link: #7db3ff;         /* 8:1 on --paper */
    --focus: #ff5a7c;
    --accent: #a5b4ff;       /* 9.8:1 on --paper */
    --accent-ink: #10121a;   /* 12:1 on --accent */
    --accent-soft: #242739;
    --ok: #74c983;           /* 9:1 on --paper */
    --ok-soft: #1c2e20;
    --warn: #d9b45f;         /* 9.7:1 on --paper */
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 6px 20px -4px rgba(0, 0, 0, 0.55);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --ink: #ececef;
  --ink-quiet: #a9abb2;
  --paper: #17181b;
  --paper-quiet: #202227;
  --surface: #22242a;
  --rule: #7d7f87;
  --rule-soft: #3b3d45;
  --link: #7db3ff;
  --focus: #ff5a7c;
  --accent: #a5b4ff;
  --accent-ink: #10121a;
  --accent-soft: #242739;
  --ok: #74c983;
  --ok-soft: #1c2e20;
  --warn: #d9b45f;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 6px 20px -4px rgba(0, 0, 0, 0.55);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  color: var(--ink);
  background: var(--paper);
  font: 1rem/1.6 "Libre Franklin", system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
  /* The one piece of civic identity the platform allows itself: a bleu France
     rule across the top of every page. Decorative, removed for print. */
  border-top: 3px solid var(--accent);
}

code, kbd, samp { font-family: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace; }

/* A decorative icon beside a word that already carries the meaning (R-14.1):
   one fixed size wherever it appears, in the text's own colour, never resized
   or recoloured by context. */
.ic { width: 1rem; height: 1rem; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; vertical-align: -0.15em; }

/* One centred column. Bars and content share the same bounds so their edges
   line up on a wide monitor; the inline padding is the same everywhere. */
.banner, .pollnav, .messages, main, .contentinfo {
  max-width: var(--page);
  margin-inline: auto;
  padding: 1rem clamp(1rem, 4vw, 3rem);
}
main { padding-block: 2rem 3.5rem; }

/* The skip link is the first focusable element and is off-screen until focused,
   never `display: none`, which would take it out of the tab order (12.7). */
.skip {
  position: absolute;
  left: -100vw;
  top: 0;
  padding: 0.5rem 1rem;
  background: var(--ink);
  color: var(--paper);
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip:focus { left: 0; z-index: 10; }

a { color: var(--link); text-underline-offset: 0.15em; }
a:hover { text-decoration-thickness: 2px; }

/* Visible focus everywhere, outline rather than a colour swap, so it survives
   forced-colours and colour-blind viewing (10.7). */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ---- type scale ---------------------------------------------------------- */

h1, h2, h3 { line-height: 1.2; text-wrap: balance; }
h1 {
  font-size: clamp(1.75rem, 1.3rem + 1.9vw, 2.35rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0 0 1.25rem;
}

/* The heading's own rule, quiet rather than an accent-coloured underline: a
   thin neutral line under the row a page's <h1> sits in (with its own action,
   on screens that have one), not on the heading itself — so a page with no
   action still gets the same rule from the row alone. */
.title-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  align-items: flex-end;
  justify-content: space-between;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 1.25rem;
}
.title-row h1 { margin: 0; }

h2 { font-size: clamp(1.25rem, 1.1rem + 0.6vw, 1.5rem); font-weight: 600; margin: 2.1rem 0 0.7rem; letter-spacing: -0.01em; }
h3 { font-size: 1.05rem; font-weight: 600; margin: 1.5rem 0 0.5rem; }
p { margin: 0 0 0.8rem; }
main > p, .paper-form p, .receipt p { max-width: var(--measure); }
section { margin-bottom: 2rem; }
section:last-child { margin-bottom: 0; }

.notice, .help { color: var(--ink-quiet); }
.help { font-size: 0.9rem; }
.pending, .purged { font-style: italic; color: var(--ink-quiet); }
.required { font-weight: 400; color: var(--ink-quiet); }
/* Inline annotations next to a table cell — "(date incertaine)", "(illisible)".
   Small, italic, in the warn key; the words carry the meaning (3.1). */
.warn { font-style: italic; color: var(--warn); }

/* ---- shell ------------------------------------------------------------- */

.banner {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
  padding-block: 1rem;
  border-bottom: 1px solid var(--rule);
}
.site-name { font-weight: 700; margin: 0; font-size: 1.15rem; letter-spacing: -0.01em; }
.site-name a { color: var(--ink); text-decoration: none; display: flex; align-items: center; gap: 0.6rem; }
.site-name a:hover { color: var(--accent); }
.site-logo { display: block; max-height: 2.25rem; width: auto; }
/* §6.5.14: `logo_dark` swaps in for `logo` under the same rule the top-of-file
   colour tokens use — both the OS `prefers-color-scheme` and theme.js's
   explicit `data-theme` override — so the two never show at once. */
.site-logo--dark { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .site-logo--light { display: none; }
  :root:not([data-theme="light"]) .site-logo--dark { display: block; }
}
:root[data-theme="dark"] .site-logo--light { display: none; }
:root[data-theme="dark"] .site-logo--dark { display: block; }
.account { display: flex; gap: 0.75rem; align-items: center; }
.account__name { font-weight: 600; }

.account__public { white-space: nowrap; }

/* Language and theme controls, in the banner (base.html `header_tools`). The
   base `.langswitch`/`.themeswitch` rules size their select, submit and toggle
   buttons for a full-width footer row and bottom-align them (`align-items:
   end`) against labels of two different weights — inline with the site name
   that reads as three controls at three heights. Here both groups share one
   compact control height and are centre-aligned against each other instead. */
.header-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem 1.25rem; }
.header-tools .langswitch,
.header-tools .themeswitch { margin-top: 0; align-items: center; gap: 0.4rem; }
.header-tools .langswitch label,
.header-tools .themeswitch > span {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-quiet);
}
.header-tools select,
.header-tools .langswitch button,
.header-tools .themeswitch button {
  min-height: 2rem;
  font-size: 0.82rem;
}
.header-tools select { padding: 0.2rem 0.5rem; }
.header-tools .langswitch button { padding: 0.3rem 0.7rem; }
.header-tools .themeswitch button { padding: 0.3rem 0.6rem; }

.contentinfo { border-top: 1px solid var(--rule); color: var(--ink-quiet); font-size: 0.9rem; margin-top: 1rem; }

/* ---- espace mairie shell (§6.5) ---------------------------------------
   A classic layout: one fixed left-column menu, the same on every screen, with
   the banner, messages, content and footer sitting beside it; a breadcrumb
   pinned to the top of the content column so the operator can always see where
   they are. No JavaScript — the column becomes a bar above the content on a
   narrow screen (§14). The menu markup is `backoffice/_nav.html`; what it lists
   is decided in `apps/backoffice/templatetags/backoffice_nav.py`. */

body.bo { --bo-side: 15.5rem; }

body.bo .banner,
body.bo .messages,
body.bo main,
body.bo .contentinfo {
  max-width: none;
  margin-inline: 0;
  margin-left: var(--bo-side);
}
body.bo main { padding: 0; }

.bo-side {
  position: fixed;
  top: 3px;                 /* clears the bleu France rule on <body> */
  left: 0;
  bottom: 0;
  width: var(--bo-side);
  display: flex;
  flex-direction: column;   /* so the tools block can sit at the foot */
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--paper-quiet);
  border-right: 1px solid var(--rule);
  padding-bottom: 1.5rem;
}
/* The icon sprite is markup, not a picture: keep it out of the flow and the
   accessibility tree (each entry still carries its text label). */
.bo-ico-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.bo-ico {
  width: 1.15rem;
  height: 1.15rem;
  flex: none;
  margin-top: 0.1rem;
  fill: none;
  stroke: var(--ink-quiet);
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.bo-side__brand {
  display: block;
  padding: 1rem 1.15rem 0.85rem;
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
}
.bo-side__brand:hover { color: var(--accent); }

/* The global links: always directly under the wordmark, in the same order. */
.bo-side__list--global { padding-top: 0.15rem; }

/* The poll state separates the global links above from the poll's own groups
   below. It is only present while a poll is in scope; the title is not
   repeated here — the breadcrumb and the <h1> already carry it. */
.bo-side__poll {
  margin-top: 0.9rem;
  padding: 0.7rem 1.15rem;
  border-top: 1px solid var(--rule);
}

/* The poll state, written out — the wash only sorts it, it does not carry it
   (R-14.1) — same pattern as .poll-index__state on the public list: an icon
   and the word, coloured by what the word already says. */
.bo-state {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0.65rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-size: 0.8rem;
  font-weight: 600;
}
.bo-state--draft { background: var(--paper-quiet); color: var(--ink-quiet); }
.bo-state--announced { background: var(--accent-soft); color: var(--accent); }
.bo-state--open { background: var(--ok-soft); color: var(--ok); }
.bo-state--closed,
.bo-state--published,
.bo-state--withdrawn {
  background: var(--paper);
  border-color: var(--rule);
  color: var(--ink-quiet);
}

.bo-side__group {
  margin: 1.1rem 0 0.2rem;
  padding: 0 1.15rem;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-quiet);
}
.bo-side__poll + .bo-side__group { margin-top: 0.35rem; }
.bo-side__list { list-style: none; margin: 0; padding: 0; }
.bo-side__link {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.5rem 1.15rem;
  color: var(--ink);
  text-decoration: none;
  border-left: 3px solid transparent;
}
.bo-side__text { display: flex; flex-direction: column; min-width: 0; }
.bo-side__link:hover { background: var(--rule-soft); }
.bo-side__link:hover .bo-ico { stroke: var(--ink); }
.bo-side__link.is-current {
  font-weight: 700;
  color: var(--accent);
  background: var(--accent-soft);
  border-left-color: var(--accent);
}
.bo-side__link.is-current .bo-ico { stroke: var(--accent); }

/* A commune admin's entry for a role they have not granted themselves yet: the
   link leads to the role screen, and the sub-label — not the colour — says so. */
.bo-side__link.needs-grant { color: var(--ink-quiet); }
.bo-side__grant {
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--ink-quiet);
}

/* Language and theme, at the foot of the panel (§6.5). `margin-top: auto`
   pushes the block down while the menu is short, and it scrolls with the rest
   once the menu is long. */
.bo-side__tools {
  margin-top: auto;
  padding: 0.9rem 1.15rem 0;
  border-top: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}
.bo-side__tools .langswitch,
.bo-side__tools .themeswitch { margin-top: 0; }
.bo-side__lang { align-items: center; }
.bo-side__lang label,
.bo-side__theme > span {
  flex-basis: 100%;
  margin: 0;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-quiet);
  align-self: start;
}
.bo-side__lang select { flex: 1 1 6rem; min-width: 0; min-height: 2.2rem; }
/* The generic <button> is a bold filled CTA (§6.5's "wall of blue" is exactly
   what a routine submit should not be here) — this one only re-submits the
   form beside it, so it takes the same quiet, bordered treatment as the
   theme toggle next to it, not the accent fill "Enregistrer" gets elsewhere. */
.bo-side__lang button {
  min-height: 2.2rem;
  padding: 0.4rem 0.75rem;
  font-weight: 500;
  font-size: 0.85rem;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--rule);
}
.bo-side__lang button:hover { opacity: 1; background: var(--paper-quiet); }
.bo-side__tools .themeswitch { flex-direction: column; align-items: stretch; gap: 0.35rem; }
.bo-side__tools .themeswitch [role="group"] { display: flex; }
.bo-side__tools .themeswitch button { flex: 1; padding: 0.4rem 0.3rem; font-size: 0.8rem; }

/* Breadcrumb: sticky, so "where am I" survives a long journal or tally. */
.bo-crumb {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--paper-quiet);
  border-bottom: 1px solid var(--rule);
  padding: 0.6rem clamp(1rem, 4vw, 2.5rem);
}
.bo-crumb ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0;
  padding: 0;
  font-size: 0.85rem;
}
.bo-crumb li { display: flex; gap: 0.35rem; align-items: baseline; }
.bo-crumb li + li::before { content: "\203A"; color: var(--ink-quiet); }
.bo-crumb a { color: var(--link); text-decoration: none; }
.bo-crumb a:hover { text-decoration: underline; }
.bo-crumb [aria-current="page"] { color: var(--ink-quiet); font-weight: 600; }

.bo-main { padding: 1.75rem clamp(1rem, 4vw, 2.5rem) 3.5rem; }

/* Narrow screens: no fixed column. It flows back into the page as a bar above
   the content, between the banner and the breadcrumb. */
@media (max-width: 52rem) {
  body.bo { --bo-side: 0rem; }
  body.bo .banner,
  body.bo .messages,
  body.bo main,
  body.bo .contentinfo { margin-left: 0; }
  .bo-side {
    position: static;
    width: auto;
    border-right: 0;
    border-bottom: 1px solid var(--rule);
    padding-bottom: 0.5rem;
  }
  .bo-side__list { display: flex; flex-wrap: wrap; }
  .bo-side__link,
  .bo-side__link.is-current { border-left: 0; border-radius: var(--radius-sm); }
  .bo-side__tools { margin-top: 0.75rem; flex-flow: row wrap; align-items: end; gap: 1rem 1.5rem; }
  .bo-crumb { position: static; }
}
.langswitch, .themeswitch { display: flex; gap: 0.5rem; align-items: end; flex-wrap: wrap; margin-top: 0.75rem; }
.langswitch label { font-weight: 400; }

/* Theme control (static/js/theme.js). Progressive enhancement: it is hidden
   until the script reveals it, since without JavaScript it does nothing and
   the OS setting already drives the page. A segmented group of toggle buttons;
   the pressed one is bold and filled — `aria-pressed` carries the state and
   the weight change backs it up for a sighted viewer (3.1). */
.themeswitch[hidden] { display: none; }
.themeswitch > span { font-weight: 600; align-self: center; }
.themeswitch [role="group"] {
  display: inline-flex;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.themeswitch button {
  margin: 0;
  padding: 0.35rem 0.75rem;
  font-weight: 500;
  color: var(--ink);
  background: var(--surface);
  border: 0;
  border-left: 1px solid var(--rule);
  border-radius: 0;
}
.themeswitch button:first-child { border-left: 0; }
.themeswitch button:hover { opacity: 1; background: var(--paper-quiet); }
.themeswitch button[aria-pressed="true"] {
  font-weight: 700;
  color: var(--accent-ink);
  background: var(--accent);
}

/* ---- messages and callouts ------------------------------------------------
   All the same object: an accent bar down the left of a tinted box. The word
   inside carries the meaning; the colour only sorts it (3.1). */

.messages { list-style: none; margin: 1rem auto 0; }
.message,
.blocker,
.warning,
.error,
.receipt__notice,
.validation-report li {
  border-left: 4px solid var(--rule);
  background: var(--paper-quiet);
  padding: 0.7rem 0.95rem;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  margin-bottom: 0.5rem;
}
.message__level { font-weight: 700; text-transform: uppercase; font-size: 0.78rem; letter-spacing: 0.04em; }
.message--error,
.blocker,
.error,
.receipt__notice,
.validation-report__bad { border-left-color: var(--focus); }
.message--success,
.validation-report__ok { border-left-color: var(--ok); }
.error { font-weight: 600; }
.warning,
.validation-report__warn { border-left-color: var(--warn); }

.blockers, .validation-report { list-style: none; padding: 0; margin: 0.5rem 0 1.5rem; }

/* ---- forms ------------------------------------------------------------- */

label { display: block; font-weight: 600; margin-bottom: 0.15rem; }
.field { margin-bottom: 1.15rem; }
.field .help, .privacy { display: block; margin-top: 0.1rem; color: var(--ink-quiet); }

input, select, textarea, button { font: inherit; }
input, select, textarea {
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
  max-width: 100%;
}
input[type="search"], input[type="text"], input[type="email"], input[type="password"], input[type="date"], select {
  min-height: 2.5rem;
}
input[type="search"], input[type="text"], input[type="email"], input[type="password"] { width: min(30rem, 100%); }
select { cursor: pointer; }
/* The reset-password field rides in a wrapping flex row beside its button
   (screen 18); keep it from pushing the button onto its own line. */
details form.inline input[type="password"] { width: min(16rem, 100%); }
input:focus-visible, select:focus-visible, textarea:focus-visible { border-color: var(--focus); }
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); width: 1rem; height: 1rem; }

button, .button {
  display: inline-block;
  padding: 0.6rem 1.1rem;
  font-weight: 600;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
  text-decoration: none;
  transition: opacity 0.12s ease, background 0.12s ease;
}
button:hover, .button:hover { opacity: 0.88; text-decoration: none; }
button:active, .button:active { opacity: 0.76; }

/* Secondary and destructive actions carry the same word-first labelling; the
   quieter fill keeps a routine screen from looking like a wall of blue, and
   sets the primary action apart (R-2.4, §6.5). */
button.secondary,
.button.secondary,
form.inline button {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--rule);
}
form.danger button {
  background: var(--surface);
  color: var(--focus);
  border-color: var(--focus);
}

/* `min-width: 0` (R-14.2): browsers give `<fieldset>` an intrinsic minimum
   width from its content — a legacy quirk of how the legend is positioned —
   that a plain `width: auto` does not override, so a config screen's
   fieldset would hold itself wider than the phone viewport regardless of how
   narrow its own fields are willing to go. */
fieldset {
  min-width: 0;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1rem 1.1rem;
  margin: 0 0 1.25rem;
}
legend { font-weight: 700; padding: 0 0.4rem; }

/* Tabs (static/js/tabs.js), grouping screen 2's configuration fieldsets
   (§6.5.2). Progressive enhancement: `[hidden]` on the list keeps it off
   until the script wires it up, and every panel renders open below it —
   flex-wrap rather than a scroll rail, so the five tabs stay reachable at a
   phone width without introducing horizontal scrolling (R-14.2). */
.tabs__list[hidden] { display: none; }
.tabs__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.25rem;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 1.1rem;
}
.tabs__tab {
  padding: 0 0 0.65rem;
  font-weight: 600;
  color: var(--ink-quiet);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  margin-bottom: -1px;
}
.tabs__tab:hover { opacity: 1; color: var(--ink); background: none; }
/* The selected tab carries the state in weight and a filled underline, not
   colour alone (3.1) — `aria-selected` is the source of truth either way. */
.tabs__tab.is-selected {
  color: var(--ink);
  border-bottom-color: var(--accent);
}
/* A rejected submission opens on the first failing tab (tabs.js), but a
   near-empty form fails several at once; the rest stayed unmarked with
   nothing on screen pointing at them. Text, not a colour or a bare dot
   (3.1): `.tabs__tab-flag` is the same wording every screen reader user
   gets too, appended by tabs.js from the template's translated
   data-tabs-error-label. */
.tabs__tab-flag {
  color: var(--focus);
  font-weight: 700;
  font-size: 0.85em;
}

/* The account-management disclosure stacks three small forms; space them and
   let each lay its label, field and button out in a row. */
details form.inline {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: end;
  margin: 0.75rem 0 0;
}
details form.inline label { margin: 0; }

/* État filter chips above a paginated list (screen 10's poll list, roll
   browser and others to come) — a fixed handful of states, unlike the list
   itself, so plain links read better here than another <select>. */
.tags { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0 0 1rem; padding: 0; }
.tags .tag { text-decoration: none; color: inherit; }
.tags .tag.active { background: var(--accent-soft); border-color: var(--accent); font-weight: 600; }

/* The row a list is currently drilled into (screen 10's poll list, the
   scrutin whose roles are shown below it). */
tbody tr.selected th, tbody tr.selected td { background: var(--accent-soft); }

/* ---- proposition editor (screen 2) --------------------------------- */

/* Revealed by static/js/option-editor.js; the no-JS path never shows them. */
.option-editor__add[hidden],
.option-row__controls[hidden],
.option-row__delete[hidden] { display: none; }
.option-editor__add { margin-top: 0.25rem; }
.option-row__delete label { display: inline-flex; align-items: center; gap: 0.4rem; }
/* A stored row awaiting removal on save: kept in place, struck through, undoable. */
.option-row--removed { opacity: 0.55; }
.option-row--removed legend { text-decoration: line-through; }

/* ---- tables ---------------------------------------------------------- */

/* R-14.2: a table whose column count tracks something the commune controls
   (propositions in the pairwise matrix, R-11.3) has no upper bound the CSS
   can plan around. Past a handful of columns it cannot fit a phone screen
   whatever the type scale does, so it scrolls in its own box — same
   principle as `td code` below, applied to the table as a whole rather than
   forcing the surrounding page to scroll sideways with it. */
.table-scroll { overflow-x: auto; margin: 0.5rem 0 2rem; }
.table-scroll table { margin: 0; }

table { border-collapse: collapse; width: 100%; margin: 0.5rem 0 2rem; }
caption { text-align: left; font-weight: 600; padding-bottom: 0.6rem; }
th, td { text-align: left; padding: 0.65rem 0.75rem; border-bottom: 1px solid var(--rule-soft); vertical-align: top; }
thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--paper-quiet);
  border-bottom: 2px solid var(--rule);
  font-size: 0.92rem;
}
/* Zebra and hover cover the row header too (the results matrix leads each row
   with <th scope="row">), or the row would stripe everything but its label. */
tbody tr:nth-child(even) th, tbody tr:nth-child(even) td { background: var(--paper-quiet); }
tbody tr:last-child th, tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover th, tbody tr:hover td { background: var(--accent-soft); }

.rolelist { list-style: none; margin: 0; padding: 0; }
.rolelist li { padding: 0.1rem 0; }

.tag {
  display: inline-block;
  background: var(--paper-quiet);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.05rem 0.55rem;
  font-size: 0.8rem;
}

/* An audit row's before/after is JSON; let it scroll rather than stretch the
   table past the viewport. */
td code { display: inline-block; max-width: 32rem; overflow-x: auto; }

/* ---- screen 1: tableau de bord --------------------------------------- */

/* Stat blocks read as cards: a raised surface, the label column quiet, the
   value column plain. Used on the dashboard, the results screens and the
   read-only configuration. */
.facts {
  display: grid;
  /* `minmax(0, …)` rather than bare `max-content 1fr` (R-14.2): a grid track's
     automatic minimum is its content's unwrapped width, so a long label
     ("Fin de la saisie des bulletins papier") or value would otherwise hold
     the column — and everything alongside it in the ancestor grid, up
     through .poll-layout — to that width even on a phone screen, forcing the
     whole page to scroll sideways instead of the text wrapping. */
  grid-template-columns: minmax(0, max-content) minmax(0, 1fr);
  gap: 0.5rem 1.5rem;
  margin: 0.5rem 0 1.5rem;
  padding: 1.1rem 1.3rem;
  background: var(--surface);
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.facts dt { font-weight: 600; color: var(--ink-quiet); min-width: 0; }
.facts dd { margin: 0; min-width: 0; }
/* A hash or seed (§9's verification section) is one unbroken run of
   characters, unlike the sentences and dates .facts otherwise holds — scoped
   to `code` so an ordinary value still wraps at word boundaries rather than
   splitting mid-word the moment the column narrows. */
.facts dd code { overflow-wrap: anywhere; }
/* Below phone width, `minmax(0, …)` above stops the page scrolling sideways,
   but a label like "Fin de la saisie des bulletins papier" still claims most
   of the row, leaving the value a handful of pixels to wrap into — legible
   in principle, unreadable in practice. Stacking the pair — label, then
   value, each at the row's full width — is what an elector on a phone
   actually needs (R-14.2), not just the absence of a scrollbar. */
@media (max-width: 34rem) {
  .facts { grid-template-columns: 1fr; }
  .facts dt:not(:first-child) { margin-top: 0.5rem; }
}
/* Nested inside another boxed block (a review article), the card treatment
   would be a box in a box — drop it back to a plain grid. */
.review .facts, .paper-elector .facts { box-shadow: none; border: 0; background: none; padding: 0; }

/* The calendar's own .facts (§6.4): each <dt> carries a decorative icon ahead
   of the word (R-14.1 — the label is what says "ouverture"/"clôture", the
   icon only echoes it), inside the <dt> itself so the <dl> stays valid. */
.facts--icons dt { display: flex; align-items: center; gap: 0.5rem; }

/* A .facts list whose every dd is a plain count (the two Participation
   sections, public and back-office) reads better as a row of figures than as
   a label/value table — each dt+dd pair wrapped in its own <div> (valid
   inside <dl>, HTML §4.4.9) becomes one tile; the count itself is set in the
   tabular-figure mono face so the digits line up. */
.facts--stats { grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr)); gap: 1rem; }
/* A label that wraps to two lines ("Inscrits n'ayant pas encore voté") must
   not push its own number lower than the single-line labels beside it — the
   whole point of a stat row is that the figures line up. Reserving two
   lines' height for every label, wrapped or not, keeps every dd starting at
   the same baseline regardless of how long its own dt happens to be. */
.facts--stats dt { font-size: 0.85rem; min-height: 2.6rem; }
.facts--stats dd {
  font-family: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 1.6rem;
  font-weight: 600;
  margin-top: 0.2rem;
}

.actions { list-style: none; padding: 0; margin: 0.5rem 0 0; display: flex; flex-direction: column; gap: 0.5rem; }
/* One bordered row per action rather than a bare link — the same card treatment
   as .poll-index below, so the two lists a mairie operator and a visitor each
   scan most often read as the same kind of object. */
.actions li a {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.75rem 1rem;
  background: var(--surface);
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--ink);
  font-weight: 600;
  transition: border-color 0.12s ease;
}
.actions li a:hover { border-color: var(--accent); text-decoration: none; }
.actions li a .ic { color: var(--ink-quiet); }
.actions li a .ic.chev { margin-left: auto; }
.actions li .pending {
  display: block;
  padding: 0.75rem 1rem;
  border: 1px dashed var(--rule-soft);
  border-radius: var(--radius-sm);
}

/* ---- screen 8: journal d'audit ------------------------------------- */

.filters { display: flex; flex-wrap: wrap; gap: 1rem; align-items: end; margin-bottom: 1.5rem; }
.filters p { margin: 0; }
.filters .help { display: block; font-size: 0.85rem; }
.pagination { display: flex; gap: 1rem; align-items: baseline; margin-top: 1.5rem; }

/* ---- screen 4: file d'attente des inscriptions --------------------
   The two decisions sit side by side and are told apart by their legend,
   never by colour alone (R-14.1). */

.review {
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 1.3rem 1.4rem;
  margin-top: 1.75rem;
}
.decision { display: flex; flex-wrap: wrap; gap: 1.25rem; align-items: start; }
.decision__half {
  flex: 1 1 20rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1rem 1.1rem;
}
/* Screen 6 wraps a <fieldset> inside a .decision__half <form>; don't draw the
   box twice. Screen 4's .decision__half is itself the fieldset. */
.decision__half > fieldset { border: 0; border-radius: 0; padding: 0; margin: 0; }
.decision__half legend { font-weight: 700; padding: 0 0.4rem; }

/* ---- screens 5–7: paper ballots (§6.5.5–7) ------------------------- */

.paper-search { margin-bottom: 1.75rem; }
.paper-elector .facts, .receipt__ranking, .paper-ranking { margin: 0.5rem 0 1.25rem; }
/* The rank itself is already spelled out in each row's select ("1er choix", "sans
   rang"…), so the list doesn't also need a decimal number competing with it —
   unlike .ranking-summary below, a read-only recap where the position in the
   list *is* the rank. */
.ranking { list-style: none; padding-left: 0; margin: 0.5rem 0 1.25rem; display: flex; flex-direction: column; gap: 0.6rem; }
.ranking__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem 1rem;
  padding: 0.85rem 1.05rem;
  background: var(--surface);
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius-sm);
}
.ranking__row label { margin: 0; font-weight: 600; }
.ranking-summary { list-style: decimal; padding-left: 1.4rem; margin: 0.5rem 0 1.25rem; }
.ranking-summary li { padding: 0.2rem 0; }

/* The dead end when the elector has already voted online (§6.4). Red, and also
   announced in words — the heading names the situation and role="alert" carries
   it to a screen reader, so the colour is never the only signal (R-14.1). */
.collision {
  border: 2px solid var(--focus);
  background: var(--paper-quiet);
  border-radius: var(--radius);
  padding: 0.9rem 1.1rem;
  margin: 1rem 0 1.75rem;
}
.collision__title { font-weight: 700; color: var(--focus); font-size: 1.05rem; margin: 0 0 0.4rem; }
.collision p:last-child { margin-bottom: 0; }

/* ---- receipts: the online one (R-7.x) and the paper one (R-8.4) ------- */

.receipt {
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.75rem;
  max-width: 34rem;
}
.receipt__poll { font-weight: 700; font-size: 1.1rem; margin: 0 0 1rem; }
.receipt__label {
  margin: 1rem 0 0.1rem;
  color: var(--ink-quiet);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
/* The tracking code is the one thing on either receipt the elector must be able
   to copy exactly: big, monospaced, set on an accent wash so it reads as the
   payload of the page. */
.receipt__code,
.tracking-code {
  display: inline-block;
  font-size: 1.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  font-family: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
  margin: 0.2rem 0 0.5rem;
}
.tracking-code {
  background: var(--accent-soft);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.9rem;
}
.tracking-code code { font: inherit; }
.receipt__identity, .receipt__consultative { font-size: 0.9rem; color: var(--ink-quiet); }
.noprint + .noprint, .receipt + .noprint { margin-top: 1rem; }
.noprint a + a { margin-left: 1.25rem; }

/* ---- public poll page and results (§6.6, §9) ----------------------
   Reading pages: prose width on the text, the propositions as a tab per
   option (.tabs__list/.tabs__tab, shared with screen 2's config editor
   above), the consultative-status statement set apart without colour
   carrying it. */

/* Fil d'Ariane (R-14.1): same list/separator shape as .bo-crumb above, but not
   sticky — the public pages have no fixed side column for it to sit clear of. */
.pub-crumb { margin-bottom: 1.1rem; }
.pub-crumb ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0;
  padding: 0;
  font-size: 0.85rem;
}
.pub-crumb li { display: flex; gap: 0.35rem; align-items: baseline; }
.pub-crumb li + li::before { content: "\203A"; color: var(--ink-quiet); }
.pub-crumb a { color: var(--link); text-decoration: none; }
.pub-crumb a:hover { text-decoration: underline; }
.pub-crumb [aria-current="page"] { color: var(--ink-quiet); font-weight: 600; }

/* The poll's own state, called out the same way as a .message: a coloured
   left border on a tinted box, word-first (the sentence itself already says
   "ouverte"/"close"/…, R-14.1) so the colour only sorts what the words
   already say. */
.status {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  font-weight: 600;
  border-left: 4px solid var(--rule);
  background: var(--paper-quiet);
  padding: 0.7rem 0.95rem;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  margin: 0 0 1.25rem;
}
.status .ic { margin-top: 0.2rem; color: var(--ink-quiet); }
.status span { flex: 1; }
/* The richer form — a title plus an explanatory sentence, not just one line
   (the dashboard's pending-review callout) — takes a <div> instead of the
   bare <span> above; its own paragraph drops back to normal weight. */
.status div { flex: 1; }
.status div p { margin: 0.2rem 0 0; font-weight: 400; }
.status--open { border-left-color: var(--ok); }
.status--open .ic { color: var(--ok); }
.status--preview { border-left-color: var(--accent); }
.status--preview .ic { color: var(--accent); }
.status--warn { border-left-color: var(--warn); }
.status--warn .ic { color: var(--warn); }
.status--closed { border-left-color: var(--rule); font-weight: 500; }

/* The poll page's main content and its "Participer" card (§6.6): a sticky
   aside rather than the button repeated top and bottom of the page, so it
   stays in view as the reader scrolls the propositions and the calendar. */
/* `minmax(0, …)` on both tracks (R-14.2): without it a track's automatic
   minimum is its content's unwrapped width, so unwrappable content anywhere
   in .poll-layout__main or __aside would hold this grid — not just the
   .facts columns nested inside it — wider than the viewport. */
.poll-layout { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 2rem; align-items: start; margin-top: 0.5rem; }
.poll-layout__aside { position: sticky; top: 1.25rem; }
@media (max-width: 58rem) {
  .poll-layout { grid-template-columns: minmax(0, 1fr); }
  .poll-layout__aside { position: static; }
}
.cta-card {
  background: var(--surface);
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 1.4rem 1.5rem;
}
.cta-card h2 { margin-top: 0; font-size: 1.15rem; }
.cta-card .button { width: 100%; text-align: center; }
.extensions { list-style: none; padding: 0; margin: 0.5rem 0 0; }
.extensions li { padding: 0.4rem 0; border-bottom: 1px solid var(--rule-soft); max-width: var(--measure); }
.extensions li:last-child { border-bottom: 0; }
.outcome {
  font-size: 1.15rem;
  font-weight: 600;
  max-width: var(--measure);
  border-left: 4px solid var(--accent);
  padding-left: 0.9rem;
}
/* The results page's own outcome (R-11.2): a card set apart from the
   derivation and verification sections below it, so the winner (or tie, or
   "no ballot retained") is the first thing read on the page, not one line
   among the tally details. */
.result-hero {
  background: var(--accent-soft);
  border-radius: var(--radius);
  padding: 1.25rem 1.5rem;
  margin: 0 0 1.5rem;
  max-width: var(--measure);
}
.result-hero__eyebrow {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-quiet);
}
.result-hero__outcome {
  font-size: 1.6rem;
  line-height: 1.3;
  border-left: 0;
  padding-left: 0;
  margin: 0 0 1rem;
  max-width: none;
}
/* Nested inside the hero (which is itself the card), the .facts treatment
   would be a card in a card — same fix as .review .facts above. */
.result-hero .facts { box-shadow: none; border: 0; background: none; padding: 0; margin: 0; }
.legal { border-top: 1px solid var(--rule); padding-top: 1rem; }
.legal p { max-width: var(--measure); }
/* One bordered, rounded row per poll rather than a hairline-separated list — the
   same card treatment as .actions above, so the two lists read as one system. */
.poll-index { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 0.75rem; }
.poll-index li {
  padding: 1.1rem 1.3rem;
  background: var(--surface);
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.poll-index li:hover { border-color: var(--rule); box-shadow: var(--shadow); }
.poll-index__link {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1.25rem;
  color: inherit;
  text-decoration: none;
}
.poll-index__main { flex: 1 1 18rem; min-width: 0; }
.poll-index__title { display: block; font-weight: 600; font-size: 1.05rem; color: var(--link); }
.poll-index__link:hover .poll-index__title { text-decoration: underline; }
.poll-index__meta { display: block; color: var(--ink-quiet); font-size: 0.88rem; margin-top: 0.15rem; }
/* Row.status (apps.publicsite.views._status_key), never read off poll.state
   alone — otherwise a poll past closes_at still reads "ouverte" for as long
   as the scheduled close_poll job takes to catch up (§5.1, §6.4, T-67/T-68).
   The word is what carries the state (R-14.1); the tint only sorts it. */
.poll-index__state {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  flex: none;
  padding: 0.3rem 0.7rem;
  border-radius: var(--radius-sm);
  font-weight: 600;
  font-size: 0.82rem;
}
.poll-index__state--open { color: var(--ok); background: var(--ok-soft); }
.poll-index__state--preview { color: var(--accent); background: var(--accent-soft); }
.poll-index__state--closed { color: var(--ink-quiet); background: var(--paper-quiet); }
.poll-index .outcome--compact { padding-top: 0.6rem; margin-top: 0.6rem; border-top: 1px dashed var(--rule-soft); }
/* A published poll's outcome, surfaced on the landing page itself (R-11.2,
   R-11.4) rather than left for a click into the poll's own page — the
   smaller sibling of .outcome on the results page. */
.outcome--compact { font-size: 1rem; margin: 0.5rem 0 0; padding-left: 0.7rem; border-left-width: 3px; }
.outcome--compact a { margin-left: 0.5rem; white-space: nowrap; }

/* ---- an option's extended description (R-3.12, §3.1 bis) ----------- */

.option-details { margin: 0.5rem 0 0.75rem; max-width: var(--measure); }
.option-details img { border-radius: 4px; }

/* 16:9 responsive wrapper around the one iframe apps.elections.optioncontent
   ever emits — width comes from the surrounding text column, not from the
   iframe's own (fixed, sandboxed) attributes. */
.youtube-embed { position: relative; width: 100%; max-width: var(--measure); aspect-ratio: 16 / 9; margin: 0.5rem 0; }
.youtube-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* ---- screen 2's per-proposition image manager (§6.5.2, R-3.12) ----- */

.option-images { list-style: none; padding: 0; margin: 0 0 0.75rem; display: flex; flex-wrap: wrap; gap: 0.75rem; }
.option-images li { display: flex; flex-direction: column; gap: 0.3rem; }
.option-images__thumb { width: 8rem; height: 8rem; object-fit: cover; border-radius: 4px; border: 1px solid var(--rule-soft); }

/* ---- screen 14's logo and favicon (§6.5.14) ------------------------ */

.commune-branding__preview { max-width: 12rem; max-height: 6rem; width: auto; height: auto; border-radius: 4px; border: 1px solid var(--rule-soft); }
.commune-branding__preview--favicon { max-width: 4rem; max-height: 4rem; }
/* A dark-theme logo is typically light-coloured artwork meant for a dark
   background — previewed on one here regardless of the operator's own
   theme, or it would often look blank. */
.commune-branding__preview--dark { background: #17181b; padding: 0.5rem; }

/* ---- back-to-top shortcut (poll_detail.html) ------------------------
   Fixed and visible by default — a harmless permanent fixture — so a visitor
   with JavaScript disabled still has the shortcut once the page runs long
   with tabs, calendar, participation and legal sections one after another.
   static/js/backtotop.js is the only thing that ever adds `.is-hidden`, and
   only once scroll position says the title is already off-screen, so with
   JavaScript the button stays out of the way until it is actually useful. */
.backtotop {
  position: fixed;
  right: clamp(1rem, 4vw, 2rem);
  bottom: 1.5rem;
  z-index: 5;
  box-shadow: var(--shadow);
}
.backtotop.is-hidden { display: none; }

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

@media print {
  body { border-top: 0; }
  .banner, .bo-side, .bo-crumb, .messages, .contentinfo, .langswitch, .themeswitch, .noprint, .backtotop { display: none !important; }
  .receipt { border: none; border-radius: 0; box-shadow: none; padding: 0; max-width: none; }
  main, body.bo main { margin: 0; padding: 0; max-width: none; }
  .bo-main { padding: 0; }
  .title-row { border-bottom: 0; }
  .poll-layout__aside { position: static; }
}
