/* PPWR Toolkit — app styles (no external dependencies)

   Theme: the same "trust" palette as the marketing site (assets/site.css) — deep
   navy ink, primary blue #2563EB, cyan on dark surfaces, cool off-white ground,
   blue-cast shadows. The app stays dense on purpose: this file borrows the
   colours and the heading face, not the site's generous radii and padding.

   Status colours are semantic and deliberately untouched by the theme: green
   pass / amber declared / red required must keep reading exactly as before.

   No external assets (CDNs, hotlinked fonts): Montserrat is self-hosted beside
   this file so the strict `font-src 'self'` CSP holds. Montserrat by Julieta
   Ulanovsky et al., SIL Open Font License 1.1 — files unmodified from Google
   Fonts, redistribution expressly permitted. */

/* latin-ext — downloaded only when a page uses a character in this range */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('montserrat-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('montserrat-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --bg: #F4F7FC;
  --panel: #ffffff;
  --ink: #0F1D33;
  --ink-soft: #33445F;
  --muted: #5D6C84;
  --line: #DCE4F0;
  --line-soft: #EAEFF7;
  --accent: #2563EB;
  --accent-deep: #1D4ED8;
  --accent-soft: #E8F0FE;
  --accent-ink: #ffffff;
  --deep: #0A1E4A;          /* trust navy — app header */
  --bright: #38BDF8;        /* cyan — accents on navy */
  --warn: #b45309;
  --bad: #b91c1c;
  --good: #15803d;
  --radius: 10px;
  /* Blue-cast shadows so lifted surfaces sit in the same light as the palette. */
  --shadow-sm: 0 1px 2px rgba(13, 38, 76, .05);
  --shadow-md: 0 8px 24px rgba(13, 38, 76, .08);
  /* Headings only. Body text stays on the system stack — this is a dense
     working UI and the system face is the more legible at .85–.95rem. */
  --sans-head: 'Montserrat', -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--ink);
  line-height: 1.45;
}
::selection { background: #CFE1FD; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
header.site {
  background: var(--deep);
  color: #fff;
  padding: 14px 22px;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
header.site .logo { font-family: var(--sans-head); font-size: 1.25rem; font-weight: 700; letter-spacing: .3px; }
header.site .logo span { color: var(--bright); }
header.site a.logo { color: inherit; text-decoration: none; }
header.site a.logo:hover span { text-decoration: underline; }
/* The "by Toolcat" tag rides inside the logo and inherits --muted from the page,
   which is a page-ground grey: on navy it needs its own on-dark value. */
header.site .logo small { color: rgba(255, 255, 255, .62); }
header.site nav { margin-left: auto; display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
header.site nav a { color: rgba(255, 255, 255, .75); text-decoration: none; font-size: .92rem; }
header.site nav a:hover { color: #fff; }
header.site :focus-visible { outline-color: var(--bright); }
/* User bar: identity is a fenced-off label, the actions are evenly spaced links. */
#user-bar { display: inline-flex; align-items: center; gap: 16px; }
#user-bar a { color: rgba(255, 255, 255, .75); text-decoration: none; font-size: .85rem; white-space: nowrap; }
#user-bar a:hover { color: #fff; }
#user-bar .ub-id { font-size: .85rem; color: rgba(255, 255, 255, .8); white-space: nowrap;
  padding-right: 16px; border-right: 1px solid rgba(255, 255, 255, .22); }
header.site nav .lang-pick select {
  background: #123; color: #EAF1FF; border: 1px solid rgba(255, 255, 255, .25);
  border-radius: 6px; padding: 3px 6px; font: inherit; font-size: .85rem;
}
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* Mandatory-backup reminder bar. Amber = due (snoozable for the day), red = overdue
   (stays until a backup is actually made). Same semantics as the field traffic light. */
.bk-banner { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 10px 22px; font-size: .9rem; border-bottom: 1px solid var(--line); }
.bk-banner.due { background: #fff6e5; border-left: 4px solid var(--warn); }
.bk-banner.overdue { background: #fdecec; border-left: 4px solid var(--bad); }
.bk-banner .bk-text { flex: 1 1 320px; }
.bk-banner .bk-detail { display: block; color: var(--muted); font-size: .83rem; margin-top: 2px; }
.bk-banner .bk-actions { display: flex; gap: 8px; flex: none; }

.tabs { display: flex; gap: 6px; padding: 12px 22px 0; flex-wrap: wrap; }
.tabs button {
  border: 1px solid var(--line);
  border-bottom: none;
  background: #EDF1F9;
  padding: 9px 16px;
  border-radius: 8px 8px 0 0;
  cursor: pointer;
  font-size: .95rem;
  color: var(--muted);
}
.tabs button.active { background: var(--panel); color: var(--ink); font-weight: 600; }

main { padding: 0 22px 40px; }
section.tab { display: none; background: var(--panel); border: 1px solid var(--line); border-radius: 0 var(--radius) var(--radius) var(--radius); padding: 20px; }
section.tab.active { display: block; }

/* ============================================================== application shell
   The /app screen uses a left sidebar (AdminLTE pattern): full-width header on top,
   navy sidebar under it on the left, the tab sections in a content column on the right.
   Everything below is scoped to `body.app-shell`, which only index.html carries:
   login.html, admin.html, warehouse.html and console.html share this stylesheet and
   keep their own single-column layouts — console.html still renders the .tabs row
   above, so none of those rules were changed or removed. */
.app-shell .shell { display: grid; grid-template-columns: 240px minmax(0, 1fr); min-height: calc(100vh - 60px); }
/* min-width:0 is what stops a wide table from widening the whole page: without it the
   grid column takes the table's max-content width and the body scrolls sideways. */
.app-shell .shell-main { min-width: 0; }
.app-shell main { padding: 16px 22px 40px; }
/* Full radius now that no tab button butts against the panel's top-left corner. */
.app-shell section.tab { border-radius: var(--radius); }

/* Sidebar carries the header's navy so header + sidebar read as one surface. The
   column stretches to the content height; the nav itself sticks to the top of it. */
.app-shell .app-sidebar { background: var(--deep); color: #fff; }
.app-shell .side-inner { position: sticky; top: 0; padding: 10px 0 18px; }
.app-shell .side-nav { display: flex; flex-direction: column; }
.app-shell .nav-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; min-height: 42px; padding: 9px 14px 9px 15px;
  border: 0; border-left: 3px solid transparent; background: none;
  font: inherit; font-size: .92rem; text-align: left;
  color: rgba(255, 255, 255, .76); cursor: pointer;
}
.app-shell .nav-item .nav-label { flex: 1 1 auto; min-width: 0; }
.app-shell .nav-item:hover { background: rgba(255, 255, 255, .07); color: #fff; }
/* Active row = the old active-tab treatment (raised out of the muted row, weight 600)
   turned on its side: a cyan accent bar and a lit row, both existing header tokens. */
.app-shell .nav-item.active { background: rgba(56, 189, 248, .13); border-left-color: var(--bright); color: #fff; font-weight: 600; }
.app-shell .app-sidebar :focus-visible { outline-color: var(--bright); outline-offset: -2px; }

/* Open-blocker counter beside a nav item: how many items on that page still block this
   product from a full pass. It wears the red REQUIRED tint of the fields it counts,
   because that is what every one of them is — required work — and it is a tally of the
   markers those pages already painted, not a second requiredness engine. No badge at
   zero. The amber DECLARED status (a declaration on file, values not yet measured) is a
   posture, not a to-do, and keeps its own colour on the status readouts. */
.app-shell .nav-badge {
  flex: none; min-width: 20px; padding: 1px 7px; border-radius: 99px;
  background: #fdecec; border: 1px solid #e0564f; color: #b23b34;
  font-size: .72rem; font-weight: 700; line-height: 1.5; text-align: center;
}
.app-shell .nav-badge[hidden] { display: none; }

/* Workspace group: the account-level destinations, fenced off from the section list. */
.app-shell .side-group { margin-top: 14px; padding-top: 12px; border-top: 1px solid rgba(255, 255, 255, .14); }
.app-shell .side-group-label {
  display: block; padding: 0 15px 6px;
  font-size: .7rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: rgba(255, 255, 255, .45);
}
.app-shell .side-link {
  display: flex; align-items: center; min-height: 40px; padding: 8px 14px 8px 15px;
  border-left: 3px solid transparent;
  color: rgba(255, 255, 255, .72); text-decoration: none; font-size: .89rem;
}
.app-shell .side-link:hover { background: rgba(255, 255, 255, .07); color: #fff; }
/* Icons, Suppliers and Team open sections of the app, so the link marks itself exactly as
   an active nav item does — same accent bar, same lit row, same weight. Warehouse leaves
   the app and never takes this class. */
.app-shell .side-link.active { background: rgba(56, 189, 248, .13); border-left-color: var(--bright); color: #fff; font-weight: 600; }

/* Off-canvas toggle — hidden until the sidebar actually goes off-canvas. */
.app-shell .nav-toggle {
  display: none; align-items: center; justify-content: center;
  width: 40px; height: 40px; flex: none; padding: 0;
  background: none; border: 1px solid rgba(255, 255, 255, .28); border-radius: 8px; cursor: pointer;
}
.app-shell .nav-toggle-bars { position: relative; }
.app-shell .nav-toggle-bars,
.app-shell .nav-toggle-bars::before,
.app-shell .nav-toggle-bars::after { display: block; width: 18px; height: 2px; background: #fff; border-radius: 2px; }
.app-shell .nav-toggle-bars::before { content: ''; position: absolute; top: -6px; }
.app-shell .nav-toggle-bars::after { content: ''; position: absolute; top: 6px; }
.app-shell .shell-backdrop { display: none; }

/* ------------------------------------------------------------ app shell on a phone */
@media (max-width: 900px) {
  .app-shell .nav-toggle { display: inline-flex; }
  .app-shell .shell { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  .app-shell .app-sidebar {
    position: fixed; top: 0; left: 0; bottom: 0; z-index: 120;
    width: 264px; max-width: 84vw;
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    transform: translateX(-100%); transition: transform .2s ease;
    box-shadow: 0 0 42px rgba(2, 8, 30, .45);
  }
  .app-shell.nav-open .app-sidebar { transform: translateX(0); }
  .app-shell .side-inner { position: static; padding-top: 14px; }
  .app-shell .shell-backdrop {
    display: block; position: fixed; inset: 0; z-index: 110;
    background: rgba(10, 30, 74, .5);
    opacity: 0; pointer-events: none; transition: opacity .18s ease;
  }
  .app-shell.nav-open .shell-backdrop { opacity: 1; pointer-events: auto; }
  /* Scroll lock: body overflow propagates to the viewport (html stays visible), so the
     page underneath cannot scroll while the drawer is open, and sticky still works. */
  .app-shell.nav-open { overflow: hidden; }
  /* The page itself must NEVER scroll sideways on a phone. `clip` rather than `hidden`
     on purpose: propagated to the viewport it forbids horizontal scrolling without
     turning <body> into a scroll container, so position:sticky (header, product bar)
     keeps working against the viewport. Wide tables still scroll inside their own
     .table-scroll / overflow-x:auto panels — this only fences the page edge.
     overflow-anchor:none makes the whole shell ineligible for scroll anchoring: when
     the product bar folds away on scroll-down (below), the browser must not "helpfully"
     rewrite scrollY to compensate — those synthetic scroll events read as user scrolls
     and would bounce the bar open and shut forever. Content below simply rises into the
     freed space, which is the intended collapse movement. */
  .app-shell { overflow-x: clip; max-width: 100%; overflow-anchor: none; }
  /* A <select> takes its longest option as an intrinsic minimum (the Documents and
     Supplier-request pickers carry 500px-long options); let every control inside a tab
     shrink to its container instead of pushing the page sideways. The deliberate
     min-widths inside scroll containers (#producer-matrix, #fi-comp-table) carry an id
     selector, so they still win and simply scroll within their panel. */
  .app-shell section.tab select, .app-shell section.tab input { max-width: 100%; min-width: 0; }
  /* Finnish builds single words no space ever breaks — "pakkausvaatimustenmukaisuus-
     vakuutus", "(elintarvikekontaktipakkaukset)" — and a word wider than its line
     overflows the block and takes the page with it on a 320px screen.
     break-word rather than anywhere on purpose: it breaks ONLY a word that would otherwise
     overflow and leaves min-content sizing untouched, so the wide tables inside
     .table-scroll keep their columns and go on scrolling instead of re-wrapping. */
  .app-shell section.tab { overflow-wrap: break-word; }
  /* The header stays reachable at every scroll position on a phone — above the product
     bar (z-index 30), below the drawer backdrop (110) and the drawer itself (120), so an
     open drawer still covers it. Desktop keeps its static, scroll-away header. */
  .app-shell header.site { position: sticky; top: 0; z-index: 100; padding: 10px 12px; gap: 10px; }
  .app-shell header.site nav { gap: 12px; }
  /* Identity is one nowrap line (company · email · role): ellipsis it rather than let it
     push the header — and with it the page — sideways on a 360px screen. */
  .app-shell #user-bar .ub-id { max-width: 52vw; overflow: hidden; text-overflow: ellipsis; padding-right: 12px; }
  .app-shell main { padding: 12px 12px 32px; }
  .app-shell section.tab { padding: 14px; }
  /* The product bar rides directly under the sticky header: its sticky top is the
     header's measured height (app.js keeps --app-header-h current through a
     ResizeObserver, because the header wraps to a variable height on phones). On
     scroll-down app.js adds .pb-collapsed and the bar folds away by HEIGHT — max-height
     plus paddings, never transform, which on a sticky element would paint it away
     without freeing the space — once the reader is past the header's own depth, and it
     comes back only once they are all the way back at the top of the document (the two
     depths are deliberately different numbers; app.js says why). A fold that reversed on
     every upward flick flickered under anyone correcting their scroll, and the picker
     inside is a control you reach for when you return to the top, not mid-page. The open
     max-height is a generous ceiling, not a measurement: the bar is never near 480px. */
  .app-shell .product-bar {
    padding: 8px 12px;
    top: var(--app-header-h, 56px);
    max-height: 480px;
    transition: max-height .28s ease, padding .28s ease;
  }
  .app-shell .product-bar.pb-collapsed {
    max-height: 0; padding-top: 0; padding-bottom: 0;
    border-bottom-width: 0; box-shadow: none; overflow: hidden;
  }
  /* The saved-product picker carries an inline max-width of 340px and a <select> takes
     its longest option as an intrinsic minimum — on a 360px screen that is what pushes
     the bar, and the page, sideways. Let both controls shrink and wrap instead. */
  .app-shell .product-bar input, .app-shell .product-bar select { max-width: 100%; min-width: 0; }
  .app-shell footer.site { padding: 16px 12px 28px; }
  .app-shell .doc-preview { padding: 14px; }
  /* iOS zooms the whole page when a focused control is under 16px; several dense
     controls here sit at .85–.9rem. Phone widths only — the desktop density stands. */
  .app-shell input, .app-shell select, .app-shell textarea { font-size: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  /* The product-bar collapse snaps instead of animating; the collapse itself stays. */
  .app-shell .app-sidebar, .app-shell .shell-backdrop, .app-shell .product-bar { transition: none; }
}
@media print {
  .app-shell .app-sidebar, .app-shell .nav-toggle, .app-shell .shell-backdrop { display: none; }
  .app-shell .shell { grid-template-columns: 1fr; }
}

h1, h2, h3 { font-family: var(--sans-head); letter-spacing: -.012em; }
h1 { font-size: 1.35rem; margin: 0 0 4px; }
h2 { font-size: 1.1rem; margin: 22px 0 8px; }
h3 { font-size: 1rem; margin: 16px 0 6px; }
p.lead { color: var(--muted); margin-top: 0; }

.grid { display: grid; gap: 18px; }
.grid.cols-2 { grid-template-columns: 1fr 1fr; }
.grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
/* Metric tiles: as many as fit, never fewer than ~200px wide, so the operator console
   reflows from four across to one without a breakpoint per count. */
.grid.cols-4 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
@media (max-width: 900px) { .grid.cols-2, .grid.cols-3 { grid-template-columns: 1fr; } }

label.field { display: block; margin: 10px 0; font-size: .9rem; font-weight: 600; }
label.field > input, label.field > select, label.field > textarea {
  display: block; width: 100%; margin-top: 4px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 7px;
  font: inherit; font-weight: 400; background: #fff; color: var(--ink);
}
label.inline { font-weight: 400; font-size: .9rem; display: inline-flex; align-items: center; gap: 6px; margin-right: 14px; }
label.field > small { font-weight: 400; color: var(--muted); margin-left: 5px; }
/* Side-by-side inputs (L × W × H). min-width:0 lets them shrink instead of
   forcing the whole page wider on a laptop-width window. */
.field-row { display: flex; gap: 6px; }
.field-row > input { min-width: 0; flex: 1 1 0; margin-top: 4px; }

/* Per-field help: an "i" beside the field title that reveals what belongs in the field
   and why it matters. Hover or keyboard focus — no click, so it never steals a tap. */
.hint-i {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; flex: none;
  margin-left: 6px; vertical-align: middle;
  border: 1px solid var(--line); border-radius: 50%;
  background: #fff; color: var(--muted);
  font: 700 italic .68rem/1 Georgia, "Times New Roman", serif;
  cursor: help; user-select: none;
}
.hint-i:hover, .hint-i:focus { border-color: var(--accent); background: var(--accent); color: #fff; outline: none; }
.hint-i:focus-visible { box-shadow: 0 0 0 2px rgba(37, 99, 235, .35); }
.hint-bubble {
  position: absolute; z-index: 60; display: none;
  top: calc(100% + 7px); left: -6px;
  width: max-content; max-width: min(340px, calc(100vw - 32px));
  padding: 8px 11px;
  background: var(--ink); color: #F2F6FD;
  border-radius: 7px; box-shadow: 0 6px 22px rgba(13, 38, 76, .28);
  font: 400 normal .78rem/1.45 -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  text-align: left; white-space: normal; pointer-events: none;
}
.hint-bubble::before {   /* --arrow is set per bubble so the tip stays under the icon */
  content: ''; position: absolute; top: -5px; left: var(--arrow, 7px);
  border: 5px solid transparent; border-top-width: 0; border-bottom-color: var(--ink);
}
.hint-i:hover .hint-bubble, .hint-i:focus .hint-bubble { display: block; }
textarea { min-height: 110px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .85rem; }

/* Searchable supplier picker: type to filter existing suppliers, or add a new one on
   purpose. Keeps one canonical name per supplier so portal links never fork on a typo. */
.combo { position: relative; }
.combo > input { display: block; width: 100%; margin-top: 4px; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: 7px; font: inherit; font-weight: 400; background: #fff; color: var(--ink); }
.combo-panel { position: absolute; z-index: 40; left: 0; right: 0; top: calc(100% + 2px);
  max-height: 260px; overflow-y: auto; background: #fff; border: 1px solid var(--line);
  border-radius: 8px; box-shadow: var(--shadow-md); }
.combo-opt { display: block; width: 100%; text-align: left; padding: 8px 11px; border: 0;
  background: none; font: inherit; cursor: pointer; color: var(--ink); }
.combo-opt small { color: var(--muted); font-weight: 400; }
.combo-opt:hover, .combo-opt.active { background: var(--tint, #E8F0FE); }
.combo-add { border-top: 1px solid var(--line); color: var(--accent); font-weight: 600; }
.combo-empty { padding: 8px 11px; color: var(--muted); font-size: .85rem; }

/* #tab-library a.btn: the retailer library's View / Download are links (a document opens in a
   new tab or downloads) and wear the button look. Scoped to that section on purpose — other
   anchors carrying .btn elsewhere keep exactly the look they have. */
button.btn, #tab-library a.btn {
  background: var(--accent); color: var(--accent-ink);
  border: none; border-radius: 7px; padding: 9px 16px;
  font: inherit; font-weight: 600; cursor: pointer;
  box-shadow: 0 1px 2px rgba(29, 78, 216, .22);
}
button.btn.secondary, #tab-library a.btn.secondary { background: #EDF1F9; color: var(--ink); border: 1px solid var(--line); box-shadow: none; }
button.btn.small, #tab-library a.btn.small { padding: 5px 10px; font-size: .85rem; }
/* Destructive action (delete current product) reads red, whatever its base variant. */
button.btn.danger { background: #fbe6e6; color: var(--bad); border: 1px solid #eeb4b4; box-shadow: none; }
button.btn.danger:hover { background: var(--bad); color: #fff; filter: none; }
button.btn:hover, #tab-library a.btn:hover { filter: brightness(1.08); }
/* align-items:center, not the flex default of stretch: a row is not only buttons — the
   Supplier requests row carries a wrapped explanation of why the send button is off, and
   under `stretch` every button on that line grew to the height of that five-line note.
   Controls in a row are uniform-height by nature, so centring them changes nothing else
   (.product-bar .btn-row has said exactly this since it was written). */
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin: 12px 0; }

/* The product action bar rides along the top of every tab (sticky), so the open
   product, the picker and the save status are always in view. Solid background
   first, then the translucent one — browsers without backdrop-filter keep an
   opaque bar rather than a see-through smear. */
.product-bar {
  position: sticky; top: 0; z-index: 30;
  background: var(--panel);
  background: rgba(255, 255, 255, .82);
  backdrop-filter: saturate(160%) blur(12px);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid var(--line);
  padding: 8px 22px; box-shadow: 0 2px 6px rgba(13, 38, 76, .06);
}
.product-bar .btn-row { margin: 0; align-items: center; }
@media print { .product-bar { display: none; } }

.preview-box {
  border: 1px dashed var(--line); border-radius: var(--radius);
  padding: 18px; display: flex; align-items: center; justify-content: center;
  min-height: 260px; background:
    repeating-conic-gradient(#F1F5FC 0 25%, #ffffff 0 50%) 0 0 / 24px 24px;
}
.preview-box svg { width: 220px; height: auto; }

/* Wide data tables scroll inside their own container rather than pushing the page
   sideways (the components table has 8 columns and overflows narrow grid cells). */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; max-width: 100%; }
/* Panels whose tables are generated by JS get the same treatment, so a wide matrix
   scrolls inside its panel instead of widening the page on a phone. #sup-spl-links and
   #sup-spl-inbox are the Supplier requests tab's product-scoped slice of the portal
   tables — the same render as #spl-links / #spl-inbox, so the same treatment. */
#matrix, #producer-matrix, #fi-fees, #audit-overview,
#pcrw-list, #spl-links, #spl-inbox,
#sup-spl-links, #sup-spl-inbox { overflow-x: auto; }
table.data { border-collapse: collapse; width: 100%; font-size: .9rem; }
#components { min-width: 720px; }
#components td, #components th { padding: 4px 5px; }
#components .num { width: 74px; }
#components input, #components select { min-width: 0; }
.grid > * { min-width: 0; }     /* let grid children shrink so tables scroll and input rows fit */
table.data th, table.data td { border: 1px solid var(--line); padding: 6px 8px; text-align: left; vertical-align: top; }
table.data th { background: #EDF2FB; }
table.data input, table.data select { width: 100%; border: 1px solid var(--line); border-radius: 5px; padding: 5px 6px; font: inherit; font-size: .88rem; }
table.data td.num { width: 90px; }
table.data .mini-icon svg { width: 44px; height: auto; display: block; }
/* The heavy-metal ledger's "plastic crate or pallet" tick: its own line above the derogation
   select, the label WRAPPING inside the cell (kept on one line, the Finnish label ran across the
   Sum column), and the box at its natural size, which `table.data input` (width:100%) takes away. */
#hm-table label.hm-crate { display: flex; align-items: flex-start; gap: 6px; margin: 0 0 4px; font-weight: 400; white-space: normal; }
#hm-table label.hm-crate input[type="checkbox"] { width: auto; flex: none; margin: 2px 0 0; }
/* With the label free to wrap, nothing else held the column open: give the select room for its
   own wording ("ei poikkeusta") rather than clipping it. */
#hm-table select[data-hmder] { min-width: 9.5em; }

.country-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 4px 10px; margin: 8px 0; }
.country-grid label { font-size: .85rem; font-weight: 400; display: flex; gap: 6px; align-items: center; }

.score-ring { display: flex; align-items: center; gap: 16px; margin: 10px 0; }
.score-num { font-size: 2.2rem; font-weight: 700; }
.checklist { list-style: none; padding: 0; margin: 8px 0; }
.checklist li { padding: 5px 0 5px 26px; position: relative; font-size: .92rem; border-bottom: 1px solid var(--line-soft); }
.checklist li::before { position: absolute; left: 0; font-weight: 700; }
.checklist li.pass::before { content: '✓'; color: var(--good); }
.checklist li.fail::before { content: '✕'; color: var(--bad); }
.checklist li.warn::before { content: '!'; color: var(--warn); }

.note { background: var(--accent-soft); border-left: 4px solid var(--accent); padding: 10px 12px; border-radius: 0 7px 7px 0; font-size: .88rem; margin: 8px 0; }
.note.warn { background: #FEF7EC; border-left-color: var(--warn); }
#producer-matrix .reg-open { display: inline-block; margin-top: 3px; font-size: .8rem; white-space: nowrap; }
.gs1-link-box { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 4px; }
#gs1-link { background: #F5F8FD; border: 1px solid var(--line); border-radius: 6px; padding: 5px 8px; font-size: .82rem; word-break: break-all; }
.badge { display: inline-block; padding: 2px 9px; border-radius: 99px; font-size: .75rem; font-weight: 700; }
.badge.good { background: #dcf5e4; color: var(--good); }
.badge.warn { background: #fbeddb; color: var(--warn); }
.badge.bad  { background: #fbdddd; color: var(--bad); }
.badge.info { background: var(--accent-soft); color: var(--accent-deep); }

.doc-preview {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: #fff; padding: 30px; max-height: 640px; overflow: auto;
  box-shadow: inset 0 0 30px rgba(13, 38, 76, .04);
}
.icon-sheet { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px; margin-top: 14px; }
.icon-card { border: 1px solid var(--line); border-radius: 8px; padding: 10px; text-align: center; background: #fff; }
/* Boxed panel, used by the operator console's metric tiles. */
.card { border: 1px solid var(--line); border-radius: 10px; padding: 16px 18px; background: #fff; box-shadow: var(--shadow-sm); }

/* Section cards — box each section of the long dossier / design forms so the page
   is grouped and easier to scan and fill. A light-grey panel on the white tab, with
   the section heading as its title. */
.section-card { background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 18px 16px; margin-bottom: 16px; }
.section-card > :first-child { margin-top: 0; }
.section-card > :last-child { margin-bottom: 0; }
.section-card > h2 { padding-bottom: 8px; border-bottom: 1px solid var(--line); }
/* Dossier: the editable form on the left, the static compliance read-outs on the right.
   align-items:start lets the two columns keep their own heights instead of stretching. */
.dossier-grid { align-items: start; }
.icon-card svg { width: 90px; height: auto; }
.icon-card .cap { font-size: .8rem; color: var(--muted); margin: 6px 0; word-break: break-word; }

footer.site { padding: 18px 22px 30px; color: var(--muted); font-size: .82rem; }

/* Pricing page */
.plans { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 18px; margin: 24px 0; }
.plan { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 22px; position: relative; }
.plan.popular { border-color: var(--accent); border-width: 2px; }
.plan .flag { position: absolute; top: -12px; left: 50%; transform: translateX(-50%); background: var(--accent); color: #fff; font-size: .75rem; font-weight: 700; padding: 3px 12px; border-radius: 99px; }
.plan h3 { margin: 0; font-size: 1.15rem; }
.plan .price { font-size: 2rem; font-weight: 700; margin: 8px 0; }
.plan .price small { font-size: .9rem; color: var(--muted); font-weight: 400; }
.plan ul { padding-left: 18px; font-size: .9rem; }
.plan ul li { margin: 6px 0; }
.feature-matrix td:first-child { width: 60%; }
.center { text-align: center; }

/* ---------------------------------------------------------------- guided setup
   Full-screen wizard. Explanations sit above every control rather than behind the
   "i", because here the explanation is the point. */
.wz-overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(10, 30, 74, .55);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.wz {
  background: #fff; border-radius: 14px; width: 100%; max-width: 760px;
  max-height: calc(100vh - 48px); display: flex; flex-direction: column;
  box-shadow: 0 24px 70px rgba(2, 8, 30, .35); overflow: hidden;
}
.wz-head { padding: 0 28px 18px; border-bottom: 1px solid var(--line); }
.wz-bar { height: 4px; background: #E4EBF7; margin: 0 -28px 16px; }
.wz-bar i { display: block; height: 100%; background: var(--accent); transition: width .25s ease; }
.wz-top { display: flex; align-items: center; justify-content: space-between; }
.wz-count { font-size: .78rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--accent); }
.wz-x { background: none; border: none; font-size: 1.1rem; color: var(--muted); cursor: pointer; padding: 4px 6px; line-height: 1; }
.wz-x:hover { color: var(--ink); }
.wz h2.wz-h { font-size: 1.35rem; margin: 6px 0 6px; letter-spacing: -.01em; }
.wz-intro { margin: 0; color: var(--muted); font-size: .93rem; line-height: 1.5; }
.wz-intro:empty { display: none; }
.wz-body { padding: 22px 28px; overflow-y: auto; flex: 1; }
.wz-foot {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 28px; border-top: 1px solid var(--line); background: #F7FAFE;
}
.wz-foot .wz-next { margin-left: auto; }
.wz-foot .wz-skip { margin-left: auto; }
.wz-foot .wz-skip + .wz-next { margin-left: 0; }
.wz-msg { color: var(--bad); font-size: .88rem; }

.wz-field { margin: 0 0 22px; }
.wz-field > label { display: block; font-weight: 650; font-size: .97rem; margin-bottom: 4px; }
.wz-req { color: var(--bad); }
.wz-help { margin: 0 0 8px; color: var(--muted); font-size: .85rem; line-height: 1.45; }
.wz-field input, .wz-field select, .wz-field textarea, .wz-part input, .wz-part select {
  width: 100%; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px;
  font: inherit; font-size: .95rem; background: #fff; color: var(--ink);
}
.wz-field input:focus, .wz-field select:focus, .wz-field textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.wz-lwh { display: flex; gap: 8px; }
.wz-lwh input { min-width: 0; flex: 1 1 0; }

.wz-check { display: flex; align-items: flex-start; gap: 9px; margin: 8px 0; font-size: .94rem; cursor: pointer; }
.wz-check input { width: auto; margin-top: 3px; flex: none; }
.wz-check.big { font-weight: 650; }
/* A checkbox row inside a field group is an option, not a heading — the bold child-label
   rule above must not reach it (Annex V bans render every option as .wz-field > label). */
.wz-field > label.wz-check { font-weight: 400; }

.wz-chips { display: flex; flex-wrap: wrap; gap: 7px; }
.wz-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 11px; border: 1px solid var(--line); border-radius: 99px;
  font-size: .87rem; cursor: pointer; background: #fff; user-select: none;
}
.wz-chip.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-deep); font-weight: 600; }
.wz-chip input { width: auto; margin: 0; }

.wz-part { border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; margin: 0 0 12px; background: #F9FBFE; }
.wz-part-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-size: .9rem; }
.wz-part-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.wz-part-grid.four { grid-template-columns: repeat(4, 1fr); }
.wz-part-grid label { font-size: .82rem; font-weight: 600; color: var(--muted); display: block; }
.wz-part-grid input, .wz-part-grid select { margin-top: 3px; font-size: .9rem; }
.wz-del { background: none; border: none; color: var(--bad); font-size: .84rem; cursor: pointer; padding: 2px 4px; }
.wz-add {
  background: var(--accent-soft); border: 1px dashed var(--accent); color: var(--accent-deep);
  border-radius: 9px; padding: 9px 16px; font: inherit; font-weight: 600; cursor: pointer; width: 100%;
}

.wz-qa { display: grid; grid-template-columns: 1fr 110px; gap: 10px; align-items: center; margin: 9px 0; font-size: .92rem; }
.wz-qa select { width: 100%; padding: 7px 9px; border: 1px solid var(--line); border-radius: 8px; font: inherit; font-size: .9rem; }

.wz-note { background: var(--accent-soft); border-left: 4px solid var(--accent); padding: 9px 12px; border-radius: 0 7px 7px 0; font-size: .88rem; margin: 10px 0 0; }
.wz-note.warn { background: #FEF7EC; border-left-color: var(--warn); }
.wz-score { display: flex; align-items: center; gap: 16px; margin-bottom: 14px; }
.wz-score-n { font-size: 2.4rem; font-weight: 800; color: var(--accent); line-height: 1; }

@media (max-width: 720px) {
  .wz-overlay { padding: 0; }
  .wz { max-width: none; max-height: 100vh; height: 100vh; border-radius: 0; }
  .wz-head { padding: 0 18px 14px; }
  .wz-bar { margin: 0 -18px 14px; }
  .wz-body { padding: 18px; }
  .wz-foot { padding: 12px 18px; flex-wrap: wrap; }
  .wz-part-grid, .wz-part-grid.four { grid-template-columns: 1fr; }
  .wz-qa { grid-template-columns: 1fr; gap: 4px; }
}

/* FAQ accordion (Documents tab) */
details.faq{border:1px solid var(--line);border-radius:8px;margin:6px 0;padding:0 12px;background:var(--card, #fff)}
details.faq summary{cursor:pointer;font-weight:600;padding:10px 0;font-size:.92rem}
details.faq p{margin:0 0 12px;font-size:.88rem;line-height:1.55;color:var(--muted, #444)}

/* National producer-responsibility accordion (#tab-epr): one collapsible card per selected
   market, mirroring the FAQ accordion above and the supplier portal's product rows. Collapsed,
   the sections read as a clean scannable list — the country on the left, its producer status on
   the right; expanded, one holds the whole per-country content block (#epr-content). No fixed
   widths: the wide tables inside keep their own .table-scroll, so this stays within the mobile
   width guard. The summary carries NO background of its own, so the .f-req-box red an unanswered
   gate paints onto it shows through as the frozen traffic light. */
details.epr-country{border:1px solid var(--line);border-radius:8px;margin:6px 0;background:var(--card, #fff)}
details.epr-country > summary{display:flex;align-items:center;gap:10px;justify-content:space-between;
  cursor:pointer;padding:11px 14px;font-weight:600;font-size:.95rem;list-style:none}
details.epr-country > summary::-webkit-details-marker{display:none}
details.epr-country > summary::after{content:"";flex:0 0 auto;width:7px;height:7px;margin-left:2px;
  border-right:2px solid var(--muted, #888);border-bottom:2px solid var(--muted, #888);
  transform:rotate(-45deg);transition:transform .15s}
details.epr-country[open] > summary::after{transform:rotate(45deg)}
details.epr-country > summary .epr-country-name{flex:1 1 auto;min-width:0}
details.epr-country > summary .epr-country-status{flex:0 0 auto;font-weight:500}
details.epr-country[open]{padding-bottom:2px}
details.epr-country > #epr-content{padding:0 12px 8px}

/* Traffic-light field states, mirroring the supplier portal so the same rule reads the same on
   both sides: a field REQUIRED for a complete/compliant record glows red while empty; a field
   covered by a declaration on file but not yet fully verified (heavy metals attested by an Art 5
   declaration, measured values not entered) glows amber; a satisfied — or genuinely optional —
   field stays white, so the form never becomes a rainbow. */
input.f-req, select.f-req, textarea.f-req { background: #fdecec; border-color: #e0564f; }
input.f-req::placeholder, textarea.f-req::placeholder { color: #c0524d; }
input.f-req:focus, select.f-req:focus, textarea.f-req:focus { outline: 2px solid #e0564f; border-color: #c53b34; }
input.f-declared, select.f-declared, textarea.f-declared { background: #fff6e5; border-color: #e3a534; }
input.f-declared:focus, select.f-declared:focus, textarea.f-declared:focus { outline: 2px solid #e3a534; border-color: #c98a1a; }
/* A control that is a DIRECT child of a field label (e.g. the Finland producer-operator gate
   select, and its limb selects) is painted by `label.field > input/select/textarea` above,
   whose specificity (0,1,2) beats the bare `.f-req`/`.f-declared`/`.f-na` selectors (0,1,1) —
   so without these the required red / declared amber / N-A grey never showed on those fields.
   Re-assert each state at matching specificity for that case; the colours are the frozen
   traffic-light hues, unchanged. */
label.field > input.f-req, label.field > select.f-req, label.field > textarea.f-req { background: #fdecec; border-color: #e0564f; }
label.field > input.f-req:focus, label.field > select.f-req:focus, label.field > textarea.f-req:focus { outline: 2px solid #e0564f; border-color: #c53b34; }
label.field > input.f-declared, label.field > select.f-declared, label.field > textarea.f-declared { background: #fff6e5; border-color: #e3a534; }
label.field > input.f-declared:focus, label.field > select.f-declared:focus, label.field > textarea.f-declared:focus { outline: 2px solid #e3a534; border-color: #c98a1a; }
/* The same case inside a data table (the Dossier parts table, the heavy-metal ledger):
   `table.data input/select` sets a neutral border at (0,1,2), which beat the bare state
   selectors, so a required material or weight there showed only the pink fill and no red
   border. Re-asserted at matching specificity; the frozen hues, unchanged. */
table.data input.f-req, table.data select.f-req, table.data textarea.f-req { border-color: #e0564f; }
table.data input.f-req:focus, table.data select.f-req:focus, table.data textarea.f-req:focus { outline: 2px solid #e0564f; border-color: #c53b34; }
table.data input.f-declared, table.data select.f-declared, table.data textarea.f-declared { border-color: #e3a534; }
table.data input.f-declared:focus, table.data select.f-declared:focus, table.data textarea.f-declared:focus { outline: 2px solid #e3a534; border-color: #c98a1a; }

/* The same red REQUIRED marking for blockers that are not a single form control: a table
   or list row (an expired evidence record, an outstanding supplier item), a result note
   whose fix is a decision rather than a field (an Annex V ban, a grade under the floor),
   and an input group that is required as a whole (the target-market grid). Written after
   .note.warn so a blocking note reads red rather than amber. */
tr.f-req-row > td, li.f-req-row { background: #fdecec; }
tr.f-req-row > td:first-child, li.f-req-row { box-shadow: inset 3px 0 0 #e0564f; }
li.f-req-row { padding: 3px 6px 3px 9px; border-radius: 0 5px 5px 0; list-style-position: inside; }
.note.f-req-note { background: #fdecec; border-left-color: #e0564f; }
.f-req-box { background: #fdecec; border: 1px solid #e0564f; border-radius: 7px; padding: 6px; }

/* Legend for the field states above, as small colour swatches matching the field tints.
   Its text is written by applyFieldLegend() so it says what the marks now mean: red is
   action still owed, amber is a declared status awaiting confirmation. */
.field-legend { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin: 2px 0 10px; font-size: .78rem; }
.field-legend .fl { border-radius: 99px; padding: 2px 9px; border: 1px solid; white-space: nowrap; }
.fl-req { background: #fdecec; border-color: #e0564f; color: #b23b34; }
.fl-declared { background: #fff6e5; border-color: #e3a534; color: #9a6a12; }
.fl-good { background: #dcf5e4; border-color: #7cc496; color: var(--good); }
.fl-na { background: #F2F5FA; border-color: #CDD6E4; color: var(--muted); }
.fl-ok { background: #fff; border-color: #CDD6E4; color: var(--muted); }

/* ==================================================================== dashboard
   The landing section. Deliberately plain surfaces: a tile is a neutral card and only
   the NUMBER may carry a status colour, and only where the number means a status.
   The three status colours are the frozen compliance traffic light — red = gap /
   required, amber = attention / awaiting, green = ready / on file — reused here with
   exactly those meanings and never as decoration. Colour is never the only carrier:
   every coloured number, segment and dot sits beside its own text label and count.

   .is-signature is the ONE dashboard status that is not on the traffic light, and it
   is deliberately painted in the brand accent blue rather than in any of the four
   frozen colours. It means "complete — the only act left is our own signature on our
   own Declaration of Conformity". Amber would have claimed something is awaited from
   someone else, green would have claimed the paper is issued, red would have claimed a
   gap and white is 'done' — all four would be a lie, and borrowing one would blur a
   meaning the rest of the app depends on. Blue is already the app's "this is yours to
   act on" colour (links, focus, primary buttons), which is exactly what this is. */
#dash-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin: 4px 0 18px; }
.dash-tile { background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px; }
.dash-num { font-family: var(--sans-head); font-size: 1.9rem; font-weight: 700; line-height: 1.1; letter-spacing: -.02em; }
.dash-num.is-gap { color: var(--bad); }
.dash-num.is-attention { color: var(--warn); }
.dash-num.is-ready { color: var(--good); }
.dash-num.is-signature { color: var(--accent); }
.dash-label { font-size: .8rem; color: var(--muted); margin-top: 2px; }

/* Stacked status bar. Plain divs — no axes, no library. The 2px flex gap is the only
   separator, so adjacent segments never blur into one another. */
.dash-bar { display: flex; gap: 2px; height: 22px; margin: 4px 0 10px; }
.dash-bar .dash-seg { border-radius: 3px; min-width: 4px; border: 0; padding: 0; cursor: pointer; }
.dash-bar .dash-seg.is-gap { background: var(--bad); }
.dash-bar .dash-seg.is-attention { background: var(--warn); }
.dash-bar .dash-seg.is-ready { background: var(--good); }
.dash-bar .dash-seg.is-signature { background: var(--accent); }
.dash-bar .dash-seg[aria-pressed="true"] { outline: 2px solid var(--ink); outline-offset: 2px; }
.dash-legend { display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: center; font-size: .86rem; }
.dash-legend button { background: none; border: 0; padding: 2px 0; font: inherit; color: var(--ink); cursor: pointer; display: inline-flex; align-items: center; gap: 7px; }
.dash-legend button[aria-pressed="true"] { font-weight: 700; text-decoration: underline; }
.dash-dot { width: 11px; height: 11px; border-radius: 99px; flex: none; }
.dash-dot.is-gap { background: var(--bad); }
.dash-dot.is-attention { background: var(--warn); }
.dash-dot.is-ready { background: var(--good); }
.dash-dot.is-signature { background: var(--accent); }

/* Two columns once there is room for two readable ones; one column below that. */
.dash-grid { display: grid; grid-template-columns: 1fr; gap: 0 18px; }
/* `1fr` is minmax(auto, 1fr), so a column's automatic minimum is its content's MIN-CONTENT:
   one long unbreakable filename or a four-column review table inside a card widens the
   column past the viewport and takes the page with it. min-width:0 lets the column shrink
   to the grid instead, so wide content scrolls inside its own .table-scroll. Exactly what
   `.grid > *` does for .grid — .dash-grid is its own class and was missing it. */
.dash-grid > * { min-width: 0; }
@media (min-width: 1100px) { .dash-grid { grid-template-columns: 1fr 1fr; } }

/* Issue / certificate rows: a whole row is the door to the product it names. */
.dash-row { display: flex; align-items: flex-start; gap: 10px; width: 100%; text-align: left;
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  padding: 9px 11px; margin: 6px 0; font: inherit; cursor: pointer; }
.dash-row:hover { border-color: var(--accent); }
/* A row that states a fact rather than opening something — the deadline list. */
.dash-row.dash-static { cursor: default; }
.dash-row.dash-static:hover { border-color: var(--line); }
.dash-row .dash-dot { margin-top: 5px; }
.dash-row b { font-size: .92rem; }
.dash-row small { color: var(--muted); display: block; font-size: .8rem; }
.dash-row .dash-open { margin-left: auto; flex: none; font-size: .8rem; color: var(--muted); white-space: nowrap; }
.dash-sub { margin-top: 14px; }
.dash-sub > summary { cursor: pointer; font-size: .88rem; font-weight: 600; padding: 4px 0; }
.dash-list { list-style: none; padding: 0; margin: 6px 0 0; font-size: .85rem; }
.dash-list li { padding: 4px 0; border-bottom: 1px solid var(--line-soft); }
.dash-kinds { display: flex; flex-wrap: wrap; gap: 8px 16px; font-size: .86rem; margin: 6px 0 10px; }

/* ================================================================= empty state
   Shown in place of the product-scoped sections when nothing is open. The sections
   themselves are only hidden, never emptied, so selecting a product restores them. */
#empty-state .empty-card { background: var(--panel); }
#empty-state h2 { margin-top: 0; border: 0; padding-bottom: 0; }
body.no-product section.tab.product-scoped { display: none; }

/* A field that is NOT APPLICABLE for this product — greyed, never red. Distinct from the
   traffic light above: it says "there is nothing to measure here", not "this is satisfied"
   and certainly not "this is required". */
input.f-na, select.f-na { background: #F2F5FA; border-color: var(--line); color: var(--muted); }
label.field > input.f-na, label.field > select.f-na { background: #F2F5FA; border-color: var(--line); color: var(--muted); }
input.f-na::placeholder { color: #97A5B8; }

/* ============================================================ workspace lightbox
   The Suppliers screen's two popovers (a party's declarations, and the products naming
   it). Deliberately the same surface as the wizard's own lightbox — white card, blue-cast
   scrim, one shadow — so a dialog reads the same wherever it opens. */
.app-modal-overlay {
  position: fixed; inset: 0; z-index: 210;
  background: rgba(10, 30, 74, .6);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.app-modal {
  background: #fff; border-radius: 14px; width: 100%; max-width: 720px; padding: 22px;
  box-shadow: 0 24px 70px rgba(2, 8, 30, .35);
  max-height: calc(100vh - 48px); overflow: auto;
}
.app-modal-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; gap: 12px; }
.app-modal-head h3 { margin: 0; font-size: 1.15rem; }
.app-modal-x { background: none; border: none; font-size: 1.1rem; color: var(--muted); cursor: pointer; padding: 4px 6px; line-height: 1; }
.app-modal-x:hover { color: var(--ink); }
.app-modal-body table.data input, .app-modal-body table.data select { width: 100%; }

/* The Tietopyynnöt cell on the Suppliers table: the pills are the control, so the button
   around them carries no chrome of its own. */
.linkish-cell {
  background: none; border: 0; padding: 0; margin: 0; font: inherit; text-align: left;
  cursor: pointer; color: inherit;
}
.linkish-cell:hover .badge { filter: brightness(.96); }
.linkish-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }

/* Arriving at a table from elsewhere: pick the row out briefly, then let it settle. */
@keyframes row-flash-fade { from { background: var(--accent-soft); } to { background: transparent; } }
tr.row-flash > td { animation: row-flash-fade 2.2s ease-out; }
@media (prefers-reduced-motion: reduce) {
  tr.row-flash > td { animation: none; background: var(--accent-soft); }
}

/* ------------------------------------------------- wizard: saved-partner fields
   A picker over the saved suppliers/factories with an escape hatch to free text, so the
   ordinary path cannot spawn a near-duplicate registry row from a typo. */
.wz-partner-pick { display: flex; gap: 8px; align-items: center; }
.wz-partner-pick select { flex: 1; min-width: 0; }
.wz-partner-pick .btn { flex: none; }
.wz-partner-manual { margin-top: 8px; }
.wz-partner-toggle { margin-top: 6px; }
.wz-linkish {
  background: none; border: 0; padding: 0; font: inherit; font-size: .85rem;
  color: var(--accent); cursor: pointer; text-decoration: underline;
}
.wz-linkish:hover { color: var(--accent-deep); }

/* ------------------------------------- Suppliers and factories: the merged tree
   One table, two levels: a supplier row, and — indented under it — every factory its
   products are ordered through. The chevron is a real button so a group can be opened
   from the keyboard and announces its own state; the gap keeps a supplier with no
   factories aligned with the ones that have them. */
.sf-chevron {
  background: none; border: 0; padding: 0; margin-right: 6px;
  font: inherit; line-height: 1; color: var(--muted); cursor: pointer;
}
.sf-chevron span { display: inline-block; transition: transform .15s ease; }
.sf-chevron.is-open span { transform: rotate(90deg); }
.sf-chevron:hover { color: var(--ink); }
.sf-chevron:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.sf-chevron-gap { display: inline-block; width: 1em; margin-right: 6px; }
/* A closed group keeps its rows in the DOM — folding is a display change, never a
   re-render, so nothing typed elsewhere in the table is lost by opening a row. */
table.data tr.sf-child[hidden] { display: none; }
table.data tr.sf-child > td { background: var(--bg); }
table.data tr.sf-child > td.sf-name { padding-left: 22px; }
table.data tr.sf-child .sf-tick { color: var(--muted); margin-right: 6px; }
table.data tr.sf-group > td { background: #EDF2FB; font-weight: 600; }
@media (prefers-reduced-motion: reduce) { .sf-chevron span { transition: none; } }

/* ------------------------------------------------------- product image card */
/* The packaging shot beside what it says about itself — where it came from, and why THAT
   image was chosen out of the product's gallery. The picture is contained rather than
   cropped: a shop photo can be any shape, and a card that changed height as products were
   opened would make the dossier column jump. */
.prod-image-row { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; }
.prod-image-thumb {
  width: 168px; height: 168px; object-fit: contain;
  background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 6px;
}
.prod-image-meta { flex: 1 1 170px; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.prod-image-meta small { color: var(--muted); }
.prod-image-meta .btn-row { margin: 0; }
@media (max-width: 560px) { .prod-image-thumb { width: 100%; height: 210px; } }

/* A note reporting a completed action — the green of the compliance traffic light, used
   here in its frozen sense: this passed, it is done. .note.warn already covers "declared,
   awaiting evidence"; this is its counterpart for "sent, on the wire". */
.note.good { background: #EFF7F1; border-left-color: var(--good); }
/* A button that is off because the server cannot do the thing, not because the form is
   incomplete: greyed and unclickable, with the reason spelled out beside it. */
.btn:disabled, .btn[disabled] { opacity: .55; cursor: not-allowed; }

/* A row of tick-boxes that has to stay aligned however long one of the labels runs. The
   GROUP is the flex container, so each label is one wrapping unit with the same gap: a
   label long enough to wrap now pushes the whole row down instead of dropping its own box
   out of line, and the box itself never shrinks or drifts to the middle of a two-line
   label. Used by any `.inline` group, not just the beverage/food-contact/Art 24(5) trio. */
.checkbox-group { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 6px 16px; }
.checkbox-group > label.inline { margin-right: 0; align-items: flex-start; max-width: 100%; }
.checkbox-group > label.inline > input[type="checkbox"] { margin-top: .15em; }
label.inline > input[type="checkbox"] { flex: 0 0 auto; }

/* The product image, repeated as a thumbnail beside the identity block at the top of the
   dossier — the fastest answer to "which of these four near-identical SKUs is open?".
   It is a button, because it goes somewhere: the Product image card, where the picture is
   replaced or removed. Hidden entirely when the product has no image, so an empty frame
   never sits at the top of the page. */
.card-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; }
.card-head-row > h2 { flex: 1 1 auto; min-width: 0; }
/* A head-row action (Product card: "Copy fields to other products…") sits level with the
   h2's text — the h2 keeps its 22px top margin inside the row — and wraps rather than squeezing it. */
.card-head-row > .btn { flex: 0 1 auto; margin-top: 19px; }
.identity-thumb {
  flex: 0 0 auto; padding: 3px; border: 1px solid var(--line); border-radius: 8px;
  background: #fff; cursor: pointer; line-height: 0;
}
.identity-thumb:hover, .identity-thumb:focus-visible { border-color: var(--accent); }
.identity-thumb img { width: 62px; height: 62px; object-fit: contain; display: block; }
/* Where the thumbnail sends you: a brief ring on the image card so the eye lands on it. */
@keyframes goto-flash { from { box-shadow: 0 0 0 3px rgba(37,99,235,.35); } to { box-shadow: 0 0 0 3px rgba(37,99,235,0); } }
.goto-flash { animation: goto-flash 1.1s ease-out 1; border-radius: 8px; }

/* A table cell that is NOT applicable rather than merely empty — greyed, in the same
   sense as the .f-na inputs: neither required (red) nor declared (amber), just nothing
   to answer here yet. Used by the Finnish channel table for a part with no material. */
td.f-na-cell { background: #F2F5FA; color: var(--muted); }

/* The Kanava select carries the derived default as its first option ("oletus (yritys)"),
   which is longer than a bare "kuluttaja" — the column has to be wide enough to read it,
   or a default shown as a default is a default clipped to nonsense. The table already
   scrolls inside .table-scroll if that makes it wider than its column. */
#fi-comp-table select[data-fich] { min-width: 11em; }

/* The per-market registration status: the B2B option spells out WHY nothing is registered
   there ("Ei koske — ostaja on tuottaja"), which does not fit the width a three-word status
   needed. The producer matrix scrolls inside .table-scroll if this widens it. */
#producer-matrix select[data-reg-status] { min-width: 15em; }
#producer-matrix input[data-reg-note] { min-width: 14em; }

/* Art 55(1) end-user information editor (Documents tab, END-USER INFORMATION only).
   Six blocks, each prefilled from the product's own data. The derived text is the
   textarea PLACEHOLDER, so an empty box reads as "this is what will be published" —
   which is exactly what it means. Deliberately carries no traffic-light colour: Art 55
   is a publication duty, not a conformity gate, and nothing here may read as a gap. */
.a55-card { border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px 4px; margin: 10px 0 14px; }
.a55-block { margin-bottom: 14px; }
.a55-block > label.field { margin-bottom: 4px; }
.a55-block textarea { min-height: 92px; font-family: inherit; font-size: .88rem; line-height: 1.5; }
.a55-block textarea::placeholder { color: var(--muted); opacity: 1; }
.a55-foot { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: .8rem; color: var(--muted); }
.a55-derived { margin-top: 6px; font-size: .8rem; color: var(--muted); }
.a55-derived > summary { cursor: pointer; }
.a55-derived > div { margin-top: 4px; padding: 8px 10px; border-left: 3px solid var(--line); white-space: pre-wrap; }

/* Subscription panel (Team). The usage meter is a HEADROOM readout, not a compliance
   state: it never borrows the frozen traffic-light colours. Neutral while there is room,
   the app's attention amber once the cap is reached — the same amber, and the same
   meaning, as the plan chip in the product bar. */
.plan-meter { height: 8px; border-radius: 999px; background: var(--line-soft); margin-top: 6px; overflow: hidden; }
.plan-meter > span { display: block; height: 100%; background: var(--muted); }
.plan-meter > span.over { background: var(--warn); }

/* Document-language order (Team). A multi-select tall enough to pick three or four
   languages without scrolling for them. */
#tm-doclang-order { min-height: 9.5em; padding: 4px 6px; }

/* Art 19(2)(a) register-check log (Scope & role). The outcome readout carries the frozen
   traffic light exactly as elsewhere: green = verified and on file, red = required work /
   a gap that bars making the packaging available. The staleness hint is deliberately
   NEITHER — it is information about age, not a state, so it is plain muted text. */
/* One block per market, not a table row: this card lives in the narrow right-hand column
   beside the producer matrix, and six columns of inputs would put the outcome — the thing
   that matters — behind a horizontal scrollbar. */
#reg-check-panel .rc-market { border: 1px solid var(--line); border-radius: var(--radius); }
/* A market whose lookup came back bad — or has none while the duty is open — wears the red
   REQUIRED treatment .f-req-box already defines (background and border). Only the box
   metrics are set here, so the frozen colour is never restated or overridden. */
#reg-check-panel .rc-market, #reg-check-panel .f-req-box { padding: 10px 12px 4px; margin: 10px 0; }
#reg-check-panel .rc-head { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 6px; }
#reg-check-panel .rc-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 0 12px; }
#reg-check-panel input, #reg-check-panel select { width: 100%; }
#reg-check-panel .rc-log { font-size: .82rem; color: var(--muted); margin: 4px 0 8px; }
/* The staleness hint is deliberately NOT one of the traffic-light colours: it is
   information about age, not a compliance state. */
#reg-check-panel .rc-stale { color: var(--muted); font-style: italic; }
#reg-check-panel .reg-open { font-size: .8rem; }

/* Registry-owned wizard fields (supplier contact + address when a saved partner is
   picked): the value belongs to the supplier registry record and is edited there, so the
   input reads as informational — grey ground, muted ink — rather than as a place to type.
   Appended for the partner-step lock; no traffic-light colour is involved on purpose. */
.wz input.wz-reglocked[readonly] {
  background: var(--line-soft);
  border-color: var(--line);
  color: var(--ink-soft);
  cursor: default;
}

/* ------------------------------------------------------------------ #156 weight calc */
/* The "Calculate from dimensions" affordance under a part's weight field, and the
   calculator panel itself (app modal body + the wizard's inline panel). Neutral styling:
   the estimate warning is an ordinary note, not a traffic-light state. */
#components .wc-btn { display: block; margin-top: 4px; font-size: .7rem; padding: 2px 6px; white-space: nowrap; }
.wc-panel .grid, .wz-wc-panel .grid { margin: 6px 0; }
.wz-wc-panel {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 12px;
  margin-top: 8px;
  background: var(--line-soft);
}
.wz-wc-panel .field span { font-weight: 600; font-size: .82rem; }
.wz-wc-panel input, .wz-wc-panel select { width: 100%; }
/* #157: the live identifier status beside the wizard's SKU / EAN labels. */
.wz [data-wz-ident] .badge { margin-left: 6px; }

/* ---------------------------------------------------------- packaging versions */
/* The version switcher in the product bar and the superseded-version banner. Both are
   deliberately archival slate, NOT a traffic-light colour: being a superseded version is
   retention status, never a conformity posture (red/amber/green keep their frozen
   meaning). The banner sits under the sticky product bar so it travels with every tab. */
.version-switcher {
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 7px;
  max-width: 220px;
  background: var(--line-soft);
  color: var(--ink-soft);
  font-weight: 600;
}
.version-banner {
  margin: 8px 0 2px;
  border: 1px solid #B9C4D6;
  border-left: 5px solid var(--ink-soft);
  border-radius: var(--radius);
  background: #EEF2F8;
  color: var(--ink);
  padding: 10px 14px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  font-size: .9rem;
}
.version-banner b { white-space: nowrap; }
.version-banner .vb-date {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: .85rem;
}
.version-banner .vb-date input[type="date"] {
  padding: 4px 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  font-size: .85rem;
}
.version-banner a { font-weight: 700; white-space: nowrap; }
.version-banner small { flex-basis: 100%; color: var(--muted); }

/* ------------------------------------------------------------------ bulk import preview
   The consent step on the Bulk tab (#bd-status): summary badge, per-product change
   table (old value struck through, new value bold) and the apply/cancel buttons. */
.bulk-diff td { vertical-align: top; font-size: .85rem; }
.bulk-field { color: var(--muted); font-weight: 600; margin-right: 2px; }
.bulk-old { color: var(--muted); text-decoration: line-through; }
.bulk-new { font-weight: 700; }
.bulk-nochange { color: var(--muted); font-style: italic; }
.bulk-vtag { color: var(--muted); font-size: .8em; white-space: nowrap; }

/* ------------------------------------------------------------ Rinki period allowance
   A FILED period's grams-per-unit field (#rk-loose-g): the frozen figure its kilograms rest
   on, shown but not editable until the period is reopened as a draft. Scoped to this one
   field, so every other disabled input keeps the browser's own look, and neutral on purpose —
   no traffic-light colour, because "read-only" is not a compliance state. */
label.field > #rk-loose-g:disabled {
  background: var(--line-soft);
  border-color: var(--line);
  border-style: dashed;
  color: var(--ink-soft);
  -webkit-text-fill-color: var(--ink-soft);
  opacity: 1;
  cursor: not-allowed;
}

/* ------------------------------------------------ supply chain: supplier mode (Team page)
   A factory-only workspace on the free chain plan (auth-ui.js sets body[data-chain-mode]) has
   no product feeds, no WooCommerce link sync and no Finnish or national producer-responsibility
   modules (decision 4), so the Team cards for them are taken off the page rather than left to
   offer settings the plan does not carry. Matched by the controls each card holds, because the
   cards carry no ids of their own; every other Team card — members, roles, notifications,
   document languages, the audit log — stays. */
body[data-chain-mode="supplier"] #tab-team .section-card:has(#tm-fi-save),
body[data-chain-mode="supplier"] #tab-team #tm-epr,
body[data-chain-mode="supplier"] #tab-team .section-card:has(#tm-feed-list),
body[data-chain-mode="supplier"] #tab-team .section-card:has(#woo-url) { display: none !important; }

/* ------------------------------------------- supply chain: retailer mode, Team page (U3)
   A retailer-only workspace is a read-only document library (decisions 10, 23, 25): it has no
   product dossiers, so the Team page cards that configure products and their Finnish / other
   national producer responsibility — the product feed import, the WooCommerce link sync, the
   company-level Finland and other-country cards, and the document-language order — do not
   apply and are taken away. Members, the chain roles and notification switches, the audit log
   and the workspace deletion stay. body[data-chain-mode] is set by auth-ui.js applyModeChrome,
   so the rule follows a role change without a reload; the cards carry no ids of their own, so
   each is found by the control it contains (test/chain-library.test.js checks those ids exist). */
body[data-chain-mode="retailer"] #tab-team .section-card:has(#tm-feed-list),
body[data-chain-mode="retailer"] #tab-team .section-card:has(#woo-url),
body[data-chain-mode="retailer"] #tab-team .section-card:has(#tm-fi-pricelist),
body[data-chain-mode="retailer"] #tab-team .section-card:has(#tm-doclang-list),
body[data-chain-mode="retailer"] #tab-team #tm-epr { display: none !important; }

/* ------------------------------------------------ supply chain: layout fixes after the QA pass
   Neutral throughout: a highlighted row, a panel and a list are layout, not compliance states,
   so none of the frozen traffic-light colours appears here.

   Supplier mode takes the other national producer-responsibility regimes (tab-epr) off the
   product nav along with Finland (decision 4). auth-ui.js applyModeChrome marks the item with
   data-chain-hidden; the item's own inline display belongs to app.js renderEprTab (it is shown
   only when a country module besides Finland ships), so the mark wins with !important. */
#side-tabs .nav-item[data-chain-hidden] { display: none !important; }

/* An opened panel under a table — the retailer library's product detail and the wholesaler's
   document request to a supplier. It sits BELOW the table, never inside a cell, so on a phone it
   is as wide as the card and nothing in it scrolls sideways with the table. */
.chain-panel {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px 14px; margin: 10px 0 0; max-width: 100%; min-width: 0;
  /* Brought into view on opening: clear of the sticky header on a phone. */
  scroll-margin-top: calc(var(--app-header-h, 56px) + 16px);
}
.chain-panel-head { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: flex-start; justify-content: space-between; }
.chain-panel-head > :first-child { flex: 1 1 200px; min-width: 0; }
/* The row whose panel is open stays marked while the panel is on screen. */
#tab-library table.data tr.active > td,
#tab-chain table.data tr.active > td { background: var(--accent-soft); }
/* A checkbox keeps its own size beside its country name (table.data inputs are width:100%,
   which is what spread them apart while the detail was drawn inside a cell). */
.chain-panel .country-grid input[type="checkbox"] { width: auto; flex: none; margin: 0; }
/* An anchor wearing .btn (base rule above) needs the box an <a> lacks. */
#tab-library a.btn { display: inline-block; text-decoration: none; line-height: 1.45; vertical-align: middle; }
/* The library's documents and registrations as rows that wrap on a phone instead of a table
   that would scroll: the name on the left, the actions or the number on the right, one under
   the other when the line is too narrow for both. */
.lib-list { list-style: none; margin: 6px 0 10px; padding: 0; border: 1px solid var(--line); border-radius: 7px; background: var(--panel); }
.lib-list > li { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: center; justify-content: space-between; padding: 8px 10px; border-top: 1px solid var(--line); }
.lib-list > li:first-child { border-top: 0; }
.lib-list-main { flex: 1 1 200px; min-width: 0; }
.lib-list-side { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* The supplier home's send form on a phone: the heavy-metal and packaging inputs keep a width a
   value fits in, and the table scrolls inside its own .table-scroll rather than squeezing each
   input to a sliver. The id selector outranks the phone rule that lets section inputs shrink. */
#sh-form tr[data-sh-hm] input { min-width: 72px; }
#sh-form tr[data-sh-hm] input[data-f="method"],
#sh-form tr[data-sh-hm] input[data-f="lab"] { min-width: 140px; }
#sh-form tr[data-sh-comp] input { min-width: 80px; }
#sh-form tr[data-sh-comp] select { min-width: 180px; }
/* An empty required material or weight in the send form is red exactly as the supplier portal
   paints it (its `input.req, select.req`): the frozen required hues of .f-req above, red =
   required, never repurposed. Scoped to the wizard's packaging rows, and specific enough to
   outrank the neutral `table.data input/select` border; nothing outside #sh-form changes. */
#sh-form tr[data-sh-comp] input.req, #sh-form tr[data-sh-comp] select.req { background: #fdecec; border-color: #e0564f; }
#sh-form tr[data-sh-comp] input.req:focus, #sh-form tr[data-sh-comp] select.req:focus { outline: 2px solid #e0564f; border-color: #c53b34; }
