/* Shell shared by the hand-written pages: pricing, support, about, the legal
   pages, the three writing tools and /mcp. The converter itself keeps app.css.

   v2 redesign: same role names, new instrument. Light is the default palette;
   Inter carries UI and display, JetBrains Mono carries every number and code
   path, mint-to-sky is the one accent system. Tokens, type scale and spacing
   stay identical to app.css on purpose: a visitor crossing from
   /xrechnung-to-excel to /pricing must not feel they have left the product.
   Read the header comment in app.css for the full direction notes. */

@font-face{
  font-family:"Inter"; font-style:normal; font-weight:400 800; font-display:swap;
  src:url(/fonts/inter-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;
}
@font-face{
  font-family:"Inter"; font-style:normal; font-weight:400 800; font-display:swap;
  src:url(/fonts/inter-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;
}

/* The palette lives in tokens.css, which every page loads before this sheet.
   Nothing in page.css may open a :root block: check_tokens() in .gen/build.py
   fails the build over one, because a second declaration of a token is how the
   two sheets drifted apart in the first place. What DOES stay here is the
   .form-wrap island below, which is a component overriding roles for its own
   subtree, not a second palette. */

*{box-sizing:border-box}
[hidden]{display:none !important}
body{
  margin:0; background:var(--bg); color:var(--ink);
  font-family:var(--sans); font-size:15.5px; line-height:1.6;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  min-height:100vh; display:flex; flex-direction:column;
  transition:background-color .25s ease, color .25s ease;
}
body > main{flex:1 0 auto}
h1,h2,h3{margin:0; text-wrap:balance; font-family:var(--cond); font-weight:800}
h1{font-size:clamp(30px,4.2vw,46px); line-height:1.06; letter-spacing:-.03em}
h2{font-size:clamp(21px,2.4vw,28px); line-height:1.15; letter-spacing:-.024em}
h3{font-size:17px; line-height:1.25; letter-spacing:-.01em; font-weight:700}
p{margin:0}
a{color:var(--accent); text-decoration:none}
a:hover{text-decoration:underline; text-underline-offset:2px}
:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:4px}
.wrap{max-width:var(--col); margin:0 auto; padding:0 var(--gutter)}
.sr{position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap}
.skip{
  position:absolute; left:12px; top:-60px; z-index:60;
  background:var(--accent); color:var(--accent-ink); font-weight:600;
  padding:10px 16px; border-radius:var(--r-sm); transition:top .15s;
}
.skip:focus{top:12px; text-decoration:none}

/* header */
.site-head{
  position:sticky; top:0; z-index:30;
  background:var(--head-glass); backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px);
  border-bottom:1px solid var(--line);
  transition:background-color .25s ease, border-color .25s ease;
}
.site-head .wrap{display:flex; align-items:center; gap:24px; height:var(--head-h)}
.brand{
  display:inline-flex; align-items:center; gap:10px; color:var(--ink);
  font-family:var(--cond); font-weight:800; font-size:19px; letter-spacing:-.02em; flex:0 0 auto;
}
.brand:hover{text-decoration:none}
.brand svg{width:27px; height:27px; display:block; border-radius:7px; flex:0 0 auto;
  box-shadow:0 0 14px rgba(61,232,160,.22)}
.site-nav{display:flex; gap:22px; margin-left:2px}
.site-nav a{color:var(--muted); font-size:14.5px; font-weight:500; padding:6px 0; position:relative;
  transition:color .15s}
.site-nav a:hover{color:var(--ink); text-decoration:none}
.site-nav a::after{
  content:""; position:absolute; left:0; right:0; bottom:0; height:2px;
  background:var(--grad); border-radius:2px;
  transform:scaleX(0); transform-origin:left;
  transition:transform .2s cubic-bezier(.3,.7,.4,1);
}
.site-nav a:hover::after{transform:scaleX(1)}
.head-right{margin-left:auto; display:flex; align-items:center; gap:16px}
.head-right a{font-size:14.5px; color:var(--muted); transition:color .15s}
.head-right a:hover{color:var(--ink); text-decoration:none}
.head-right a[aria-current="page"]:not(.btn){color:var(--ink); font-weight:600}
/* .head-right a is a class+type selector and outranks .btn-primary, so the
   header CTA was painting its label in --muted on the bright gradient: 1.9:1,
   unreadable in both themes. Restate the button ink, hover included. */
.head-right a.btn-primary,.head-right a.btn-primary:hover{color:var(--accent-ink)}
.icon-btn{
  display:grid; place-items:center; width:36px; height:36px; padding:0; cursor:pointer;
  border:1px solid var(--line-strong); border-radius:10px;
  background:transparent; color:var(--muted);
  transition:background .16s, color .16s, border-color .16s, transform .16s;
  position:relative; overflow:hidden;
}
.icon-btn:hover{background:var(--accent-soft); color:var(--accent); border-color:var(--accent); transform:translateY(-1px)}
.icon-btn:active{transform:scale(.94)}

/* The print control shares the download row's framed action language: a compact
   icon-only action with the same height as the adjacent download buttons.
   Its outline ring uses the same visual weight as the other controls, so it
   reads as part of the action row rather than a separate square utility.
   The larger icon keeps the print action legible at both desktop and mobile. */
.icon-btn.print-btn{
  margin-left:auto; width:32px; height:32px; border:0; border-radius:var(--r-sm);
  background:#111; color:#fff;
}
.icon-btn.print-btn svg{width:15px; height:15px}
.icon-btn.print-btn:hover{background:#111; color:#fff; opacity:.85; transform:none}
.icon-btn.print-btn:active{transform:scale(.92)}
.menu{display:none; position:relative}
.menu > summary{list-style:none; cursor:pointer}
.menu > summary::-webkit-details-marker{display:none}
.menu-panel{
  position:absolute; right:0; top:calc(100% + 10px); width:min(78vw,264px);
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--shadow-3); padding:8px; display:flex; flex-direction:column;
  animation:menuIn .18s cubic-bezier(.3,.9,.4,1) both;
}
@keyframes menuIn{from{opacity:0; transform:translateY(-6px) scale(.98)}to{opacity:1; transform:none}}
.menu-panel > a{
  color:var(--ink-2); font-size:15px; padding:10px 12px; border-radius:var(--r-xs);
  transition:background .13s, color .13s;
}
.menu-panel > a:hover{background:var(--accent-soft); color:var(--ink); text-decoration:none}
@media (max-width:960px){
  .site-nav{display:none}
  /* order:2 puts the burger to the RIGHT of the language picker - owner
     swap, 2026-09-19 mobile pass. Mirrors the app.css rule. */
  .menu{display:block; order:2}
  .head-right .hide-sm{display:none}
}

/* buttons */
.btn{
  position:relative; overflow:hidden;
  font:inherit; font-size:15px; font-weight:600; cursor:pointer;
  border:1px solid var(--line-strong); background:transparent; color:var(--ink);
  padding:9px 16px; border-radius:10px; display:inline-flex;
  align-items:center; justify-content:center; gap:7px; white-space:nowrap;
  transition:background .16s, border-color .16s, transform .16s, color .16s, box-shadow .16s;
}
.btn:hover{background:var(--accent-soft); border-color:var(--accent); color:var(--accent);
  text-decoration:none; transform:translateY(-1px)}
/* Same specificity trap as app.css: a pseudo-class outranks .btn-primary, so
   hover used to strip the gradient and leave the header CTA dark-on-dark.
   The compound selector wins regardless of file order. */
.btn.btn-primary:hover,
.btn.btn-primary:active{
  background:var(--grad);
}
.btn:active{transform:scale(.96)}
.btn-primary{
  background:var(--grad); border:none; color:var(--accent-ink); font-weight:700;
  box-shadow:0 4px 18px rgba(61,232,160,.30);
}
.btn-primary::after{
  content:""; position:absolute; top:0; left:-80%; width:45%; height:100%;
  background:linear-gradient(100deg, transparent, rgba(255,255,255,.42), transparent);
  transform:skewX(-20deg); animation:sheen 4.6s ease-in-out infinite;
}
@keyframes sheen{0%,58%{left:-80%}78%,100%{left:135%}}
.btn-primary:hover{box-shadow:0 6px 26px rgba(61,232,160,.45); color:var(--accent-ink); filter:saturate(1.08)}
.btn-lg{font-size:16px; padding:12px 22px; border-radius:12px}
.btn:disabled{opacity:.45; cursor:not-allowed; transform:none !important}
.btn-wide{width:100%}
.btn.busy{pointer-events:none; color:transparent !important}
.btn.busy::before{
  content:""; position:absolute; width:16px; height:16px; border-radius:50%;
  /* The spinner must not read currentColor: the busy rule above just made
     currentColor transparent, so a currentColor ring is invisible. Same fix
     app.css:399-414 already documents; ink and accent are the honest pair. */
  border:2px solid color-mix(in srgb, var(--ink) 25%, transparent);
  border-top-color:var(--accent);
  animation:spin .7s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg)}}
/* Amber as text on the paper background is 2.35:1, the worst contrast on
   the site (app.css:2019-2024 fixed it there); page.css pages get the same
   ink link with an amber underline instead of an amber slab of text. */
.btn-link{border:0; background:none; color:var(--ink); padding:0; font-size:14px;
  font-weight:600; cursor:pointer; font-family:inherit; position:relative}
.btn-link:hover{text-decoration:underline; text-underline-offset:3px;
  text-decoration-color:var(--accent); background:none}
.fx-ripple{
  position:absolute; border-radius:50%; pointer-events:none;
  background:currentColor; opacity:.22;
  transform:scale(0); animation:ripple .5s ease-out forwards;
}
@keyframes ripple{to{transform:scale(2.6); opacity:0}}

/* ------------------------------------------------------------- page head --*/
.page-head{
  background:var(--band); color:var(--band-ink); position:relative; overflow:hidden;
  /* the band used to leave 54px under its last line, the content another 44,
     and the tools directory 30 on top of that: 128px between a sentence and
     the card it introduces. */
  padding:clamp(42px,5.4vw,68px) 0 clamp(28px,3vw,38px);
}
.page-head::before{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:
    radial-gradient(90% 120% at 82% -10%, rgba(65,191,240,.15) 0%, transparent 55%),
    radial-gradient(70% 100% at 10% 115%, rgba(61,232,160,.11) 0%, transparent 55%);
}
.page-head::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background-image:
    linear-gradient(rgba(255,255,255,.03) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.03) 1px, transparent 1px);
  background-size:64px 64px;
  /* One-off, and not a colour: inside a mask, #000 means fully opaque and
     transparent means cut away. The palette has nothing to say about it. */
  mask-image:radial-gradient(ellipse 95% 80% at 50% 0%, #000 40%, transparent 80%);
  -webkit-mask-image:radial-gradient(ellipse 95% 80% at 50% 0%, #000 40%, transparent 80%);
}
.page-head .wrap{position:relative; z-index:1}
.page-intro{max-width:62ch; display:flex; flex-direction:column; gap:12px}
.page-intro > *{animation:heroRise .55s cubic-bezier(.2,.7,.3,1) both}
.page-intro > *:nth-child(2){animation-delay:.06s}
.page-intro > *:nth-child(3){animation-delay:.12s}
@keyframes heroRise{from{opacity:0; transform:translateY(14px)}to{opacity:1; transform:none}}
.page-head h1{color:var(--band-hi)}
.page-intro p{color:var(--band-muted); font-size:17px; text-wrap:pretty}
.page-intro p a{color:var(--band-hi); text-decoration:underline; text-decoration-color:var(--accent);
  text-underline-offset:3px; text-decoration-thickness:2px}
/* The band's body ink is deliberately muted; a date the sentence is built
   around has to come back out of it, or the bold tag renders as bold grey and
   marks nothing. */
.page-intro p strong{color:var(--band-hi); font-weight:600}
.eyebrow{
  font-family:var(--mono); font-size:11px; text-transform:uppercase; letter-spacing:.15em;
  font-weight:600; color:var(--accent);
}
.page{padding-top:24px; padding-bottom:8px}

.notice{
  display:flex; gap:11px; align-items:flex-start; margin-top:24px;
  border:1px solid var(--line); border-left:3px solid var(--accent);
  background:var(--surface); border-radius:var(--r-sm); box-shadow:var(--shadow-1);
  padding:13px 16px; font-size:14.5px; color:var(--ink-2); max-width:var(--col-mid);
  animation:resultIn .35s ease both;
}
@keyframes resultIn{from{opacity:0; transform:translateY(10px)}to{opacity:1; transform:none}}
.notice b{color:var(--ink)}
.note{font-size:14px; color:var(--muted)}
/* Nugroho, 2026-08-27: the five legal pages (about, privacy, imprint,
   impressum, datenschutz) read as bare prose floating on the page
   background, which is exactly the plain unstyled sheet he flagged in light
   mode. Give .legal the same raised-card recipe as every other block so all
   five lift together; both themes inherit it from the shared tokens. */
/* Centered reading column: the card sits on the page's middle axis, the
   same column the about pages' .prose uses. Owner call 2026-09-12 after
   seeing the card hung off the left edge under a full-width head. */
.legal{
  position:relative;
  max-width:var(--measure); margin:28px auto 0; display:flex; flex-direction:column; gap:18px;
  background:linear-gradient(180deg, var(--panel-hi), var(--panel));
  -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  border:1px solid var(--line); border-radius:var(--r-lg);
  padding:28px 32px 32px; box-shadow:var(--shadow-1);
}
.legal::before{
  content:""; position:absolute; left:0; right:0; top:0; height:2.5px;
  background:var(--grad); border-radius:var(--r-lg) var(--r-lg) 0 0;
}
@media (max-width:560px){ .legal{padding:22px 20px 26px} }
.legal h2{font-size:19px}
.legal p,.legal li{color:var(--ink-2); font-size:15.5px}
.legal ul{margin:0; padding-left:1.2em}
.facts{
  display:grid; grid-template-columns:minmax(11ch,16ch) 1fr;
  gap:9px 20px; margin:0 auto; max-width:var(--measure);
}
.facts dt{
  font-family:var(--mono); text-transform:uppercase; letter-spacing:.08em;
  color:var(--muted); font-size:11px; font-weight:500; padding-top:3px;
}
.facts dd{margin:0; color:var(--ink)}

/* plans */
.plans{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(265px,1fr));
  gap:18px; align-items:start;
}
.plan{
  position:relative; display:flex; flex-direction:column; gap:16px; height:100%;
  border:1px solid var(--line); border-radius:var(--r);
  background:linear-gradient(180deg, var(--panel-hi), var(--panel));
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  padding:24px 22px; box-shadow:var(--shadow-1);
  transition:transform .18s, border-color .18s, box-shadow .18s;
}
.plan:hover{transform:translateY(-3px); box-shadow:var(--shadow-2)}
.plan-live{border-color:var(--line)}
/* the featured tier reads one step closer: accent border and a soft glow,
   no scale tricks. Declared after .plan-live so the accent border wins.
   The old mint rgba(61,232,160) and the var(--grad) top bar were the dark
   theme's accent leaking into the v5 skin - the "green aura" the owner
   flagged on 2026-09-12. The featured tier now carries the v5 amber, and
   the gradient bar survives only there. */
.plan-featured{
  border-color:var(--v5-amber);
  /* No glow: the 44px amber halo rendered as jagged stacked bands above
     the card in real browsers (owner screenshot 2026-09-12). No ::before
     bar either: an absolutely positioned bar outran the card's rounded
     corners and poked out at both ends (second owner screenshot, same
     day). A thicker top border follows the radius natively. */
  box-shadow:var(--shadow-2);
  border-top-width:3px;
}
.plan-top{display:flex; flex-direction:column; gap:5px}
.plan-name{display:flex; align-items:center; gap:10px}
/* the best-offer tag on the highlighted tier: the v5 lead-chip recipe,
   amber fill with ink text, small and uppercase */
.plan-flag{
  background:var(--v5-amber); color:var(--v5-ink);
  font:600 11px/1 var(--sans); letter-spacing:.07em; text-transform:uppercase;
  padding:5px 8px; border-radius:4px; white-space:nowrap;
}
.plan-name h2{font-size:21px}
.tag{
  font-family:var(--mono); font-size:10.5px; text-transform:uppercase; letter-spacing:.08em;
  font-weight:600; padding:3px 9px; border-radius:100px;
  border:1px solid var(--line-strong); color:var(--muted);
}
.tag-live{background:var(--ok-soft); border-color:var(--ok); color:var(--ok)}
.tag-soon{background:var(--accent-soft); border-color:var(--accent); color:var(--accent)}
.plan-price{
  font-family:var(--mono); font-variant-numeric:tabular-nums;
  font-size:38px; font-weight:600; line-height:1.05; margin-top:8px; letter-spacing:-.03em;
}
.plan-price small{font-family:var(--sans); font-size:13.5px; font-weight:400; color:var(--muted); letter-spacing:0}
.plan-price .tbd{color:var(--muted); font-weight:500; font-size:21px; letter-spacing:0}
.plan-for{color:var(--muted); font-size:14px}
.plan ul{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:9px}
.plan li{position:relative; padding-left:24px; font-size:14.5px; color:var(--ink-2)}
.plan li::before{
  content:""; position:absolute; left:0; top:.12em; width:15px; height:15px;
  background-color:var(--accent);
  -webkit-mask:var(--mark-check) center/contain no-repeat;
          mask:var(--mark-check) center/contain no-repeat;
}
.plan .btn{margin-top:auto}
.plan-note{
  margin-top:0; font-size:13px; color:var(--muted); line-height:1.45;
  min-height:calc(2 * 1.45 * 13px);
}

.link-cols{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  gap:18px; margin-top:30px;
}
.link-col{
  border:1px solid var(--line); border-radius:var(--r); background:var(--surface);
  box-shadow:var(--shadow-1); padding:22px;
}
.link-col h2{
  font-size:12px; text-transform:uppercase; letter-spacing:.12em;
  color:var(--muted); margin-bottom:12px; font-family:var(--mono); font-weight:600;
}
.link-col ul{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:9px}
.link-col a{font-size:15px}

/* support page, same card recipe as the pricing plans: translucent panel,
   blur, gradient rule along the top edge. The tracks are deliberately NOT
   equal: the crypto card carries the pills and the address, so it takes the
   wider one and the PayPal button stops floating in a half-empty box. */
.support-row{
  display:grid; grid-template-columns:minmax(0,2fr) minmax(0,3fr);
  gap:18px; max-width:var(--col-mid);
}
@media (max-width:700px){
  .support-row{grid-template-columns:1fr}
}
.support-card{
  position:relative; display:flex; flex-direction:column; gap:14px;
  border:1px solid var(--line); border-radius:var(--r);
  background:linear-gradient(180deg, var(--panel-hi), var(--panel));
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  box-shadow:var(--shadow-1); padding:24px 22px;
  transition:transform .18s, border-color .18s, box-shadow .18s;
}
.support-card:hover{transform:translateY(-3px); box-shadow:var(--shadow-2)}
.support-card::before{
  content:""; position:absolute; left:0; right:0; top:0; height:2.5px;
  background:var(--grad); border-radius:var(--r) var(--r) 0 0;
}
.support-card h2{font-size:21px}
/* the action sits at the foot of the card the way .plan's button does, so a
   short card reads as roomy instead of half-empty */
.support-card > :last-child{margin-top:auto}
.fmt-list{display:flex; flex-wrap:wrap; gap:8px}
.fmt{
  font-family:var(--mono); font-size:12px; font-weight:500; padding:5px 12px;
  border-radius:100px; border:1px solid var(--line-strong); color:var(--ink-2); background:var(--bg);
}
.addr-row{display:flex; gap:8px; align-items:center}
.addr-row .btn{flex:0 0 auto}
.addr{
  flex:1 1 auto; min-width:0; white-space:normal;
  overflow-wrap:anywhere; word-break:break-all;
  font-family:var(--mono); font-size:12.5px; line-height:1.55; color:var(--ink-2);
  background:var(--surface-2); border:1px solid var(--line); border-radius:var(--r-sm);
  padding:10px 12px;
}
.thanks{
  margin-top:34px; padding:18px 20px; max-width:var(--col-mid);
  border:1px solid var(--line); border-left:3px solid var(--accent);
  border-radius:var(--r-sm); background:var(--surface); box-shadow:var(--shadow-1);
  color:var(--ink-2); font-size:15px;
}

/* comparison + faq blocks */
/* scroll-margin so a jump to #faq clears the sticky header, the same way
   section.block does in app.css. */
.block{padding:48px 0; border-top:1px solid var(--line);
  scroll-margin-top:var(--head-h)}
.page > .block:first-child{border-top:0; margin-top:0; padding-top:0}
/* A block whose top rule would land under a bare control rather than between
   two sections. The rule separates a section from a card or a framed box above
   it; four pixels under a button it reads as an underline on the button. Used
   by the spreadsheet writer pages, where the drop control lost its frame. */
.block-flush{border-top:0}
/* A .wrap already inside a .wrap: it wants the max-width and the centring, not
   a second helping of the gutter. Twin of the rule in app.css, which the
   generated tool pages use; these pages load page.css. */
.wrap-flush{padding-left:0; padding-right:0}
.btn-row{display:flex; flex-wrap:wrap; gap:10px; align-items:center}
.block h2 + *{margin-top:24px}
.block h2 + p.lead{margin-top:8px}
.block > .wrap > p.lead{color:var(--muted); margin-bottom:24px; max-width:var(--measure); font-size:16.5px; text-wrap:pretty}
.table-scroll{
  overflow-x:auto; border:1px solid var(--line);
  border-radius:var(--r); background:var(--surface);
}
table{border-collapse:collapse; width:100%; font-size:14.5px; color:var(--ink); background:transparent}
th,td{
  padding:12px 14px; text-align:left; border-bottom:1px solid var(--line);
  vertical-align:top; color:var(--ink);
}
thead th{
  font-family:var(--mono); font-size:10.5px; text-transform:uppercase; letter-spacing:.09em;
  color:var(--muted); font-weight:500; background:var(--surface-2); white-space:nowrap;
}
tbody tr:last-child td{border-bottom:0}
tbody td:first-child{font-weight:600; color:var(--ink)}
thead th.mid,
tbody td.mid{text-align:center; width:15%}
tbody td.mid{color:var(--ink-2)}
tbody td.yes{color:var(--ok); font-weight:600}
.faq{display:flex; flex-direction:column; border-top:1px solid var(--line)}
.faq details{border-bottom:1px solid var(--line)}
.faq summary{
  padding:16px 0; cursor:pointer; font-weight:600; font-size:16.5px;
  list-style:none; display:flex; align-items:center; gap:12px; color:var(--ink);
  transition:color .15s;
}
.faq summary::-webkit-details-marker{display:none}
/* Plus, closing to a minus, exactly as .qa-q does on /help. The rotating
   chevron this replaces was the dark language's glyph, and shipping both left
   one component answering the same question two ways depending on the page. */
.faq summary::before{
  content:"+"; order:2; flex:0 0 22px; margin-left:auto; width:22px; text-align:center;
  color:var(--v5-amber-deep); font:400 20px/1 var(--sans);
}
.faq details[open] summary::before{content:"\2212"}
/* ink text, amber underline: --v5-amber-deep as TEXT is 3.57:1 on paper
   and 3.63:1 on surface, both under 4.5, and a hover state is text too.
   Swept together so the rule is the same everywhere. */
.faq summary:hover{color:var(--v5-ink); text-decoration:underline;
  text-decoration-color:var(--v5-amber); text-underline-offset:3px}
.faq details p{padding:0 0 18px 26px; color:var(--ink-2); font-size:15.5px; max-width:var(--measure);
  animation:faqIn .28s ease both}
@keyframes faqIn{from{opacity:0; transform:translateY(-4px)}to{opacity:1; transform:none}}

/* The FAQ reads the same on every page of the site: a mono tag over a centered
   heading, then bordered cards in a centered column. These rules are the twins
   of the ones in app.css, which the generated pages use; the two stylesheets
   share the tokens, so the two copies render identically. Keep them in step. */
.sec-tag{
  font-family:var(--mono); font-size:11px; font-weight:600;
  /* muted, not --accent. This is the mock's .micro role, a label above a
     heading, and the v5 island binds --accent to amber: 2.27:1 at 11px. The
     eyebrow on the writer page head (page.css, .eyebrow) already sets the
     same role in --v5-muted; this is the one place that did not follow. */
  letter-spacing:.14em; text-transform:uppercase; color:var(--v5-muted);
  margin:0 0 10px;
}
.sec-head{text-align:center}
#faq .faq{
  max-width:780px; margin:36px auto 0; gap:12px; border-top:none;
}
#faq .faq details{
  border:none; border-bottom:1px solid var(--v5-line); border-radius:0;
  background:none; backdrop-filter:none; -webkit-backdrop-filter:none;
}
#faq .faq summary{padding:15px 2px; font-size:16.5px}
#faq .faq details p{padding:0 26px 16px 2px; max-width:64ch}

/* sign in */
.auth{max-width:400px; margin:56px auto 0}
.auth-card{
  border:1px solid var(--line); border-radius:var(--r); background:var(--surface);
  padding:28px; box-shadow:var(--shadow-2); display:flex; flex-direction:column; gap:18px;
}
.auth-head{display:flex; flex-direction:column; gap:6px; text-align:center}
.auth-head h1{font-size:29px}
.auth-head p{color:var(--muted); font-size:14.5px}
.auth form{display:flex; flex-direction:column; gap:14px}
.field{display:flex; flex-direction:column; gap:6px}
.field label{
  font-family:var(--mono); font-size:10.5px; text-transform:uppercase; letter-spacing:.1em;
  font-weight:500; color:var(--muted);
}
.input{
  font:inherit; font-size:15px; color:var(--ink); background:var(--bg);
  border:1px solid var(--line-strong); border-radius:10px;
  /* Chrome forces line-height:normal on a dropdown select and ignores any
     author value, so a select came out 41px beside a 45px input and, in a row
     that aligns on its bottom edge, dragged its label four pixels down. A
     floor on the box is the one thing the browser does honour. */
  min-height:45px;
  padding:10px 12px; width:100%;
  transition:border-color .16s, box-shadow .16s;
}
.input::placeholder{color:var(--muted)}
.input:focus{border-color:var(--accent); outline:none; box-shadow:0 0 0 3px var(--accent-soft)}
.input:disabled{background:var(--surface-2); color:var(--muted); cursor:not-allowed}
.auth-alt{
  border-top:1px solid var(--line); padding-top:16px;
  font-size:14px; color:var(--ink-2); text-align:center;
}
.auth-foot{margin-top:16px; text-align:center; font-size:13px; color:var(--muted)}

/* ------------------------------------------------- mandate deadline check --*/
/* The Check group on /tools: e-rechnung-pflicht (DE) and
   obligation-facturation-electronique (FR). One tool asking one question about
   one company, in two languages, so its layout belongs here and not in style
   attributes on each page, which is where it was. Both pages carried
   `max-width:640px` on the card, `margin-top:16px` on the verdict and a <p>
   with display:flex standing in for .btn-row, and the two copies had already
   started to disagree by a pixel here and a question there.

   The grid is the other half of the fix. The card is 640px wide inside a
   1140px column, so 456px beside it sat empty on both pages while the answer
   the visitor came for -- which date applies to them -- was a sentence inside
   the verdict box. The timeline puts the whole calendar next to the control
   and the verdict marks the row you land on, so the tool answers in the place
   a person is already looking. */
.checker{
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(20px,2.4vw,32px); align-items:start;
  /* The checker is a plain div, so the next .block keeps its border-top
     divider; without this gap the hairline sits on the buttons' bottom edge
     and reads as a line struck through them (the .btn fill is transparent). */
  margin-bottom:48px;
}
.checker-col{display:flex; flex-direction:column; gap:18px}
.checker-card{display:flex; flex-direction:column; gap:16px}
/* .form-card's own h2 carries the bottom rule and the 16px under it; inside a
   flex column that margin doubles the gap, so it is handed back. */
.checker-card > h2{margin-bottom:0}
.checker-card .notice{margin-top:0}
/* The submit sits on its own row rather than in the .field, so the control and
   the button do not fight over the label column. */
.checker-card .btn{align-self:flex-start}

/* The calendar, one <li> per date. Dates are typeset in the mono face and
   aligned on their own column so 2027 lines up under 2026 and the reader can
   see the phase-in as a shape rather than as three sentences. */
.timeline{
  list-style:none; margin:0; padding:0;
  display:flex; flex-direction:column;
}
.tl-row{
  display:grid; grid-template-columns:auto minmax(0,1fr); gap:4px 14px;
  padding:0 0 22px 22px; position:relative;
}
.tl-row:last-child{padding-bottom:0}
/* The rail is drawn by the rows, not by a container, so the last row can stop
   it at its own dot instead of trailing into the padding below the list. */
.tl-row::before{
  content:""; position:absolute; left:4px; top:16px; bottom:0;
  width:1px; background:var(--line-strong);
}
.tl-row:last-child::before{display:none}
.tl-row::after{
  content:""; position:absolute; left:0; top:6px;
  width:9px; height:9px; border-radius:50%;
  background:var(--surface-2); border:1px solid var(--line-strong);
  transition:background-color .2s, border-color .2s, box-shadow .2s;
}
.tl-date{
  grid-column:1/-1;
  font-family:var(--mono); font-size:11px; font-weight:600;
  letter-spacing:.1em; text-transform:uppercase; color:var(--muted);
  transition:color .2s;
}
.tl-what{grid-column:1/-1; font-size:14.5px; color:var(--ink-2); text-wrap:pretty}
.tl-what b{color:var(--ink); font-weight:600}
/* A date already in force gets a filled dot rather than a hollow one, and
   nothing else. It must NOT be coloured: in dark mode --ok and --accent are
   the same #3DE8A0, so painting "already in force" green put it in exactly the
   ink the is-you row below uses, and the two rows read as one highlight. The
   row already says it is in force in words; the dot only has to say the phase
   is behind us. */
.tl-row.is-past::after{background:var(--line-strong); border-color:var(--line-strong)}
/* The row the verdict lands on. Set by the page script, cleared on every run,
   so a second answer never leaves the first one marked. */
.tl-row.is-you::after{
  background:var(--accent); border-color:var(--accent);
  box-shadow:0 0 0 4px var(--accent-soft);
}
/* The YOU pill beside it carries the amber; the date itself stays ink:
   amber 11px text on the v5 background is 2.15:1 and the date is the one
   fact the whole tool exists to point at (audit 2026-10-02, D1 F3). */
.tl-row.is-you .tl-date{color:var(--ink)}
.tl-row.is-you .tl-what{color:var(--ink)}
.tl-you{
  display:none; margin-left:8px; vertical-align:1px;
  font-family:var(--sans); font-size:10.5px; font-weight:600;
  letter-spacing:.08em; text-transform:uppercase;
  color:var(--accent-ink); background:var(--accent);
  border-radius:999px; padding:2px 8px;
}
.tl-row.is-you .tl-you{display:inline-block}

/* One column below the point where 640px of card and a readable calendar stop
   fitting side by side. */
@media (max-width:900px){
  .checker{grid-template-columns:minmax(0,1fr)}
}

/* footer */
/* The footer is the one region that stays dark in both themes, which is why
   its colours are literals rather than tokens: they are not a palette that
   flips, they are one surface that does not. Values follow the v2 mockup.

   No border under the column grid. The single rule in this footer sits on top
   of .foot-base, so the columns and the brand read as one block and the fine
   print reads as a separate line under it. A rule in both places boxes the
   columns in and makes the whole footer look like a table. */
.site-foot{
  border-top:1px solid var(--line); background:var(--foot-bg); color:var(--foot-ink);
  padding:64px 0 36px; margin-top:96px; position:relative;
}
.site-foot > .wrap{position:relative}
.foot-top{
  display:grid; grid-template-columns:minmax(200px,1.15fr) minmax(0,4fr); gap:36px;
  margin-bottom:48px;
}
.foot-brand{display:flex; flex-direction:column; gap:14px}
.foot-brand .brand{color:var(--foot-hi); font-size:20px}
/* 30ch, so the sentence breaks into the three or four short lines the mockup
   shows rather than running the full width of the brand column. */
.foot-story{font-size:13.5px; color:var(--foot-story); line-height:1.6; max-width:30ch}
/* The mockup has four link columns and this site has five, because the footer
   is the internal-link floor: check_internal_links() fails the build if a
   landing page is not in it, and there are 29 pages to name. Five columns in
   the same width means each one is narrower than the mockup's, so the brand
   column gives up some room and the column gap tightens. Below that, the
   longest label ("Germany, XRechnung") breaks onto two lines. */
/* v2 footer: six short category columns, mirrored from app.css. Each links to
   a /tools anchor, not the individual pages, so the footer stays a slim index.
   Locale footers stamp cols-N so no track sits empty. */
.foot-cols{display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); gap:40px 20px}
/* locale footers carry fewer columns (a language only gets the pages its
   market searches for); a fixed 4-track grid left the tail tracks empty and
   the row ragged. The build stamps cols-N from the real column count. */
.foot-cols.cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}
.foot-cols.cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.foot-cols.cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.foot-cols h3{
  font-size:11px; text-transform:uppercase; letter-spacing:.14em; color:var(--foot-head);
  margin-bottom:16px; font-weight:600; font-family:var(--mono);
}
.foot-cols ul{list-style:none; margin:0; padding:0; display:grid; gap:10px}
.foot-cols a{color:var(--foot-link); font-size:13.5px; line-height:1.45; transition:color .15s; display:inline-block; padding:1px 0}
.foot-cols a:hover{color:var(--foot-hi)}
/* The two halves came to 1128px inside a 1140px wrap, so a 12px gap tipped
   them onto separate lines: right at the boundary, which is the worst place to
   leave a layout. The tagline may shrink now, so they share one line at full
   width and still wrap cleanly when the viewport cannot hold both. */
.foot-base{
  border-top:1px solid rgba(255,255,255,.08); padding-top:22px;
  display:flex; justify-content:space-between; flex-wrap:wrap; gap:10px 18px;
  font-family:var(--mono); font-size:11px; color:var(--foot-legal); line-height:1.7;
}
/* The tagline needs 627px and the links 501px: 1146 with the gap, which no
   desktop width here can give them, since the wrap caps at 1140. Sharing a
   line therefore meant shrinking the tagline until it broke in two and left
   one word stranded under the rest. It takes its own row instead, one clean
   line down to 627px and a normal wrap below that. */
.foot-base > :first-child{flex:1 1 100%; min-width:0}
/* shrinkable, so on a phone the link row wraps inside itself instead of
   pushing the page into a sideways scroll at its max-content width. */
.foot-base .foot-links{flex:0 1 auto; min-width:0}
.foot-base .foot-links{display:flex; flex-wrap:wrap; gap:0 6px; align-items:baseline}
.foot-base a{color:var(--foot-legal); transition:color .15s}
.foot-base a:hover{color:var(--foot-hi)}
.foot-base .sep{opacity:.45}
.foot-base a[href^="mailto:"]{text-decoration:underline; text-underline-offset:2px}

@media (max-width:1000px){ .foot-cols{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media (max-width:820px){ .foot-top{grid-template-columns:1fr} }
@media (max-width:560px){ .foot-cols{grid-template-columns:1fr} }
@media (prefers-reduced-motion:reduce){ *{transition:none !important; animation:none !important} }

/* ---------------------------------------------------------- invoice form --*/
.form-wrap{display:flex; flex-direction:column; gap:20px; margin-top:24px}
/* The .form-wrap island is gone. It existed to paint the invoice form as
   paper inside a dark page (Nugroho, 2026-08-27); the page is paper now,
   and leaving it would let the theme toggle repaint the form in the OLD
   light palette on top of v5: :root[data-theme="dark"] .form-wrap is
   (0,2,0) and the body island is (0,0,1), so the island would win its
   own subtree and the form would go blue-grey inside a paper page. */
.form-card{
  border:1px solid var(--line); border-radius:var(--r); background:var(--surface);
  padding:22px 24px 24px; box-shadow:var(--shadow-1);
}
.form-card > h2{
  font-family:var(--mono); font-size:11px; font-weight:600; letter-spacing:.12em;
  text-transform:uppercase; color:var(--muted); margin-bottom:16px;
  padding-bottom:12px; border-bottom:1px solid var(--line);
}
.form-card > h2 .opt{text-transform:none; letter-spacing:0; font-weight:400; font-size:12.5px}
.two-col{display:grid; grid-template-columns:1fr 1fr; gap:22px}
.fields{display:grid; grid-template-columns:repeat(auto-fit,minmax(155px,1fr)); gap:13px}
.fields .wide{grid-column:1/-1}
.fields .span2{grid-column:span 2}
@media (max-width:560px){ .fields .span2{grid-column:1/-1} }
.field label{font-size:11.5px}
.req{color:var(--bad); font-weight:700}

.lines{display:flex; flex-direction:column; gap:10px}
/* 20px between columns, not 10: Qty's label is right-aligned over its number
   and Unit's is left-aligned over its dropdown, so at 10px the two words sat
   ten pixels apart and read as one label, QTY UNIT. */
.line{display:grid; gap:10px 20px; align-items:end;
  grid-template-columns:minmax(160px,1fr) 72px 120px 100px 72px 88px 36px}
/* FatturaPA rows carry one field more than the others: AliquotaIVA and Natura
   are separate columns. On the shared 7-track grid the Net amount landed in
   the remove button's 36px track and the figures clipped at the card edge. */
.line-natura{grid-template-columns:minmax(150px,1fr) 64px 120px 96px 96px 88px 88px 36px}
/* The EN UBL writers (XRechnung, Peppol, credit note) carry the same extra
   column for a different reason: a VAT category picker sits beside the rate,
   Automatic by default, so the same 8-track layout FatturaPA already needed
   fits this case too instead of inventing a second one. */
.line-vatcat{grid-template-columns:minmax(150px,1fr) 64px 120px 88px 64px 128px 88px 36px}
/* Eight labelled tracks do not fit between 781 and 1020 without their column
   headers colliding, so those widths get the stacked layout small screens use. */
@media (max-width:1020px){
  .line-natura, .line-vatcat{grid-template-columns:1fr 1fr; padding-bottom:12px;
    border-bottom:1px solid var(--line)}
  .line-natura .cell-desc, .line-vatcat .cell-desc{grid-column:1/-1}
  .line-natura .net-val, .line-vatcat .net-val{text-align:left}
  /* stacked fields need their names back; the header-row trick only makes
     sense while the row is a single grid line. Two extra classes beat the
     .line + .line label rule further down without !important. */
  .line.line-natura + .line.line-natura label,
  .line.line-vatcat + .line.line-vatcat label{position:static; width:auto;
    height:auto; clip:auto; white-space:normal}
}
.line .net-val{
  font-variant-numeric:tabular-nums; text-align:right; font-size:15px;
  padding:11px 2px 11px 0; color:var(--ink); white-space:nowrap; display:block;
  /* an empty amount still holds the height of a filled one, or the cell
     collapses and the bottom-aligned row drops the Net label below the rest. */
  min-height:46px;
}
.line .input{padding:10px 9px}
/* The unit dropdown carries a code and its name, "H87 piece", and the track
   was 76px against a label needing 110px including the padding and the arrow.
   Chrome clips a select mid-glyph, so "H87 piece" rendered as "H87" and a bare
   vertical stroke, which a visitor reported as a stray line in the column.
   The track now fits the labels people actually pick, and the two longest,
   "MTK square metre" and "MTQ cubic metre", end in an ellipsis instead of half
   a letter. Measured in Chrome: text-overflow does apply to a select. */
.line select.unit{text-overflow:ellipsis}
.line .cell-num .input{text-align:right}
/* Every column header sits over the middle of its own column.

   Left was the first answer and right was the one before it. Right-aligned,
   a header drifted towards the NEXT column's label and left Net floating over
   nothing; left-aligned, the headers were flush with controls that are not,
   because a quantity and a price are right-aligned inside their box, so the
   word and the figure it named pulled apart. Centred, a header is over its own
   column whatever the control inside does with its content, and it cannot
   reach a neighbour. The description keeps its left edge: it is the one wide
   free-text column, and a centred word there floats in the middle of nothing. */
.line .field label{text-align:center}
.line .cell-desc label{text-align:left}
.line .net label{text-align:center}
.icon-x{
  width:36px; height:38px; border:1px solid var(--line-strong); border-radius:var(--r-sm);
  background:transparent; color:var(--muted); font-size:17px; line-height:1; cursor:pointer;
  transition:border-color .15s, color .15s, transform .15s;
}
.icon-x:hover{border-color:var(--bad); color:var(--bad); transform:scale(1.06)}
.icon-x:active{transform:scale(.92)}
.icon-x:disabled{opacity:.35; cursor:not-allowed}
.line-add{margin-top:16px}
@media (min-width:781px){
  .line + .line label{
    position:absolute; width:1px; height:1px; overflow:hidden;
    clip:rect(0 0 0 0); white-space:nowrap;
  }
}
@media (max-width:780px){
  .two-col{grid-template-columns:1fr}
  .line{grid-template-columns:1fr 1fr; padding-bottom:12px; border-bottom:1px solid var(--line)}
  .line .cell-desc{grid-column:1/-1}
  .line .net-val{text-align:left}
}

.sums{
  border-top:1px solid var(--line); padding-top:16px; display:flex; flex-wrap:wrap;
  gap:20px; justify-content:space-between; align-items:flex-end;
}
.sum-table{margin-left:auto; min-width:280px; display:flex; flex-direction:column; gap:8px}
.sum-row{display:flex; justify-content:space-between; gap:24px; font-size:14.5px; color:var(--ink-2)}
/* The totals are the invoice the visitor just wrote, so they are set in the
   interface face, not the code face: JetBrains Mono draws a dotted zero, and
   a dotted zero on an amount line reads as a defect. tabular-nums keeps the
   decimal points of the rows in one column without it. */
.sum-row b{font-variant-numeric:tabular-nums; color:var(--ink); font-weight:600}
.sum-row.grand{
  border-top:2px solid var(--line-strong); padding-top:10px; font-size:16px; color:var(--ink);
}
.sum-row.grand b{font-variant-numeric:tabular-nums; font-size:23px; font-weight:600}
/* A category that isn't Standard or Zero rated needs its exemption reason
   right under the row it belongs to, not a second dialog. Stacked like the
   form's other fields (label above input), not spread like a totals row: it
   is something to fill in, not a number to read. */
.reason-row{display:flex; flex-direction:column; gap:4px; margin:-2px 0 2px}
.reason-row label{font-size:12px; color:var(--muted)}
.reason-row .reason-input{width:100%}
.actions{display:flex; flex-wrap:wrap; gap:10px; align-items:center}
.actions .note{flex-basis:100%}
/* Nugroho, 2026-08-28: the download row hangs directly off .form-wrap, so in
   dark mode it floated on the dark page while the paper override had already
   flipped its ink to dark navy: invisible buttons. Card it like every other
   row in the form so the light sheet carries it in both themes. */
.form-wrap .actions{
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r);
  padding:14px 16px; box-shadow:var(--shadow-1);
}
.notice.has-list{flex-direction:column; gap:2px}
.notice ul{margin:6px 0 0; padding-left:1.15em}
.notice li{font-size:14px; color:var(--ink-2)}

/* ------------------------------------------------------ the column mapper --
   Shown only when the Excel reader cannot tell which column is which, in the
   place the result would have gone. It is a form, not a dialog: nothing is
   blocked, the page keeps scrolling, and Cancel simply removes it. */
.xmap{
  margin-top:24px; padding:18px 20px 20px;
  background:var(--surface); border:1px solid var(--line);
  border-left:3px solid var(--accent); border-radius:var(--r);
  box-shadow:var(--shadow-1);
}
.xmap-title{margin:0 0 6px; font-size:19px}
.xmap-lead{margin:0 0 10px; color:var(--ink-2); font-size:14.5px; max-width:72ch}
.xmap-read{margin:0 0 12px; color:var(--muted); font-size:13.5px}
.xmap-sheet{display:flex; align-items:center; gap:10px; margin-bottom:14px}
.xmap-sheet label{font-family:var(--mono); font-size:10.5px; text-transform:uppercase;
  letter-spacing:.1em; color:var(--muted)}
.xmap-sheet .input{width:auto; min-width:180px}
/* Label and control, two columns, so the eye reads down the field names the
   way it reads down an invoice. */
.xmap-grid{
  display:grid; grid-template-columns:max-content minmax(0, 1fr);
  gap:8px 14px; align-items:center;
}
.xmap-grid > label{font-size:14px; color:var(--ink-2)}
.xmap-grid > label.xmap-req{color:var(--ink); font-weight:600}
.xmap-cell{display:flex; gap:10px; align-items:center; min-width:0}
.xmap-cell .input{min-height:40px; padding:8px 10px; font-size:14px}
.xmap-extra{max-width:230px}
.xmap-preview-lead{margin:16px 0 6px; font-size:13.5px; color:var(--muted)}
.xmap-empty{margin:0; font-size:13.5px; color:var(--muted)}
/* Wide files exist, and the page must not scroll sideways because of one. */
.xmap-scroll{overflow-x:auto}
.xmap-table{border-collapse:collapse; font-size:13.5px; min-width:100%}
.xmap-table th, .xmap-table td{
  border:1px solid var(--line); padding:6px 10px; text-align:left;
  white-space:nowrap;
}
.xmap-table th{color:var(--muted); font-weight:600; background:var(--surface-2)}
.xmap-actions{display:flex; align-items:center; gap:12px; margin-top:18px; flex-wrap:wrap}
.xmap-hint{font-size:13px; color:var(--muted)}
.xmap .btn:disabled{opacity:.5; cursor:not-allowed; transform:none}
@media (max-width:640px){
  .xmap-grid{grid-template-columns:1fr; gap:4px}
  .xmap-grid > label{margin-top:8px}
  .xmap-cell{flex-direction:column; align-items:stretch}
  .xmap-extra{max-width:none}
}
.notice.ok{border-left-color:var(--ok)}
.notice.bad{border-left-color:var(--bad)}
/* Between ok and bad: nothing is wrong, but a date has already passed and the
   visitor is inside an obligation rather than ahead of one. The deadline
   checkers need it; painting "you must already be able to receive" in the
   same green as "you have another year" was reading as reassurance. */
.notice.warn{border-left-color:var(--warn)}

/* drop target on the spreadsheet tools */
.drop{
  margin-top:24px; padding:32px 22px; text-align:center;
  border:1.5px dashed var(--line-strong); border-radius:var(--r);
  background:color-mix(in srgb, var(--bg) 55%, transparent);
  transition:border-color .18s, background .18s, box-shadow .18s;
}
.drop.over,.drop.working{
  border-color:var(--accent); border-style:solid;
  background:var(--accent-soft);
  box-shadow:0 0 0 4px rgba(61,232,160,.10), inset 0 0 40px rgba(61,232,160,.06);
}
.drop.working::before{
  content:""; position:absolute; left:8%; right:8%; bottom:14px; height:3px;
  border-radius:3px;
  background:linear-gradient(90deg, transparent, var(--accent), transparent);
  background-size:40% 100%; background-repeat:no-repeat;
  animation:scanX 1.1s linear infinite;
}
.drop{position:relative}
@keyframes scanX{from{background-position:-40% 0}to{background-position:140% 0}}
.drop-title{font-family:var(--cond); font-size:21px; font-weight:700; letter-spacing:-.015em}
.drop-sub{color:var(--muted); font-size:14.5px; margin:8px 0 16px}

/* The drop zone's internal rhythm, unified with the home drop (owner
   2026-09-19: make the dropzones like the home page's). The heading block
   steps 10px, actions and the conversion strip follow at 16px; descendant
   selectors so the element margins that accumulated under the old orb-first
   markup cannot come back. The same block lives in page.css. */
.drop{padding:24px 24px 20px; margin:0}
.drop .dz-head{display:flex; flex-direction:column; align-items:center; gap:10px}
.drop .dz-orb, .drop .drop-title{margin:0}
.drop .drop-sub{margin:0 auto}
.drop .drop-actions{margin:16px 0 0}
.drop .conv-strip{margin:16px auto 0}

/* The embed page's received-files state: a dialog that lives inside the
   drop zone (owner 2026-09-19), not a popup. One row per recognised file,
   then the count and what comes next. Row colours reuse the format wall's
   own document pictograms (red PDF, emerald XML). */
.drop-state{
  margin:16px auto 0; max-width:420px; text-align:left;
  border:1px solid var(--line-strong); border-radius:10px;
  background:var(--v5-sand);
  padding:10px 14px;
}
.ds-row{display:flex; align-items:center; gap:9px; padding:5px 0}
.ds-ico{width:20px; height:22px; flex:none}
.ds-name{font-weight:600; font-size:13.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.ds-kind{margin-left:auto; flex:none; font:600 10.5px/1 var(--sans); letter-spacing:.5px; color:var(--muted)}
.ds-note{margin:6px 0 0; font-size:13px; color:var(--muted)}


/* ---- frameless variant ---------------------------------------------------

   On the home page and inside the converter panels the dashed box IS the
   product and earns its size, and it keeps the frame. On the spreadsheet
   writer pages and the format detector it stood alone in the page column at
   1096 by 189 pixels: a bordered field of empty space directly under three
   step cards that had already explained the job. Same control, no chrome,
   left aligned with everything above it.

   The page is the drop target on these pages now, so there is no box to aim
   at and no box to miss. The over state moves to the button, which is the only
   thing left to light up. */
.drop-plain{
  margin-top:20px; padding:0; text-align:left;
  border:0; background:none;
}
.drop-plain .drop-title{font-size:16px; font-weight:600; font-family:var(--sans)}
.drop-plain .drop-sub{margin:4px 0 14px}
.drop-plain > p{color:var(--muted); font-size:14.5px; margin:0 0 14px}
.drop-plain.over .btn-primary,
.drop-plain.working .btn-primary{box-shadow:0 0 0 4px var(--accent-soft)}
.drop-plain.working::before{display:none}

/* tool bar */
.toolbar{
  display:flex; align-items:center; justify-content:space-between;
  flex-wrap:wrap; gap:14px 18px; margin-top:24px;
  padding:15px 18px; border:1px solid var(--line); border-radius:var(--r);
  background:var(--surface); box-shadow:var(--shadow-1);
}
.toolbar-info{display:flex; flex-direction:column; gap:2px; min-width:0}
.toolbar-label{
  font-family:var(--mono); font-size:10.5px; text-transform:uppercase;
  letter-spacing:.12em; font-weight:500; color:var(--muted);
}
.toolbar-file{font-size:15px; color:var(--ink-2); overflow-wrap:anywhere}
.toolbar-file.set{color:var(--ink); font-weight:600}
.toolbar-act{display:flex; align-items:center; gap:10px; flex:0 0 auto; flex-wrap:wrap}
.toolbar .input{min-width:210px}

/* ---- frameless variant ---------------------------------------------------

   Same reasoning as .drop-plain below the step cards. A toolbar holding a
   control and an action is a strip and earns its frame: that is what the
   Standard picker and the template button make on the XRechnung and ZUGFeRD
   spreadsheet pages. Where the national formats cut the picker, because there
   is nothing to pick, the frame was left wrapping one 244 pixel button in
   1096 by 76 pixels of surface, directly above three step cards wearing the
   same border and the same background, so it read as a fourth card that had
   lost its text. Frameless there, and the button lines up with the card grid
   under it instead of sitting inside a box of its own. */
.toolbar-plain{
  padding:0; border:0; background:none; box-shadow:none;
}
body.dragging::after{
  content:""; position:fixed; inset:0; z-index:50; pointer-events:none;
  border:3px dashed var(--accent); background:var(--accent-soft); opacity:.35;
}
@media (max-width:560px){
  .toolbar{align-items:stretch}
  .toolbar-act, .toolbar-act .btn, .toolbar .input{width:100%}
  /* A full-bleed button belongs inside the strip, where it fills the box it
     lives in. Once the box is gone it is just a button on the page, and the
     one under it, "Select workbook", is auto width. */
  .toolbar-plain .toolbar-act, .toolbar-plain .toolbar-act .btn{width:auto}
}

/* One strip, not three cards. The design law forbids three identical step
   cards and the writer form dropped them entirely (mock-v5-create replaces
   them with one instruction line and a guide behind a button). The converter
   and the Excel pages have no approved mock and their three cards carry real
   per-format copy, so the copy stays and the cards go: a single bordered row
   with hairlines between the steps. */
.steps{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(215px,1fr));
  margin-top:24px; border:1px solid var(--v5-line); border-radius:8px;
  background:var(--v5-surface); overflow:hidden;
}
.step-card{
  border:none; border-left:1px solid var(--v5-line); border-radius:0;
  background:none; box-shadow:none; transition:none;
  padding:16px 20px; display:flex; flex-direction:column; gap:4px;
}
.step-card:first-child{border-left:none}
.step-card:hover{transform:none}
.step-card b{
  font:600 12px/1 var(--sans); letter-spacing:.5px; text-transform:uppercase;
  color:var(--v5-muted);
}
.step-card span{font-size:14.5px; color:var(--v5-ink-2); line-height:1.5}
@media (max-width:700px){
  .steps{grid-template-columns:1fr}
  .step-card{border-left:none; border-top:1px solid var(--v5-line)}
  .step-card:first-child{border-top:none}
}

/* ------------------------------------------------------- the MCP page ---*/
/* --code, the MCP page's coder face, is a token: see tokens.css. */

.lede{color:var(--ink-2); font-size:16.5px; max-width:var(--measure); margin:0 0 18px}
.fineprint{color:var(--muted); font-size:14px; line-height:1.6; max-width:var(--measure); margin-top:14px}
.mid code, .fineprint code, .lede code{
  font-family:var(--code); font-size:.92em; background:var(--surface-2);
  border:1px solid var(--line); border-radius:var(--r-xs); padding:1px 5px;
}

.clientlist{list-style:none; padding:0; margin:0;
  display:grid; grid-template-columns:repeat(auto-fit,minmax(232px,1fr)); gap:12px}
.clientlist-item{
  border:1px solid var(--line); border-radius:var(--r); background:var(--surface);
  padding:13px 15px; display:flex; flex-direction:column; gap:3px; box-shadow:var(--shadow-1);
  transition:transform .16s, border-color .16s;
}
.clientlist-item:hover{transform:translateY(-2px); border-color:var(--line-strong)}
.clientlist-item strong{font-family:var(--cond); font-size:15.5px}
.clientlist-item span{
  font-family:var(--code); font-size:12px; color:var(--muted);
  overflow-wrap:anywhere;
}

/* /mcp install + key cards. Same surface grammar as .clientlist: 1px line,
   var(--surface), shadow-1. One accent only; path CTAs are primary/secondary.
   Owner 2026-09-18 vision pass: Install h2 sat too far from its lede; Config
   sat too tight under the path cards; Copy must sit inside the snippet. */
/* the 12px h2 + .lede step is the family rule now; the #install exception folded into it */
#install .mcp-paths{margin:16px 0 0}
.mcp-config{margin-top:28px; margin-bottom:10px}
.mcp-paths{
  display:grid; grid-template-columns:1fr 1fr; gap:14px; margin:16px 0 0;
}
.snippet-wrap{position:relative; margin:0 0 4px}
.snippet-wrap .snippet{
  width:100%; margin:0; padding:14px 48px 14px 16px;
}
.snippet-copy{
  position:absolute; top:10px; right:10px;
  width:34px; height:34px; display:inline-flex; align-items:center; justify-content:center;
  border:1px solid var(--line-strong); border-radius:var(--r-xs);
  background:var(--surface); color:var(--ink-2); cursor:pointer; padding:0;
  transition:color .15s, border-color .15s, background-color .15s, transform .15s;
}
.snippet-copy:hover{color:var(--ink); border-color:var(--accent); background:var(--accent-soft)}
.snippet-copy:active{transform:scale(.96)}
.snippet-copy.copied{background:var(--ok); border-color:var(--ok); color:var(--accent-ink)}
.mcp-path{
  border:1px solid var(--line); border-radius:var(--r); background:var(--surface);
  padding:18px 18px 16px; display:flex; flex-direction:column; gap:10px;
  box-shadow:var(--shadow-1); min-height:100%;
}
.mcp-path-lead{
  margin:0; font-family:var(--code); font-size:11px; letter-spacing:.06em;
  text-transform:uppercase; color:var(--muted); line-height:1.45;
}
.mcp-path h3{
  margin:0; font-family:var(--cond); font-size:18px; color:var(--ink);
  text-wrap:balance;
}
.mcp-path p{margin:0; font-size:14.5px; line-height:1.55; color:var(--ink-2); text-wrap:pretty}
.mcp-path .btn{margin-top:auto; align-self:flex-start}
.mcp-facts{
  list-style:none; margin:0 0 20px; padding:0;
  display:flex; flex-wrap:wrap; gap:8px 20px;
}
.mcp-facts li{
  display:flex; align-items:center; gap:8px;
  font-size:13.5px; color:var(--muted);
}
.mcp-facts li::before{
  content:""; width:6px; height:6px; border-radius:50%;
  background:var(--accent); flex:0 0 auto;
}
.mcp-card{
  border:1px solid var(--line); border-radius:var(--r); background:var(--surface);
  padding:20px 20px 18px; box-shadow:var(--shadow-1);
  display:flex; flex-direction:column; gap:14px; max-width:var(--measure);
}
.mcp-card > .lede{margin:0}
.mcp-card > .btn-row{margin:0}
.mcp-card > .fineprint{margin:0}
@media (max-width:720px){
  .mcp-paths{grid-template-columns:1fr}
}

.buycard{
  border:1px solid var(--line-strong); border-radius:var(--r-lg); background:var(--surface);
  padding:26px 28px; max-width:var(--card); box-shadow:var(--shadow-2), var(--glow-accent);
}
.buycard-head{border-bottom:1px solid var(--line); padding-bottom:16px; margin-bottom:16px}
.buycard .plan-price{font-size:40px}
.ticks{list-style:none; padding:0; margin:0 0 22px; display:flex; flex-direction:column; gap:9px}
.ticks li{position:relative; padding-left:24px; font-size:15px; color:var(--ink-2)}
.ticks li::before{
  content:""; position:absolute; left:1px; top:2px; width:15px; height:15px;
  background-color:var(--ok);
  -webkit-mask:var(--mark-check) center/contain no-repeat;
          mask:var(--mark-check) center/contain no-repeat;
}
.paypal-slot{min-height:52px}
.buy-status{font-size:14px; color:var(--muted); margin-top:12px; line-height:1.55}
.buy-status.bad{color:var(--bad); background:var(--bad-soft); border-radius:var(--r-sm); padding:10px 12px}

/* ---------------------------------------------------- the key dialog ---*/
.keydlg{
  border:1px solid var(--line-strong); border-radius:var(--r-lg); background:var(--surface);
  color:var(--ink); padding:28px 30px 26px; max-width:640px; width:calc(100vw - 32px);
  max-height:calc(100vh - 48px); overflow:auto; box-shadow:var(--shadow-3); position:relative;
}
.keydlg::backdrop{background:rgba(4,6,10,.62); backdrop-filter:blur(4px)}
.keydlg[open]{animation:dlgIn .28s cubic-bezier(.2,.9,.3,1.1) both}
@keyframes dlgIn{from{opacity:0; transform:translateY(14px) scale(.97)}to{opacity:1; transform:none}}
.keydlg h2{font-family:var(--cond); font-size:25px; margin:0 0 4px}
.keydlg-eyebrow{color:var(--ok); margin:0; font-family:var(--mono); font-size:11px;
  text-transform:uppercase; letter-spacing:.12em; font-weight:600}
.keydlg-x{
  position:absolute; top:12px; right:14px; border:0; background:none; cursor:pointer;
  font-size:26px; line-height:1; color:var(--muted); padding:4px 8px; border-radius:var(--r-xs);
  transition:color .14s, background .14s, transform .14s;
}
.keydlg-x:hover{color:var(--ink); background:var(--surface-2); transform:rotate(90deg)}
.keystep{
  font-family:var(--mono); font-size:10.5px; letter-spacing:.12em; text-transform:uppercase;
  color:var(--muted); margin:22px 0 8px; font-weight:500;
}

.copyrow{display:flex; gap:10px; align-items:stretch}
.copyrow-block{align-items:flex-start}
.copyrow .btn{flex:0 0 auto; align-self:flex-start}
.copybtn.copied{background:var(--ok); border-color:var(--ok); color:var(--accent-ink)}

.keyval{
  flex:1 1 auto; font-family:var(--code); font-size:19px; letter-spacing:.06em;
  background:var(--accent-soft); border:1px solid var(--accent); border-radius:var(--r-sm);
  padding:11px 14px; user-select:all; overflow-wrap:anywhere;
}

/* The connect card (owner pass 2026-09-19): the URL row wears the snippet
   family, so the steps must sit in the same compact rhythm - no default ol
   indent, card text size, one gap line between rows. */
.connect-steps{
  list-style:decimal; margin:0; padding-left:22px;
  display:flex; flex-direction:column; gap:7px;
  font-size:15px; line-height:1.5; color:var(--ink-2);
}
.connect-steps li{padding-left:4px}
.mcp-card .snippet-wrap{margin:0}

/* ---- /mcp hero scale (owner pass 2026-09-19): the hero h1 sat a step above
   the section rhythm the page reads at, and the h1-to-lede gap rode the
   default 12px intro gap on top of the h1's slack. One step down, one gap
   tighter, scoped to this page's head so the other page.css heroes keep
   their approved size. */
.mcp-head h1{font-size:clamp(26px,2.6vw,32px)}
.mcp-head .page-intro{gap:8px}
/* h2 straight into its .lede is one 12px step everywhere, not 24: the
   2026-09-18 #install exception becomes the family rule. */
.block h2 + p.lede{margin-top:12px}

.clientpick{display:flex; flex-wrap:wrap; gap:7px; margin-bottom:12px}
.chip{
  font-family:var(--cond); font-size:13.5px; cursor:pointer;
  border:1px solid var(--line-strong); background:transparent; color:var(--ink-2);
  border-radius:999px; padding:6px 13px;
  transition:color .15s, background-color .15s, border-color .15s;
}
.chip:hover{border-color:var(--accent); color:var(--accent); transform:translateY(-1px)}
.chip:active{transform:scale(.95)}
.chip[aria-selected="true"]{
  background:var(--accent); border-color:var(--accent-deep); color:var(--accent-ink); font-weight:600;
}

.snippet{
  flex:1 1 auto; margin:0; min-width:0; font-family:var(--code); font-size:12.5px;
  line-height:1.55; background:var(--band); color:var(--band-ink);
  border-radius:var(--r-sm); padding:14px 16px; overflow-x:auto; white-space:pre;
  border:1px solid var(--line);
}
.snippet-where{
  display:flex; flex-direction:column; gap:2px; margin:10px 0 0;
  font-family:var(--code); font-size:12.5px; color:var(--ink-2); overflow-wrap:anywhere;
}
.snippet-note{font-size:14px; color:var(--muted); margin:8px 0 0; line-height:1.55; max-width:var(--measure)}
.snippet-link{margin:10px 0 0; font-size:14px}

.recinput{
  flex:1 1 auto; font-family:var(--code); font-size:14.5px; color:var(--ink);
  background:var(--bg); border:1px solid var(--line-strong);
  border-radius:var(--r-sm); padding:11px 13px; min-width:0;
  transition:border-color .16s, box-shadow .16s;
}
.recinput:focus{outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft)}

@media (max-width:560px){
  .keydlg{padding:24px 18px 22px}
  .copyrow{flex-direction:column}
  .copyrow .btn{align-self:stretch}
  .keyval{font-size:16px}
}

/* light mode dissolves the page-head band, the same call as the home hero:
   one pale sheet, no sudden dark stripe at the top of a light page */
:root[data-theme="light"] .page-head{background:transparent; color:var(--ink)}
:root[data-theme="light"] .page-head::before,
:root[data-theme="light"] .page-head::after{content:none}
:root[data-theme="light"] .page-head h1{color:var(--ink)}
:root[data-theme="light"] .page-intro p{color:var(--muted)}
:root[data-theme="light"] .page-intro p a{color:var(--accent)}
/* The band goes transparent in light mode, so the #fff that lifts a bold date
   out of the dark band paints white on #F2F5F9 and the date disappears.
   Measured at 255,255,255 on 242,245,249 before this line existed. */
:root[data-theme="light"] .page-intro p strong{color:var(--ink)}

/* ---- site-wide ambient, mirrored from app.css ----------------------------
   Hand-written pages load only this sheet, so the aurora orbs, the faint
   grid and the translucent panel tokens they feed on must live here too. */
/* The ambient tokens (--panel, --gl-line, --aura-hi, --orb-op) are in tokens.css. */
.aura{
  position:fixed; inset:0; z-index:-2; overflow:hidden;
  background:radial-gradient(ellipse 80% 50% at 50% -20%, var(--aura-hi), transparent 60%), var(--bg);
}
.orb{position:absolute; border-radius:50%; filter:blur(110px); opacity:var(--orb-op); will-change:transform}
.orb-1{width:560px;height:560px;left:-140px;top:-180px;background:var(--orb-1);animation:drift1 26s ease-in-out infinite alternate}
.orb-2{width:480px;height:480px;right:-120px;top:60px;background:var(--orb-2);animation:drift2 32s ease-in-out infinite alternate}
.orb-3{width:420px;height:420px;left:38%;top:52%;background:var(--orb-3);animation:drift3 38s ease-in-out infinite alternate}
@keyframes drift1{to{transform:translate(90px,70px)}}
@keyframes drift2{to{transform:translate(-80px,110px)}}
@keyframes drift3{to{transform:translate(-60px,-90px)}}
.gridlines{
  position:fixed; inset:0; z-index:-1; pointer-events:none;
  background-image:
    linear-gradient(var(--gl-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--gl-line) 1px, transparent 1px);
  background-size:72px 72px;
  /* One-off, and not a colour: inside a mask, #000 means fully opaque and
     transparent means cut away. The palette has nothing to say about it. */
  mask-image:radial-gradient(ellipse 90% 62% at 50% 0%, #000 30%, transparent 78%);
  -webkit-mask-image:radial-gradient(ellipse 90% 62% at 50% 0%, #000 30%, transparent 78%);
}
@media (prefers-reduced-motion:reduce){ .orb{animation:none} }

/* tools directory (/tools), rebuilt in the v5 grammar. It was the dark
   language's directory wearing the light island: glass panels, an icon chip
   per group and a grid of boxed arrow chips. The structure is an index now,
   and an index is rows: each shelf is the same hairline-row list the related
   sections wear site-wide, grouped by the file the visitor starts from. */
/* no margin of its own: .page already opens the content area, and the two
   stacked put the first group a screen away from its own introduction.
   The 860px column keeps the two row columns inside a readable measure. */
.tdir{max-width:860px; margin:0 auto; display:flex; flex-direction:column; gap:28px}
/* the head shares the shelves' 860px column, so the h1, the lead and the
   jump row start on the same left edge the first shelf does */
.tdir-intro{max-width:860px; margin:0 auto}
/* each shelf is a card (owner rebuild 2026-09-17): surface, hairline, the
   card radius, and a coloured pictogram square saying what the shelf does -
   the same icon language the format wall and the drop zones speak */
.tdir-group{
  background:var(--v5-surface); border:1px solid var(--v5-line-strong);
  border-radius:10px; padding:22px 26px;
  animation:resultIn .4s ease both; scroll-margin-top:80px;
}
.tdir-group:nth-child(2){animation-delay:.05s}
.tdir-group:nth-child(3){animation-delay:.1s}
.tdir-group:nth-child(4){animation-delay:.15s}
.tdir-group:nth-child(5){animation-delay:.18s}
.tdir-group:nth-child(6){animation-delay:.21s}
.tdir-group:nth-child(7){animation-delay:.24s}
@media (prefers-reduced-motion:reduce){ .tdir-group{animation:none} }
.tdir-head{
  display:grid; grid-template-columns:auto 1fr; column-gap:16px; row-gap:6px;
  align-items:center; margin:0;
}
.shelf-ico{
  width:38px; height:38px; border-radius:9px; grid-row:1; grid-column:1;
  display:inline-flex; align-items:center; justify-content:center;
}
.shelf-ico svg{width:20px; height:20px; color:#fff}
.tdir-head h2{
  display:flex; align-items:center; gap:12px; margin:0;
  font-family:var(--display); font-weight:500; font-size:20px;
  letter-spacing:-.01em; color:var(--v5-ink);
}
.tdir-head p{grid-column:1 / -1; grid-row:2; color:var(--v5-muted); font-size:15px; margin:0; max-width:56ch}
.tdir-list{
  list-style:none; margin:16px 0 0; padding:0;
  display:grid; grid-template-columns:1fr 1fr; gap:0 40px;
}
/* one row = one tool: a fixed grid keeps the hairlines aligned across both
   columns - the multicol balancing before left them ragged (owner
   2026-09-17: "berantakan") - and the chevron names the row as a link */
.tdir-list li{border-top:1px solid var(--v5-line)}
.tdir-list a{
  display:flex; justify-content:space-between; align-items:baseline; gap:12px;
  padding:11px 10px; font-size:14.5px; color:var(--v5-ink); font-weight:600;
  text-decoration:none; border-radius:6px;
  transition:background-color .15s cubic-bezier(.2,0,0,1);
}
/* one quiet arrow, right-aligned: the row reads as a link, not a label */
.tdir-list a::after{
  content:"→"; margin-left:auto; color:var(--v5-muted);
  font-weight:400; font-size:13px;
}
.tdir-list a:hover{background:var(--v5-sand); text-decoration:none}
.tdir-list a:focus-visible{outline:2px solid var(--v5-amber); outline-offset:2px}
@media (max-width:900px){ .tdir-list{grid-template-columns:1fr} }
.site-nav a[aria-current="page"]{color:var(--ink); font-weight:600}

/* The Converter / Validator / Create strip, same component as app.css so the
   two sheets cannot drift into two different-looking tabs. On tool pages that
   retain it, it sits under the title rather than in a panel head. Writer forms
   intentionally omit it: the form is the destination, not another view. */
.tc-tabs{display:inline-flex; gap:4px; background:var(--tab-track); padding:4px; border-radius:10px; margin-top:16px}
.tc-tab{
  font-size:12.5px; font-weight:500; color:var(--muted);
  padding:7px 14px; border-radius:8px;
  transition:background .18s, color .18s, transform .12s;
}
.tc-tab:hover{color:var(--ink); text-decoration:none}
.tc-tab:active{transform:scale(.96)}
.tc-tab.active{background:var(--tab-active-bg); color:var(--tab-active-ink); box-shadow:var(--tab-active-sh)}
:root[data-theme="light"] .page-head .tc-tab{color:var(--muted)}

/* The strip is a CONTROL, not a paragraph. .page-intro is a column flex, so a
   block child stretches to the full text measure: three small tabs sat in a
   606px grey track with half of it empty. It hugs its own width now. */
.page-intro .tc-tabs{align-self:flex-start}

/* And the strip added its own height to a head that already carried 38px of
   padding under it, with another 30px on the block below, so the form opened
   roughly a hundred pixels further down than the title implied. Tightened only
   where the strip exists, so /tools and the MCP pages, which use the same head
   without tabs, keep the spacing they were designed with. */
.page-head:has(.tc-tabs){padding-bottom:20px}
.page-head:has(.tc-tabs) + .wrap.page{padding-top:16px}

/* Tabular figures everywhere: lining up a money column was the only real
   job the monospace face had, and Inter's tnum does it without the dot. */
body{font-variant-numeric:tabular-nums}

/* Hidden on screen. This sits BEFORE the print block on purpose: both
   rules are a single class, so the cascade is decided by source order,
   and with this line last it won inside the print block too and the mark
   never reached paper. Printing the page to PDF and searching it is what
   caught that; every screen check said the element was visible. */
.print-brand{display:none}

/* ---------- print, the writer pages ----------
   A create page is a FORM, which is why it was left out of the first print
   pass. Nugroho asked for it anyway and he is right: what somebody wants on
   paper here is the invoice they just typed, before they issue it, and the
   fields already hold every value that invoice has.

   So the inputs are un-styled rather than hidden. A printed <input> keeps its
   value; what makes it look like a form instead of a document is the box
   around it, so the boxes come off and the values stay. */
/* ---- /pdf-to-xrechnung: the page beside the fields read off it -----------

   This tool used to open on an empty form, which is the parsing pipeline drawn
   on screen rather than the job anyone came to do. The job is: give it a PDF,
   get a sendable file. So the empty state is a drop target and nothing else,
   and once a file is read the screen becomes the one thing a form alone can
   never be, a comparison: the paper on the left, what was read from it on the
   right, and hovering a field lights the words it came from.

   Only this tool loads this block's markup; every other writer keeps the plain
   form. */

/* A card in the same idiom as every other card on the site, not a slab of
   plain background across the viewport. Its width comes from its content. */
.pdfx-drop{margin-top:24px}
.pdfx-drop-in{
  max-width:520px; margin:0 auto; padding:30px 26px 26px; text-align:center;
  border:1px dashed var(--line-strong); border-radius:var(--r);
  background:var(--surface); box-shadow:var(--shadow-1);
  transition:border-color .16s, background .16s;
}
.pdfx-drop.over .pdfx-drop-in{border-color:var(--accent); background:var(--accent-soft)}
.pdfx-drop .toolbar-label{display:block; margin-bottom:8px}
.pdfx-drop-say{
  color:var(--ink); font-size:17px; font-weight:600; letter-spacing:-.01em;
  margin:0 0 16px;
}
.btn-drop{font-size:15px; padding:11px 20px}
.pdfx-drop .steps{margin-top:26px}

/* The review, in the Create editor's grammar (owner, 2026-09-17: one form,
   wherever it was filled from). The scan column takes the seat the live sheet
   has on Create; the download row rides it the same way (.ed-actions), so
   there is one command row per page whichever seat it sits in. */
.pdfx-split{
  display:grid; grid-template-columns:minmax(0,1fr); gap:24px;
  align-items:start; margin-top:16px;
}
.pdfx-split.open{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr)}
.pdfx-split .form-wrap{min-width:0}

/* What the read did, said in the two counts and one sentence. It sits above
   the first section because it answers the question the whole review opens
   with: why are some boxes empty. */
.read-verdict{margin:0 0 18px}
.read-pills{display:flex; flex-wrap:wrap; gap:8px}
.read-lead{
  margin:10px 0 0; font:500 13.5px/1.5 var(--sans);
  color:var(--v5-ink-2); max-width:62ch;
}
.pdfx-split .pdfx-agree{flex-basis:100%; margin:2px 0 0}

.pdfx-paper{margin-top:0}
.pdfx-paper-card{
  min-width:0; border:1px solid var(--v5-line-strong); border-radius:8px;
  background:var(--v5-surface); overflow:hidden;
}
.pdfx-paper-bar{
  padding:10px 14px; border-bottom:1px solid var(--v5-line); font-size:13px;
  color:var(--v5-ink-2); background:var(--v5-surface); white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis;
  font-weight:600;
}
.pdfx-paper-scroll{
  max-height:calc(100vh - 210px); overflow-y:auto; overflow-x:hidden; padding:14px;
  display:flex; flex-direction:column; align-items:center; gap:14px;
  scroll-behavior:smooth;
}
.pdfx-paper-hint{
  margin:0; padding:10px 14px; border-top:1px solid var(--v5-line);
  font-size:12.5px; color:var(--v5-muted); background:var(--v5-surface);
}
.paper-sheet{position:relative; box-shadow:var(--shadow-1); background:var(--paper-bg); border-radius:4px}
.paper-sheet canvas{display:block; border-radius:4px}
.paper-marks{position:absolute; inset:0; pointer-events:none}
/* The one bold moment on the page. Everything else here is quiet on purpose. */
.paper-mark{
  position:absolute; border-radius:3px;
  background:color-mix(in srgb, var(--accent) 32%, transparent);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 32%, transparent);
  animation:pdfx-lit .16s ease-out;
}
@keyframes pdfx-lit{from{opacity:0; transform:scale(.97)} to{opacity:1; transform:none}}

/* A field that knows where it came from says so, quietly, and says it louder
   the moment you touch it. */
.pdfx-split [data-sourced]{position:relative}
.pdfx-split .field:has([data-sourced]) > label::after{
  content:""; display:inline-block; width:5px; height:5px; margin-left:7px;
  border-radius:50%; background:var(--v5-amber); vertical-align:middle;
}
.pdfx-split [data-sourced]:hover,
.pdfx-split [data-sourced]:focus{border-color:var(--v5-amber)}
.pdfx-split .line:hover{background:var(--v5-sand); border-radius:9px}

@media (prefers-reduced-motion:reduce){
  .paper-mark{animation:none}
  .pdfx-paper-scroll{scroll-behavior:auto}
}
@media (max-width:1020px){
  /* Below this the two panes stop being a comparison and start being a
     scroll, so the paper goes back to being one thing above the other. */
  .pdfx-split.open{grid-template-columns:minmax(0,1fr)}
  .pdfx-paper{position:static}
  .pdfx-paper-scroll{max-height:52vh}
}

/* Off-screen print host for the OCR page: the same paper anatomy the Create
   page prints. liveSheet paints #edSheet on every input; page-ui's exPrint
   handler prints it. Off-screen rather than display:none because the painter
   measures the host it paints into. */
.ed-print{
  position:absolute; left:-10000px; top:0; width:794px; background:#fff;
}

/* The pills the review speaks in. app.css defines these for the converter
   pages; the writer pages load only this sheet, and a class defined nowhere
   renders as bare text, which is how the review first shipped saying
   "22 fields read 13 still needed" in body copy. */
.pill{
  display:inline-flex; align-items:center; gap:7px; white-space:nowrap;
  font-size:12.5px; font-weight:600; padding:5px 11px; border-radius:999px;
  border:1px solid var(--v5-line-strong); color:var(--v5-ink-2); background:transparent;
}
.pill.ok{background:var(--ok-soft); border-color:transparent; color:var(--ok)}
.pill.warn{background:var(--warn-soft); border-color:transparent; color:var(--warn)}
.pill.bad{background:var(--bad-soft); border-color:transparent; color:var(--bad)}

/* Whether the lines found add up to the total the invoice printed. This is the
   whole reason to trust a reading: not that the tool says it worked, but that
   two numbers taken from different parts of the page agree. Silent when the
   page printed no total to check against, because an unanswerable question is
   worse than an unasked one. Flat since owner round 2 (2026-09-18): it lived
   in a green box inside the verdict's sand card, a card in a card. */
.pdfx-agree{
  display:flex; align-items:baseline; gap:8px; margin:10px 0 0;
  font:600 13.5px/1.5 var(--sans); color:var(--ok);
}
.pdfx-agree.off{color:var(--warn)}


/* =========================================================================
   v5, the workbench language for the page.css families
   (mock-v5-create.html and mock-v5-help.html, owner-approved 2026-09-05)

   This started as body.v5help, one page. It is now the body, because every
   family this sheet dresses is ported: writer form, writer excel, writer
   output, converter, and the hand-written pages. There is no scope left to
   name, so there is no scope: an unscoped island cannot leak into a page it
   was not meant for, because there is no such page.

   The island re-declares the STANDARD tokens at v5 values, so components that
   already read tokens follow without being restyled one by one. Values are
   COPIED FROM the mocks; their 4-pt spacing scale (8/12/16/24/32/48/72) is
   written out as literals because this sheet carries no spacing scale.

   Light is a decision, not a theme member. The theme toggle is stood down on
   these pages the same way it is on the v5 home, and the .form-wrap island
   that used to paint the form as paper inside a dark page is gone with the
   dark page: the whole surface is paper now.

   page.css pages never load app.css, so the two v5 faces are declared here
   instead of borrowed, the same duplication-for-isolation the Inter faces
   above already accept.
   ========================================================================= */
@font-face{
  font-family:"Archivo"; font-style:normal; font-weight:500 600; font-display:swap;
  src:url(/fonts/archivo-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;
}
@font-face{
  font-family:"Fraunces"; font-style:normal; font-weight:100 900; font-display:swap;
  src:url(/fonts/fraunces-latin.woff2?v=2) 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;
}
body{
  /* the full standard set, matching the app.css island so the two skins
     resolve the same names to the same values */
  --line-strong:var(--v5-line-strong);
  --grad:none; --foil:var(--v5-amber-deep); --foil-soft:rgba(184,119,22,.10);
  --ok:#0B7A50; --row-hover:rgba(23,33,43,.035);
  --band:var(--v5-bg); --band-2:var(--v5-sand);
  --band-ink:var(--v5-ink); --band-muted:var(--v5-muted); --band-hi:var(--v5-ink);
  --head-h:64px;
  --panel:rgba(23,33,43,.03); --panel-hi:rgba(23,33,43,.05);
  --shadow-1:0 1px 2px rgba(23,33,43,.05);
  --shadow-2:0 1px 2px rgba(23,33,43,.05);
  font-size:16px; line-height:1.5;
  --bg:var(--v5-bg); --surface:var(--v5-surface); --surface-2:var(--v5-sand);
  --ink:var(--v5-ink); --ink-2:var(--v5-ink-2); --muted:var(--v5-muted);
  --line:var(--v5-line); --line-strong:var(--v5-line-strong);
  --accent:var(--v5-amber); --accent-deep:var(--v5-amber-deep);
  --accent-ink:var(--v5-ink); --accent-soft:rgba(227,154,46,.14);
  --head-glass:rgba(243,245,247,.90);
  color-scheme:only light;
  background:var(--v5-bg); color:var(--v5-ink);
}
.aura{
  background:radial-gradient(1100px 480px at 70% -10%, rgba(227,154,46,.05), transparent 62%), var(--v5-bg);
}
.aura .orb,
.gridlines{display:none}

/* -------------------------------------------------------------------------
   The page.css families, held to mock-v5-create.html.

   Everything above resolves tokens and follows the island. What is below is
   the part a token cannot carry: shapes, faces and the rules the dark
   language painted on top of the palette (the aurora on the page head, the
   pill tab strip, the condensed headline face).
   ------------------------------------------------------------------------- */

/* the page head: flat paper, no aurora, no grid. Same stand-down the v5 home
   makes in app.css, and for the same reason: v5 forbids the glow. */
.page-head{
  background:var(--v5-bg); color:var(--v5-ink);
  padding:32px 0 16px; overflow:visible;
}
.page-head::before, .page-head::after{content:none}
.page-head h1{
  font-family:var(--display); font-weight:500; color:var(--v5-ink);
  font-size:clamp(28px,3.2vw,40px); line-height:1.05; letter-spacing:-.02em;
}
.page-intro p{color:var(--v5-ink-2); font-size:16px; max-width:62ch}
.page-intro p strong{color:var(--v5-ink); font-weight:600}
.page-intro p a{color:var(--v5-ink); text-decoration-color:var(--v5-amber)}

/* ...and the same decision for every other link on these pages, which is what
   mock-v5-create.html states at its line 66: a{color:var(--ink)}.

   page.css:44 says a{color:var(--accent)}, which was right when --accent was
   mint on near-black. This island rebinds --accent to --v5-amber (see the
   token block above), so every prose link on a writer page, /pricing,
   /support and the legal pages became #E39A2E on #FDFBF4: 2.27:1, the worst
   contrast on the site, measured on 12 links on /create-xrechnung alone.
   Amber is a FILL. Dark ink on an amber button reads about 8:1; the same
   amber as text does not read at all. The rule above was already the right
   answer, applied to one selector. */
a{color:var(--v5-ink)}
/* =========================================================================
   The sibling list under a writer page, in the same grammar app.css uses for
   the reader pages' "Other formats". It was eight links joined by middots
   inside a single <p>: a word soup, and the last one left on the site.
   ========================================================================= */
/* No top rule, owner call 2026-09-09: the converter's treatment generalized
   to every page. The sibling selector repeats because section.block +
   section.block outranks the bare class where #related follows #about. */
.tool-related,
section.block + section.block.tool-related{padding:56px 0; border-top:0}
.related-list{
  list-style:none; margin:20px 0 0; padding:0;
  columns:2; column-gap:48px;
}
.related-list li{
  border-top:1px solid var(--v5-line); padding:10px 0;
  font-size:14.5px; break-inside:avoid;
}
.related-list li a{color:var(--v5-ink); font-weight:600; text-decoration:none}
.related-list li a:hover{
  color:var(--v5-ink); text-decoration:underline;
  text-decoration-color:var(--v5-amber); text-underline-offset:3px;
}
/* Quiet, without a rule, on every page now: the closing line sat between two
   hairlines, this sheet's own border-top above it and the related section's
   border-top below it, one sentence in a box drawn by two rules that each
   belonged to a neighbour. The converter dropped both first (2026-09-09) and
   the owner took them off the rest the same day. The row rules INSIDE the
   related list stay - they are the component. */
.close-note{
  margin:28px 0 0; font-size:14px; color:var(--v5-muted);
}
/* The writer's own closing line. It used to be a full width .close-note
   under BOTH columns with a rule over it, which drew a line across the page
   under a form that had already closed itself. It belongs to the download
   buttons, so it sits under them, quiet and without a rule. */
.ed-note{
  margin:4px 0 0; font-size:13px; line-height:1.5; color:var(--v5-muted);
}
.ed-note a{color:var(--v5-ink-2); text-decoration:underline; text-underline-offset:3px}
.ed-note a:hover{color:var(--v5-ink); text-decoration-color:var(--v5-amber)}
.close-note a{color:var(--v5-ink-2); text-decoration:underline; text-underline-offset:3px}
.close-note a:hover{
  color:var(--v5-ink); text-decoration-color:var(--v5-amber);
}
@media (max-width:900px){ .related-list{columns:1} }
.steps-list{list-style:none; margin:0; padding:0}

/* ink text, amber underline. See the same rule in app.css: no amber shade
   that still reads as amber clears 4.5:1 on this paper, and a hover state
   is text too. */
a:hover{
  color:var(--v5-ink); text-decoration:underline;
  text-decoration-color:var(--v5-amber); text-underline-offset:3px;
}
/* the eyebrow is the mock's .micro: Inter 600, half-point tracking, muted.
   The mono face at .15em was the dark language's label voice. */
.eyebrow{
  font-family:var(--sans); font-size:12px; letter-spacing:.5px;
  text-transform:uppercase; font-weight:600; color:var(--v5-muted);
}

/* headings take the display face across the sheet, as they do in the mock */
h1, h2, h3{font-family:var(--display); font-weight:500}
h1{font-size:clamp(28px,3.2vw,40px); line-height:1.05; letter-spacing:-.02em}
h2{font-size:clamp(21px,2.4vw,28px); line-height:1.1; letter-spacing:-.02em}
h3{font-size:17px; line-height:1.25; letter-spacing:-.01em; font-weight:500}

/* the ambient layer, stood down like the home's */
.aura{
  background:radial-gradient(1100px 480px at 70% -10%, rgba(227,154,46,.07), transparent 62%), var(--v5-bg);
}
.aura .orb, .gridlines{display:none}

/* the header, to the mock */
.site-head{background:rgba(243,245,247,.92); border-bottom-color:var(--v5-line)}
.site-nav a{color:var(--v5-ink); font-weight:500; font-size:16px}
.site-nav a:hover{
  color:var(--v5-ink); text-decoration:underline;
  text-decoration-color:var(--v5-amber); text-underline-offset:5px;
}
.brand{color:var(--v5-ink)}
/* The language picker is one popup, not a chip row. Native details, the same
   disclosure the burger uses, so it opens on touch and needs no script. The
   summary is one compact chip at every width; the menu reuses the panel
   recipe the burger established. */
.lang-pick{position:relative}
.lang-pick > summary{
  list-style:none; cursor:pointer; user-select:none;
  display:inline-flex; align-items:center; gap:6px;
  font-size:12.5px; font-weight:600; letter-spacing:.02em;
  color:var(--v5-muted); border:1px solid var(--line);
  border-radius:8px; padding:7px 10px; background:none;
  transition:color .15s, border-color .15s;
}
.lang-pick > summary::-webkit-details-marker{display:none}
.lang-pick > summary:hover{color:var(--v5-ink); border-color:var(--v5-ink)}
.lang-pick[open] > summary{color:var(--v5-ink); border-color:var(--v5-amber)}
.lang-pick .chev{transition:transform .15s ease}
.lang-pick[open] .chev{transform:rotate(180deg)}
.lang-menu{
  position:absolute; right:0; top:calc(100% + 8px); z-index:80; min-width:176px;
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--shadow-3); padding:6px; display:grid;
  animation:menuIn .18s cubic-bezier(.3,.9,.4,1) both;
}
.lang-menu a{
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  padding:9px 11px; border-radius:var(--r-xs);
  font-size:14px; color:var(--ink-2); text-decoration:none;
  transition:background .13s, color .13s;
}
.lang-menu a:hover{background:var(--accent-soft); color:var(--ink); text-decoration:none}
.lang-menu a[aria-current]{color:var(--v5-ink); font-weight:700}
/* The theme toggle is stood down here for the same reason it is on the home:
   v5 is light by decision, the scope beats [data-theme] on specificity, so a
   toggle on these pages moves the palette of nothing a visitor can see. */
.icon-btn#theme{display:none}

/* buttons: solid amber, no gradient, no shine, no lift */
.btn{border-radius:5px; font:600 16px/1 var(--sans); padding:10px 16px}

/* ---- the layer frame ----
   The same ring app.css puts on every button, declared again here because
   these two files are alternatives, not layers: 53 tool pages load app.css and
   75 content pages load page.css, and nothing loads both. The frame went into
   app.css first and /pricing, /tools, /help and /about kept plain buttons; the
   header CTA is in the shared shell, so the site had two different buttons in
   the same corner depending on which page you were on. An outline rather than
   a box-shadow for the same two reasons as there: it takes no space, and its
   gap is transparent, so it needs no help from whatever is behind it. */
.btn{outline:2px solid var(--frame-ring,var(--v5-line)); outline-offset:3px}
.btn:focus-visible{outline-color:var(--v5-ink); outline-width:3px}
.btn-primary{--frame-ring:var(--v5-amber-deep)}
.btn-dark{--frame-ring:var(--v5-teal-deep)}
.btn-primary{
  background:var(--v5-amber); border:1px solid var(--v5-amber); color:var(--v5-ink);
  box-shadow:none;
}
.btn.btn-primary:hover,
.btn.btn-primary:active{
  background:var(--v5-amber-hover); border-color:var(--v5-amber-deep);
  color:var(--v5-ink); box-shadow:none; transform:none; filter:none;
}
.btn-primary::after{display:none}
.btn-sm{padding:8px 14px; font-size:14px}
.icon-btn{
  background:var(--v5-surface); border:1px solid var(--v5-line-strong);
  color:var(--v5-ink-2); border-radius:5px;
}
.icon-btn:hover{
  background:var(--v5-sand); border-color:var(--v5-muted);
  color:var(--v5-ink); transform:none;
}
.icon-btn.print-btn{background:var(--v5-surface); color:var(--v5-ink-2)}

/* the tab strip: an underline, not a pill. The dark language's grey track put
   grey text on a grey pill, which is the one combination the v5 palette has
   no contrast for. */
.tc-tabs{
  display:flex; gap:8px; background:none; padding:0;
  border-radius:0; margin-top:16px; flex-wrap:wrap;
}
.tc-tab{
  border:none; border-radius:0; background:none;
  font:500 16px/1 var(--sans); color:var(--v5-ink);
  padding:12px 16px; text-decoration:none;
}
.tc-tab:hover{
  color:var(--v5-ink); background:none; text-decoration:none;
  box-shadow:inset 0 -4px 0 var(--v5-line-strong);
}
.tc-tab:active{transform:none}
.tc-tab.active, .tc-tab[aria-current="page"]{
  color:var(--v5-ink); font-weight:600; background:none;
  box-shadow:inset 0 -4px 0 var(--v5-amber);
}

/* the toolbar and the step cards */

/* :not(.toolbar-plain) is load-bearing. .toolbar-plain at line 965 zeroes the
   border, background and shadow for the three national spreadsheet pages that
   cut the Standard picker, and this block is 700 lines further down at the
   same specificity, so it silently won and the frameless variant has not
   existed since the v5 pass landed. Measured on /excel-to-facturae before the
   fix: padding really was 0, but the computed border was still
   "1px solid rgb(199,193,172)" over a painted surface, which drew a
   1096 by 40 pixel empty bar with one button at its far left and 837 pixels
   of nothing beside it. Same family as the .btn-primary:hover bug from the v5
   session: a later same-weight rule quietly beating a variant. */
.toolbar:not(.toolbar-plain){
  background:var(--v5-surface); border:1px solid var(--v5-line-strong);
  border-radius:8px; box-shadow:none;
}
.toolbar-label{
  font:600 12px/1 var(--sans); letter-spacing:.5px; text-transform:uppercase;
  color:var(--v5-muted);
}
.toolbar-file{color:var(--v5-ink-2)}
.step-card{
  background:var(--v5-surface); border:1px solid var(--v5-line);
  border-radius:8px; box-shadow:none;
}
.step-card:hover{transform:none; border-color:var(--v5-line-strong)}
.step-card b{font-family:var(--display); font-weight:500; font-size:16px; letter-spacing:-.01em}
.step-card span{color:var(--v5-ink-2)}

/* the form: the mock's card, its section headings and its controls */
.form-card{
  background:var(--v5-surface); border:1px solid var(--v5-line-strong);
  border-radius:10px; box-shadow:none; padding:24px 32px 32px;
}
/* the mock states section headings at 17px in the display face, not as an
   11px tracked-out mono label; the label voice belonged to the dark skin */
.form-card > h2{
  font-family:var(--display); font-weight:500; font-size:17px;
  letter-spacing:-.01em; text-transform:none; color:var(--v5-ink);
  margin-bottom:12px; padding-bottom:12px; border-bottom:1px solid var(--v5-line);
}
.form-card > h2 .opt{font-size:13px; font-weight:500; color:var(--v5-muted)}
.field label{
  font:600 12.5px/1.3 var(--sans); color:var(--v5-ink-2);
  letter-spacing:0; text-transform:none;
}
.field label small{font-weight:500; color:var(--v5-muted)}
.input, .field input, .field select, .field textarea{
  border:1px solid var(--v5-line-strong); border-radius:5px;
  background:var(--v5-surface); color:var(--v5-ink);
  font:500 14px/1.2 var(--sans); padding:9px 11px;
}
.input:hover, .field input:hover, .field select:hover{border-color:var(--v5-muted)}
.input:focus, .field input:focus, .field select:focus, .field textarea:focus{
  outline:2px solid var(--v5-amber); outline-offset:0; border-color:var(--v5-amber);
}
.hint{color:var(--v5-muted)}
.req{color:var(--v5-amber-deep)}
.net-val{
  border:1px dashed var(--v5-line-strong); border-radius:5px; background:var(--v5-bg);
  font-variant-numeric:tabular-nums; color:var(--v5-ink-2);
}
.icon-x{
  border:1px solid var(--v5-line-strong); background:var(--v5-surface);
  color:var(--v5-muted); border-radius:5px;
}
.icon-x:hover{color:var(--v5-ink); border-color:var(--v5-muted)}
.sums{font-variant-numeric:tabular-nums; color:var(--v5-ink-2)}
.sums .grand, .sums b{color:var(--v5-ink)}

/* status banner */
.notice{
  border:1px solid var(--v5-line); border-left:3px solid var(--v5-amber);
  background:var(--v5-surface); border-radius:5px; box-shadow:none;
  color:var(--v5-ink-2);
}
.notice b{color:var(--v5-ink)}

/* FAQ: plain lines, the plus/minus glyph the Help page already uses */
.faq{border-top:1px solid var(--v5-line)}
.faq details{border-bottom:1px solid var(--v5-line); background:none}
.faq summary{
  background:none; color:var(--v5-ink);
  font:600 16.5px/1.4 var(--sans);
}
.faq summary:hover{color:var(--v5-ink); text-decoration:underline; text-decoration-color:var(--v5-amber); text-underline-offset:3px; background:none}
.faq details[open]{background:none}
.faq details[open] summary{color:var(--v5-ink)}
.faq p{color:var(--v5-ink-2)}

/* the drop zone, the same object the reader wears, with the same frame:
   the home drop's dashed amber window on the white plate (owner
   2026-09-24, one dropzone look site-wide) */
.drop{
  border:1.5px dashed rgba(227,154,46,.75); border-radius:14px;
  background:var(--v5-surface);
}
/* The mint ring died on the reader side as "a second accent nobody chose"
   (app.css, the same rule); this island kept painting it under the amber
   border every time a file dragged over. One accent, both skins. */
.drop:hover, .drop.over, .drop.working{border-color:var(--v5-amber); background:var(--v5-sand); box-shadow:none}
/* the drag ring is part of the home drop's frame grammar: a set-off 2px
   ring while the file is over the zone, and no ring on the read state
   (the scan band is its affordance). app.css carries this on .drop.over;
   these pages load page.css alone, so the same numbers live here. */
.drop.over{outline:2px solid var(--v5-amber); outline-offset:4px}
.drop-title{
  font-family:var(--display); font-weight:500; font-size:24px;
  letter-spacing:-.01em; line-height:1.1; color:var(--v5-ink);
}
.drop-sub{color:var(--v5-muted); font-size:15px}
/* The hint line becomes the file line once a file is read: the same element,
   the same place, and ink rather than muted so the detection is not read as
   another instruction. */
.drop-sub.set{color:var(--v5-ink); font-weight:600}
.under-drop{color:var(--v5-ink-2); font-size:15px}
.drop-actions{
  display:flex; gap:18px; justify-content:center; align-items:center; flex-wrap:wrap;
}
/* The promise, said once. app.css carries this on the reader pages; these
   five pages load page.css and had no rule for it at all, so the sentence
   rendered as a plain paragraph under a 40px heading and read as a second
   lead. Values are app.css's, so it is one component in two skins. */
/* .page-intro .v5-note, not a bare .v5-note: ".page-intro p" is (0,1,1) and a
   bare class is (0,1,0), so the lead paragraph won the size and the colour
   and the promise rendered at 16px in full ink instead of 13.5px muted.
   Measured in Chrome, not reasoned: computed font-size came back 16px. */
.page-intro .v5-note{margin-top:14px; font-size:13.5px; color:var(--v5-muted)}
/* The drop arrow. The 5 hand-written convert/detect landing pages
   (convert-invoice + de/fr/it/pl twins) load page.css and not app.css,
   so without these rules the <svg> inside .dz-orb had no width and
   stretched to fill the full-width dropzone. */
.dz-orb{
  width:46px; height:46px; margin:0 auto 12px; padding:0;
  background:none; border:none; border-radius:0; box-shadow:none;
  color:var(--v5-amber-deep);
}
.dz-orb svg{width:46px; height:46px}

/* pills keep their status colours; the neutral one goes to paper */
.pill{
  border:1px solid var(--v5-line-strong); background:var(--v5-surface);
  color:var(--v5-ink-2); border-radius:20px; font:500 12.5px/1 var(--sans);
}
.pill.ok{color:var(--v5-teal); border-color:var(--v5-teal)}
.pill.bad{color:var(--bad); border-color:var(--bad)}

/* the footer, the mock's slim dark bar. Identical to the app.css rule on
   purpose: the same component, the same values, and the last pass shipped
   the markup to every page with the layout in one skin only. */
.site-foot{
  background:var(--v5-ink); border-top:1px solid var(--v5-ink);
  color:var(--v5-line); margin-top:72px; padding:32px 0;
}
.site-foot a{color:var(--v5-line)}
.site-foot a:hover{color:var(--v5-bg)}
.foot-slim .foot-brand{color:var(--v5-bg); font-family:var(--display); font-weight:600; font-size:15px}
.site-nav a[aria-current="page"]{color:var(--v5-ink); box-shadow:0 3px 0 var(--v5-amber)}
/* the command palette lives on the home; a dead button is worse than none */
.kbd-chip{display:none}
/* the slim footer's LAYOUT, restated here because page.css pages never load
   app.css. Its colours are in the v5 block above, one rule for every page:
   the Help page used to state its own lighter version here, so the same
   component was two different objects depending on the page. */
.foot-slim{display:flex; align-items:center; gap:24px; flex-wrap:wrap; font-size:14px}
.foot-slim .foot-brand{font-family:var(--display); font-weight:600; font-size:15px}
.foot-slim .foot-nav{margin-left:auto; display:flex; gap:16px; flex-wrap:wrap}
/* the badge row under the brand, identical to app.css on purpose: page.css
   pages never load app.css, and one component must stay one object
   everywhere. One 32px height for every badge, widths follow each ratio. */
.foot-left{display:flex; flex-direction:column; align-items:flex-start; gap:10px}
.foot-badges{display:flex; align-items:center; gap:14px; flex-wrap:wrap}
.foot-badge img{display:block; height:32px; width:auto}

.help-head{padding:48px 0 24px; text-align:center}
.help-head .v5-micro{
  display:block; margin-bottom:12px;
  font:600 12px/1 var(--sans); letter-spacing:.5px; text-transform:uppercase;
  color:var(--v5-muted);
}
.help-head h1{
  font-family:var(--display); font-weight:500;
  font-size:clamp(30px,3.6vw,44px); line-height:1.05; letter-spacing:-.02em;
  margin:0;
}
.help-lead{color:var(--v5-ink-2); font-size:17px; max-width:52ch; margin:12px auto 0}
.help-search{margin:24px auto 0; max-width:520px; position:relative}
.help-search svg{position:absolute; left:15px; top:50%; transform:translateY(-50%); color:var(--v5-muted)}
.help-search input{
  width:100%; border:1px solid var(--v5-line-strong); border-radius:8px;
  background:var(--v5-surface); color:var(--v5-ink);
  font:500 16px/1.2 var(--sans); padding:14px 76px 14px 46px;
}
.help-search input:focus{outline:2px solid var(--v5-amber); outline-offset:0; border-color:var(--v5-amber)}
.help-search .count{
  position:absolute; right:14px; top:50%; transform:translateY(-50%);
  font:600 12.5px/1 var(--sans); color:var(--v5-muted);
}

.help-faq{padding:24px 0 32px}
.help-group{margin-bottom:28px}
.help-group h2{
  font-family:var(--display); font-weight:500; font-size:20px; letter-spacing:-.01em;
  padding-bottom:8px; border-bottom:2px solid var(--v5-amber); display:inline-block;
  margin:0 0 8px;
}
/* centering the whole column, not the text */
.help-group{max-width:720px; margin:0 auto 28px}
.qa{border-bottom:1px solid var(--v5-line)}
.qa-q{
  width:100%; display:flex; justify-content:space-between; gap:16px; align-items:center;
  background:none; border:none; cursor:pointer; text-align:left;
  font:600 16.5px/1.4 var(--sans); color:var(--v5-ink); padding:15px 2px;
}
.qa-q:hover{color:var(--v5-ink); text-decoration:underline; text-decoration-color:var(--v5-amber); text-underline-offset:3px}
.acc{display:grid; grid-template-rows:0fr; transition:grid-template-rows .28s cubic-bezier(.2,.7,.3,1)}
.acc.open{grid-template-rows:1fr}
.acc > div{overflow:hidden}
.acc p{margin:0 0 16px; color:var(--v5-ink-2); font-size:15px; max-width:64ch}
.acc p:last-child{margin-bottom:16px}
.acc a{color:var(--v5-teal-deep); font-weight:600}
.help-empty{color:var(--v5-muted); font-size:15px; padding:24px 0; border-bottom:1px solid var(--v5-line); text-align:center}

.help-feedback{padding:0 0 72px}
.fb-card{
  background:var(--v5-surface); border:1px solid var(--v5-line-strong);
  border-radius:10px; padding:28px 32px; max-width:640px;
}
.fb-card h2{font-family:var(--display); font-weight:500; font-size:22px; letter-spacing:-.01em; margin:0 0 8px}
.fb-sub{color:var(--v5-muted); font-size:14.5px; margin:0 0 20px}
/* SCOPED TO .fb-card ON PURPOSE, 2026-09-08. These five rules dress the Help
   page's feedback form, and .frow is also the field-row class every writer
   form uses. Unscoped, ".frow input" (0,1,1) sat 150 lines BELOW
   ".field input" (0,1,1) in this file and won on order, so the help page's
   15px/11px control was painting every control on /create-xrechnung and its
   siblings. Measured before the fix: #fNumber came back 45px tall with
   padding 11px 12px, not the 9px 11px the v5 skin declares for it. */
.fb-card .frow{margin-bottom:14px}
.fb-card .frow label{display:block; font:600 13px/1.3 var(--sans); color:var(--v5-ink-2); margin-bottom:5px}
.fb-card .frow label small{font-weight:500; color:var(--v5-muted)}
.fb-card .frow input, .fb-card .frow select, .fb-card .frow textarea{
  width:100%; border:1px solid var(--v5-line-strong); border-radius:5px;
  background:var(--v5-surface); color:var(--v5-ink);
  font:500 15px/1.4 var(--sans); padding:11px 12px;
}
.fb-card .frow textarea{min-height:110px; resize:vertical}
.fb-card .frow input:focus, .fb-card .frow select:focus, .fb-card .frow textarea:focus{
  outline:2px solid var(--v5-amber); outline-offset:0; border-color:var(--v5-amber);
}
.fb-actions{display:flex; gap:12px; align-items:center; flex-wrap:wrap}
.fb-note{font-size:13px; color:var(--v5-muted)}
.fb-note a{color:var(--v5-teal-deep); font-weight:600}
.fb-done{
  display:flex; gap:14px; align-items:flex-start;
  background:var(--v5-sand); border:1px solid var(--v5-line-strong);
  border-radius:8px; padding:20px;
}
.fb-done[hidden]{display:none}
.fb-done svg{flex:0 0 auto; margin-top:2px}
.fb-done b{display:block; font-size:16px; margin-bottom:4px}
.fb-done p{margin:0; color:var(--v5-ink-2); font-size:14.5px}
.fb-done a{color:var(--v5-teal-deep); font-weight:600}

/* the expand glyph: plus, closing to a minus; the text itself stays straight
   left - centered question lines read as zigzag down the page */
.qa-q::after{
  content:"+"; flex:0 0 auto; color:var(--v5-amber-deep);
  font:400 20px/1 var(--sans);
}
.qa-q[aria-expanded="true"]::after{content:"−"}
.fb-card{margin:0 auto}

@media (max-width:640px){
  .fb-card{padding:20px 16px}
  .help-search input{padding-right:14px}
  .help-search .count{display:none}
}
@media (prefers-reduced-motion:reduce){
  .acc{transition:none}
  .qa-q .chev{transition:none}
}

/* -------------------------------------------------------------------------
   The editor (mock-v5-create.html): the form on the left, the invoice it
   produces on the right, updating as you type.

   Every value is the mock's: the 1.05/.95 split, the 24px gutter, the
   64/78/1fr/66/1fr line-meta track, the sheet's 24px padding and hairlines.
   ------------------------------------------------------------------------- */
.ed-grid{
  display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  gap:24px; align-items:start; margin-top:16px;
}
.ed-form{
  background:var(--v5-surface); border:1px solid var(--v5-line-strong);
  border-radius:10px; padding:24px 32px 32px; min-width:0;
  display:block; margin:0;
}

.form-topbar{
  display:flex; justify-content:space-between; align-items:center; gap:12px;
  flex-wrap:wrap; margin-bottom:16px;
}
.form-topbar-act{display:flex; gap:8px; flex-wrap:wrap}
.ed-micro{
  font:600 12px/1 var(--sans); letter-spacing:.5px; text-transform:uppercase;
  color:var(--v5-muted);
}
.guide-btn{
  display:inline-flex; align-items:center; gap:7px;
  border:1px solid var(--v5-line-strong); border-radius:5px;
  background:var(--v5-surface); color:var(--v5-ink); cursor:pointer;
  font:600 13.5px/1 var(--sans); padding:8px 12px;
}
.guide-btn:hover{border-color:var(--v5-amber); color:var(--v5-ink)}

/* a section per group of fields, divided by a hairline, not by a card each */
/* The divider goes BETWEEN sections, not under each one. It used to be a
   bottom border with ":last-of-type" cancelling the last, and that selector
   never matched: the sections are divs and the last div inside the form is
   #status, so every page shipped a hairline hanging under Clear form with
   nothing beneath it. */
.ed-section{padding:20px 0}
.ed-section:first-of-type{padding-top:0}
/* No hairlines between sections, owner 2026-09-17: the 40px of white space
   where two 20px paddings meet is the divider. The line-item entries keep
   their own internal separators below. */
.ed-form > .ed-section:last-of-type{padding-bottom:0}
/* Three sizes and three inks carry the whole form: 17px ink for the section,
   12px muted for the field label, 14px ink for what the person typed. The
   heading used to sit two points above a 12.5px label in the same ink-2,
   which is a difference you have to look for. */
.ed-section > h2{
  font-family:var(--display); font-weight:500; font-size:17px;
  letter-spacing:-.01em; text-transform:none; color:var(--v5-ink);
  margin:0 0 14px; padding:0; border:none;
}
.ed-section > h2 .opt{font-size:13px; font-weight:500; color:var(--v5-muted)}
.frow{display:grid; gap:10px; margin-bottom:10px}
.frow.g2{grid-template-columns:1fr 1fr}
.frow.g3{grid-template-columns:1fr 1fr 1fr}
/* FatturaPA and Facturae widen Postcode/City/Country to hold Province: four
   short address fields share the row where three sat before (WP25b). */
.frow.g4{grid-template-columns:1fr 1fr 1fr 1fr}
.frow:last-child{margin-bottom:0}
.ed-form .field{gap:0}
/* DIR3 is revealed by the buyer-type selector; the lead is a hint,
   not a field name. */
.dir3-lead{
  font:500 12.5px/1.4 var(--sans); letter-spacing:0; text-transform:none;
  color:var(--v5-muted);
}
.dir3-block[hidden]{display:none !important}
.ed-form .field label{
  display:block; font:600 12px/1.3 var(--sans); color:var(--v5-muted);
  margin-bottom:4px; letter-spacing:0; text-transform:none;
}
.ed-form .field label small{font-weight:500; color:var(--v5-muted)}
.ed-form .hint{margin-top:8px; font-size:13px; color:var(--v5-muted)}
/* The control box, stated here rather than left to .input at line 505.
   That rule carries min-height:45px, a floor from the pre-v5 skin that the
   v5 padding (9px 11px on a 14px face, so a 33px box) could not get under:
   a box cannot be shorter than its own floor, so every field on every writer
   form was 45px whatever the padding said. (0,2,1) beats (0,1,0), and the
   floor is restated at the height this face actually needs, because Chrome
   ignores line-height on a <select> and a select with no floor comes out
   four pixels shorter than the input beside it. */
.ed-form .field .input,
.ed-form .field input,
.ed-form .field select{
  min-height:34px; padding:7px 10px; border-radius:4px;
  font:500 14px/1.2 var(--sans);
}
.ed-form .field input[type="date"]{padding-block:6px}

/* THE LEDGER ENTRY, 2026-09-08.

   This was a two tier horizontal grid and it never fitted: seven controls
   need about 645px on one line and the form column is 497px, so the row was
   folded in two and the column headings ended up stacked above the first
   description rather than above their own cells. The owner called it: this is
   the INPUT surface, not the invoice. The invoice is what the file and the
   sheet on the right say, and both of those keep their columns. Here, one
   field per line, each under the last, which is what every form a person has
   ever filled in does.

   The entry is a ledger posting, not a card: no shadow, no radius, hairline
   rules, and a numeral in the left margin. That numeral is the same one the
   reading copy prints in its "#" column, so the thing you type and the thing
   you send are numbered the same way. It comes from a CSS counter, so
   striking entry 2 renumbers the rest with no JS.

   The fields are sized to what they hold, not stretched to the column: a
   quantity box 460px wide for the digit 4 is the tell of a form nobody
   looked at. Net is not a seventh field. It is worked out, so it closes the
   entry as the carried amount on its own rule, right margin, and it is the
   one figure in the entry with any weight. */
.form-wrap .lines{
  display:block; border:1px solid var(--v5-line-strong); border-radius:6px;
  background:var(--v5-surface); overflow:hidden; counter-reset:ledger;
}
.linerow{
  display:block; border:none; border-radius:0; background:none;
  padding:14px 16px 0; margin:0; counter-increment:ledger;
}
.ed-form .lines > .linerow + .linerow{border-top:1px solid var(--v5-line-strong)}

/* the margin numeral and the strike, on one line above the entry */
.l-head{display:flex; align-items:center; justify-content:space-between; gap:12px}
.l-num{
  font:600 12px/1 var(--sans); font-variant-numeric:tabular-nums;
  color:var(--v5-muted); letter-spacing:.04em;
}
.l-num::before{content:counter(ledger)}

.l-fields{
  /* The entry reads like the posting it is, owner 2026-09-17 "atur tata
     letak form agar proper": description as the full-width headline, the
     four posting figures (qty, unit, price, VAT %) on one row beneath it,
     the category select filling the last row. Fixed-width inputs become
     flex tracks so the row fills the column instead of stacking. */
  display:flex; flex-wrap:wrap; gap:11px; margin-top:8px;
  /* Bottom-align the boxes on each wrapped line: labels wrap at different
     widths ("VAT rate (AliquotaIVA)" takes two lines where "Reason for 0%
     VAT (Natura)" takes one), and a stretched box drops its input under its
     own label, so the inputs of one row started at different heights
     (owner 2026-09-28: "tidak sejajar"). Growing the labels upward instead
     keeps every input on one baseline. */
  align-items:end;
}
/* Three levers, not size alone: the description is the entry's headline, the
   figures are the posting, the labels are metadata. */
.linerow .field label{
  display:block; font:600 11px/1.3 var(--sans); letter-spacing:.06em;
  text-transform:uppercase; color:var(--v5-muted); margin-bottom:3px;
}
.linerow .field input, .linerow .field select{
  border:1px solid var(--v5-line-strong); border-radius:4px;
  background:var(--v5-surface); color:var(--v5-ink);
  font:500 14px/1.2 var(--sans); padding:7px 10px; min-height:34px; width:100%;
}
/* Description is not a headline: same weight/size as the posting fields. */
.linerow .field .desc{font-size:14px; font-weight:500}
.linerow .field input:hover, .linerow .field select:hover{
  border-color:var(--v5-muted);
}
.linerow .field input:focus, .linerow .field select:focus{
  outline:2px solid var(--v5-amber); outline-offset:0; border-color:var(--v5-amber);
}
/* Sized to the content, the way a fixed-length field should be: a quantity
   is two or three characters and a VAT percent is two. The wrapper carries
   the track size; the input fills it. */
.linerow .f-desc{flex:1 1 100%}
.linerow .f-qty{flex:0 1 100px}
.linerow .f-vat{flex:0 1 100px}
.linerow .f-unit{flex:1 1 140px; max-width:200px}
.linerow .f-price{flex:1 1 150px; max-width:220px}
.linerow .f-cat{flex:1 1 240px}
/* The legal reference of a 0% FatturaPA line: a sentence ("Esente art. 10,
   c. 1, n. 20, DPR 633/72"), so it takes the whole row under the Natura. */
.linerow .f-rif{flex:1 1 100%}
.linerow .f-price .input, .linerow .f-qty .input,
.linerow .f-vat .input{font-variant-numeric:tabular-nums}

/* the carried amount: the one figure in the entry with weight, on the rule
   that closes it */
.l-net{
  display:flex; align-items:baseline; justify-content:space-between; gap:16px;
  margin-top:12px; padding:0 0 14px;
}
.l-net > span{
  font:600 11px/1.3 var(--sans); letter-spacing:.06em;
  text-transform:uppercase; color:var(--v5-muted);
}
/* Three classes deep, and every one of them is load bearing. ".line .net-val"
   is the pre-v5 spreadsheet cell 1200 lines above at (0,2,0) with an
   11px/46px box on it, so a two-class rule here lost to it and the carried
   amount came back as a 46px block. Measured before the fix: computed
   min-height 46px, padding "11px 2px 11px 0px". */
.linerow .net-val{
  border:none; background:none; padding:0; min-height:0; display:inline;
  font:700 16px/1.2 var(--sans); font-variant-numeric:tabular-nums;
  color:var(--v5-ink); text-align:right; white-space:nowrap;
}
.linerow .net-val:empty::before{
  content:"0.00"; color:var(--v5-muted); font-weight:500;
}
/* THE LABELS STAY. ".line + .line label" at line 787 clips every label after
   the first row off screen: it is the pre-v5 header-row trick, where the
   column names were printed once at the top and each row after it repeated
   them invisibly for screen readers. There is no header row any more, each
   entry names its own fields, so from the second entry down that rule would
   have hidden every name on the page. It is scoped by a media query rather
   than deleted because /pdf-to-xrechnung still wears the old row. */
.linerow .field label{
  position:static; width:auto; height:auto; overflow:visible;
  clip:auto; clip-path:none; white-space:normal;
  /* ".line .field label{text-align:center}" is the other half of the same
     pre-v5 idea: centred because it sat over a column, and the description
     was excepted back to the left. Above a stacked field it centred every
     name over its own box, so QTY floated in the middle of a 100px input. */
  text-align:left;
}
/* Same family: the stacked-row rules for the two qualifier layouts still
   hang a border and 12px of padding on the row below 1020px, which under a
   ledger entry draws a second rule right beside the entry's own. */
@media (max-width:1020px){
  .linerow.line-natura, .linerow.line-vatcat{
    padding-bottom:0; border-bottom:none;
  }
}

.icon-x, .rm{
  appearance:none; border:1px solid var(--v5-line-strong); background:var(--v5-surface);
  color:var(--v5-muted); border-radius:5px; width:30px; height:38px; flex:0 0 30px;
  cursor:pointer; font:500 15px/1 var(--sans);
}
.icon-x:hover, .rm:hover{color:var(--v5-ink); border-color:var(--v5-muted)}
/* 44px of hit area on a 26px glyph, without a 44px box in the layout. */
.linerow .rm{
  position:relative; border:none; background:none; width:26px; height:26px;
  flex:0 0 26px; border-radius:4px; font-size:17px; line-height:1;
  color:var(--v5-muted); transition:color .16s, background .16s;
}
.linerow .rm::after{content:""; position:absolute; inset:-9px}
.linerow .rm:hover{background:var(--v5-sand); color:var(--bad)}
/* THE TOTALS, rebuilt 2026-09-08 to match the sheet on the right.

   .sums was a wrapping flex with justify-content:space-between, so the rows
   were laid out as loose objects rather than as a column of amounts: with two
   VAT rates on screen the browser put "Net total" and "VAT 7% on 25.00" side
   by side on one line and "VAT 19% on 1470.00" alone on the next, which is
   what "where is the net, where is the VAT" describes. Amounts read as a
   total only when they share one right edge, and the panel beside this one
   (.sheet .totals) had been doing exactly that all along. */
.ed-form .sums{
  /* Stated, not omitted: ".sums" 1300 lines above still carries the pre-v5
     rule and its 16px of padding, and dropping the line from this block only
     handed the old one back. */
  display:block; border-top:none; padding-top:0; margin-top:18px;
}
.ed-form .sums > *{
  margin-left:auto; width:min(320px,100%);
}
.ed-form .sum-row{
  display:flex; justify-content:space-between; align-items:baseline; gap:20px;
  padding:5px 0; font-size:13.5px; color:var(--v5-ink-2);
}
.ed-form .sum-row b{font-weight:600; color:var(--v5-ink)}
.ed-form .sum-row.grand{
  border-top:1px solid var(--v5-line-strong); margin-top:6px; padding-top:10px;
  font-size:15px; font-weight:600; color:var(--v5-ink);
}
.ed-form .sum-row.grand b{font-size:18px; font-weight:700}
/* The exemption reason belongs to the VAT row above it, so it keeps that
   column rather than spanning the form. */
.ed-form .reason-row{padding:6px 0 2px}
.ed-form .reason-row label{font-size:11.5px; color:var(--v5-muted)}
.ed-form .reason-row .reason-input{min-height:30px; padding:6px 9px; font-size:13px}

.linklike, .addline{
  background:none; border:none; padding:0; cursor:pointer;
  font:600 15px/1 var(--sans); color:var(--v5-ink);
  text-decoration:underline; text-underline-offset:3px;
}
.linklike:hover, .addline:hover{color:var(--v5-ink); text-decoration:underline; text-decoration-color:var(--v5-amber); text-underline-offset:3px}
.addline{margin-top:8px}

.ed-foot{
  display:flex; justify-content:space-between; align-items:center; gap:16px;
  flex-wrap:wrap; margin-top:16px;
}
.clearform{
  background:none; border:none; padding:0; cursor:pointer;
  font:500 14px/1 var(--sans); color:var(--v5-muted);
  text-decoration:underline; text-underline-offset:3px;
}
.clearform:hover{color:var(--v5-ink)}
/* On a phone the two form links measured 15px and 14px tall (A6-9 audit,
   /create-xrechnung at 390): under any tap-target floor and easy to miss
   with a thumb mid-form. Below 560 the padding carries the hit area instead
   of the bare text: 32px tall, finger-wide sides. The 8px of top padding on
   .addline replaces the gap its 8px margin made, so the optical rhythm of
   the form footer is unchanged; desktop keeps the bare links. */
@media (max-width:560px){
  .addline, .clearform{
    display:inline-block; min-height:32px; padding:8px 10px;
  }
  .addline{margin-top:0}
}

/* ---- the preview column -------------------------------------------------- */
.ed-preview{
  position:sticky; top:80px; display:flex; flex-direction:column; gap:12px;
  min-width:0;
}
.ed-badge{
  /* Lives ON the sheet now, owner 2026-09-17: pinned to the paper's top
     edge like a stamp, half over the border. The sheet's 24px padding
     keeps the pill clear of the INVOICE block it sits above. */
  position:absolute; top:-13px; right:14px; z-index:5;
  display:inline-flex; align-items:center; gap:8px;
  background:var(--v5-sand); border:1px solid var(--v5-line-strong);
  border-radius:999px; font:600 12.5px/1 var(--sans); padding:7px 13px;
  color:var(--v5-ink-2);
}
.ed-badge i{width:8px; height:8px; border-radius:50%; background:var(--v5-teal); flex:0 0 auto}
.ed-paper{min-width:0}
.ed-paper-empty{
  margin:0; padding:24px; min-height:320px;
  display:grid; place-items:center; align-content:center;
  border:1px solid var(--v5-line-strong); border-radius:8px;
  background:oklch(100% 0 0); color:var(--v5-muted); opacity:.72;
  font-size:14.5px; text-align:center;
}
.ed-paper-empty[hidden]{display:none}

/* ---- the sheet itself, the mock's values --------------------------------- */
.sheet{
  background:oklch(100% 0 0); color:var(--v5-ink);
  border:1px solid var(--v5-line-strong); border-radius:8px;
  padding:24px; font-size:13px;
}
.sheet .micro{
  font:600 10px/1 var(--sans); letter-spacing:.5px; text-transform:uppercase;
  color:var(--v5-muted); display:block; margin-bottom:3px;
}
.sheet-head{display:flex; justify-content:space-between; gap:16px; align-items:flex-start}
.sheet .seller b{font-size:15px; font-weight:700}
.sheet .seller div{color:var(--v5-muted); font-size:12px; line-height:1.5; margin-top:2px}
.sheet .doc-title{text-align:right}
.sheet .doc-title h3{
  font-family:var(--display); font-weight:500; font-size:22px;
  letter-spacing:-.01em; margin:0; color:var(--v5-ink);
}
.sheet .doc-title div{font-size:12px; color:var(--v5-muted); margin-top:2px}
.sheet .billto{margin-top:16px}
.sheet .billto b{font-size:13.5px; font-weight:700}
.sheet .billto div{font-size:12px; color:var(--v5-muted)}
.sheet hr{border:none; border-top:1px solid var(--v5-line); margin:16px 0}
.sheet table.lines{width:100%; border-collapse:collapse; font-size:13px; display:table}
.sheet table.lines th{
  font:600 10px/1 var(--sans); letter-spacing:.5px; text-transform:uppercase;
  color:var(--v5-muted); padding:0 8px 8px; text-align:left;
  border-bottom:1px solid var(--v5-line-strong); background:none;
}
.sheet table.lines td{
  padding:9px 8px; border-bottom:1px solid var(--v5-sand); vertical-align:top;
  color:var(--v5-ink);
}
/* The line number column, the same one the reading copy on the home page
   prints. Narrow, muted, right of nothing. */
.sheet table.lines th.n, .sheet table.lines td.n{
  width:1%; white-space:nowrap; text-align:right; padding-right:4px;
  color:var(--v5-muted); font-variant-numeric:tabular-nums;
}
.sheet table.lines th.c, .sheet table.lines td.c{text-align:center}
.sheet table.lines th.r, .sheet table.lines td.r{text-align:right; font-variant-numeric:tabular-nums}
.sheet td.desc{min-width:0; overflow-wrap:anywhere}
.sheet .totals{
  margin-left:auto; width:min(270px,100%); font-size:13px;
  font-variant-numeric:tabular-nums;
}
.sheet .totals div{display:flex; justify-content:space-between; padding:4px 0; color:var(--v5-ink-2)}
.sheet .totals .grand{
  border-top:1px solid var(--v5-line-strong); margin-top:5px; padding-top:9px;
  color:var(--v5-ink); font-weight:700; font-size:15px;
}
.sheet .pay{margin-top:16px; font-size:11.5px; color:var(--v5-muted)}
.sheet .sheet-foot{margin-top:8px; font-size:11px; color:var(--v5-muted)}

/* .ed-actions, not .export-bar: the inventory matches a class on a word
   boundary and a hyphen is one, so "export-bar" reads as wearing .bar,
   the reader's export row. AGENTS.md warns about this by name. */
/* THE DOWNLOAD ROW as real software buttons, round 2 2026-09-17. Owner: the
   framed tiles were too big; the research standard (Material 3: 40dp button,
   20dp leading icon; GitHub Primer: 32px button, 16px icon) is a compact
   horizontal button, icon inline with a single-line label. Round 3: the
   pair must read as one control - same black frame, same type weight.
   Round 4+6: the row FLOATS with the sticky preview column instead of
   lining up in flow, but OUTSIDE the sheet - hung just under the paper's
   bottom-right corner, never covering the invoice and never pushed around
   by scroll. Scoped to .ed-tile so the page's other .btn controls keep
   their grammar. */
.ed-actions{
  position:absolute; right:0; top:100%; margin-top:10px; z-index:5;
  display:flex; justify-content:flex-end;
}
.ed-downloads{
  display:flex; flex-wrap:wrap; align-items:center; gap:8px;
}
.ed-tile{
  display:inline-flex; flex-direction:row; align-items:center; justify-content:center;
  gap:8px; min-height:38px; padding:6px 13px; cursor:pointer;
  background:var(--v5-surface); border:1px solid var(--v5-ink); border-radius:8px;
  font-family:var(--sans); font-size:13px; font-weight:600; line-height:1;
  color:var(--v5-ink); white-space:nowrap;
  transition:background-color .12s, transform .12s;
}
.ed-tile svg{width:20px; height:20px; display:block; flex:0 0 auto}
.ed-tile:hover{background:var(--v5-sand)}
.ed-tile:active{transform:scale(.96)}
.ed-tile:focus-visible{
  outline:2px solid var(--v5-ink); outline-offset:2px;
}
@media (max-width:560px){
  .ed-downloads{gap:8px}
  .ed-tile{flex:1 1 auto; min-height:44px; font-size:12.5px}
}

/* the undo toast */
.toast{
  position:fixed; left:24px; bottom:24px; z-index:110;
  display:flex; align-items:center; gap:16px;
  background:var(--v5-ink); color:var(--v5-bg); border-radius:8px;
  padding:14px 18px; font-size:14px; box-shadow:0 10px 30px -10px rgba(35,32,26,.5);
}
.toast[hidden]{display:none}
.toast button{
  background:none; border:none; cursor:pointer; color:var(--v5-amber);
  font:700 14px/1 var(--sans); letter-spacing:.02em; padding:4px 2px;
}
.toast button:hover{text-decoration:underline}

/* the filling guide, a dialog instead of three step cards */
/* THE OVERLAY SCROLLS, not the card inside it, and the page behind is frozen
   while it is open (writer-live.js puts .guide-open on <html>).

   Before: the card was the scroller and the page behind was still live, so a
   wheel anywhere on the dimmed backdrop, which is most of the screen, scrolled
   the FORM while the dialog sat still. The dialog looked frozen because the
   only thing that moved was behind it. A flex overlay with margin:auto on the
   card centres it while it fits and scrolls the whole overlay once it does
   not, so the wheel works wherever the pointer is. */
.guide{
  position:fixed; inset:0; z-index:100; background:rgba(35,32,26,.4);
  display:flex; align-items:flex-start; justify-content:center;
  padding:16px; overflow-y:auto; overscroll-behavior:contain;
}
.guide[hidden]{display:none}
.guide-open, .guide-open body{overflow:hidden}
.guide-card{
  width:min(520px,100%); margin:auto;
  background:var(--v5-surface); border:1px solid var(--v5-line-strong);
  border-radius:10px; padding:32px;
}
.guide-card h2{font-size:22px; letter-spacing:-.01em; margin:0 0 16px}
.gstep{display:flex; gap:16px; padding:12px 0}
.gstep .n{
  flex:0 0 30px; height:30px; border-radius:50%; background:var(--v5-teal);
  color:var(--v5-surface); display:grid; place-items:center;
  font:800 14px/1 var(--sans);
}
.gstep b{display:block; font-size:14.5px; margin-bottom:2px}
.gstep p{margin:0; color:var(--v5-ink-2); font-size:13.5px}
.form-card > .btn-row{
  margin-top:20px; padding-top:18px; border-top:1px solid var(--v5-line); justify-content:center;
}
.form-card > .btn-row .btn{flex:0 0 auto; min-width:0}
article.form-card > h2{
  font-size:22px; letter-spacing:-.01em; margin-bottom:16px;
}
@media (max-width:700px){
  .form-card > .btn-row .btn{flex:0 0 auto}
}
.guide-close{margin-top:16px; width:100%}

@media (max-width:1020px){
  .ed-grid{grid-template-columns:1fr}
  /* relative, not static: the badge and the download row anchor to this
     column, and sticky in a single column would offset the whole sheet.
     top:0 must ride along: the base rule's top:80px is the DESKTOP sticky
     offset, and under position:relative that same 80px becomes a permanent
     downward displacement - the preview (download row riding with it) left
     its grid row and covered the Related heading on every create page.
     Measured 2026-09-19: row 2 sized 386px correctly, preview painted at
     +80px; grid bottom 3483, preview bottom 3563, Related top 3491. */
  .ed-preview{position:relative; top:0}
  /* No sticky column to anchor to, so the row returns to the flow under
     the paper - floating over the sheet corner is a wide-screen grammar. */
  .ed-actions{position:static; margin-top:10px; justify-content:flex-start}
}
/* Every field carries its own name above it now, so the placeholder is a
   second copy of the same word until something is typed over it. */
.linerow .field input::placeholder{color:transparent}
@media (max-width:700px){
  .frow.g2, .frow.g3, .frow.g4{grid-template-columns:1fr}
  /* The entry was already one field per line; a narrow screen only takes the
     width off the fixed boxes. */
  .linerow .f-unit .input, .linerow .f-cat .input,
  .linerow .f-price .input{width:100%}
  .ed-form{padding:16px}
}
@media (max-width:560px){
  /* The line number is a nicety, not data, and on a narrow sheet its column
     is what pushes the seven-column table past the page and gives the whole
     document a horizontal scrollbar. Measured: scrollWidth 488 against a
     486 client. */
  .sheet table.lines th.n, .sheet table.lines td.n{display:none}
  .toast{left:12px; right:12px; bottom:12px}
  .guide-card{padding:20px 16px}
  .sheet{padding:16px}
  .sheet-head{flex-direction:column; gap:12px}
  .sheet .doc-title{text-align:left}
}

/* The same reversible-pair line as app.css, on the two writer templates that
   own half of a pair. Left aligned here because this family's head is. */
/* .page-intro, not a bare .swap: this line is a <p> inside .page-intro, and
   ".page-intro p" at line 1551 is (0,1,1) against (0,1,0), so the bare class
   lost the size and the colour while keeping the layout. Measured, not
   guessed: computed font-size came back 16px and the colour was the lead
   paragraph's ink. Third time this file has done it in one session. */
.page-intro .swap{
  display:flex; align-items:center; gap:7px;
  margin:14px 0 0; font-size:13.5px; color:var(--v5-muted); max-width:none;
}
.page-intro .swap-ico{width:20px; height:10px; flex:0 0 auto; color:var(--v5-amber)}
.page-intro .swap a{color:inherit; text-decoration:underline; text-underline-offset:3px;
        text-decoration-color:var(--v5-line-strong)}
.page-intro .swap a:hover{color:var(--v5-ink); text-decoration-color:var(--v5-amber)}

/* --------------------------------------------------------- the gap panel --
   What a target wants and the file does not carry.

   The refusal used to arrive as one line with the reasons joined by
   semicolons, and the layout clipped it to the first reason and a half. This
   is the same refusal as a list, with the box that would satisfy each row
   sitting beside the row it answers, because most of these rules want a fact
   no parser can invent: XRechnung's BR-DE-6 wants the seller's telephone
   number and an Italian FatturaPA does not carry one.

   Rows are hairline separated rather than boxed. Every other surface on this
   page already has a border and a background, and a card inside a card is how
   the old toolbar came to read as a fourth step card. */
.gaps-wrap{
  margin-top:18px; padding:16px 18px 18px;
  border:1px solid var(--v5-line); border-radius:8px;
  background:var(--v5-bg);
}
.gap-lead{margin:0 0 12px; font-size:14.5px; color:var(--v5-ink-2)}
.gaps{list-style:none; margin:0; padding:0}
.gap-row{padding:12px 0; border-top:1px solid var(--v5-line)}
.gap-row:first-child{border-top:0; padding-top:0}
.gap-why{display:block; font-size:14.5px; color:var(--v5-ink); font-weight:600}
.gap-note{display:block; margin-top:3px; font-size:13.5px; color:var(--v5-muted)}
.gap-inputs{display:flex; gap:10px; flex-wrap:wrap; margin-top:9px}
.gap-field{display:flex; flex-direction:column; gap:4px; min-width:150px; flex:1 1 150px}
.gap-field > span{
  font-family:var(--mono); font-size:10.5px; text-transform:uppercase;
  letter-spacing:.1em; font-weight:500; color:var(--v5-muted);
}
.gap-field .input{width:100%}
.gaps-wrap .btn-primary{margin-top:16px}
@media (max-width:560px){
  .gap-field{flex-basis:100%}
  .gaps-wrap .btn-primary{width:100%}
}

/* ---- the target dialog ---------------------------------------------------

   The converter's flow is the home page's: drop a file, a dialog asks one
   question, one chip answers it. These are the answer dialog's recipes from
   app.css, restated here because writer pages load this sheet and never
   app.css; the numbers are the approved ones, not new ones. One addition the
   home dialog has no use for: the target chip is a two-line control, label
   over the note that says what the file would need.

   The template parks the scrim off <main> and converter-ui.js re-parents it
   to <body> at bind, for the measured reason recorded there: a
   backdrop-filter on any ancestor turns position:fixed into a trap measured
   against that ancestor, which is how the home dialog once stood 900x48
   inside a 1400x900 viewport. */
.dlg-scrim{position:fixed; inset:0; z-index:60; display:none; place-items:center;
  padding:24px; background:rgba(35,32,26,.58); overflow:auto;
  -webkit-backdrop-filter:blur(2px); backdrop-filter:blur(2px)}
body.has-target #targetDlg{display:grid}
/* The review needs the room, and this is the intake dialog's component in
   the intake dialog's situation: chips plus a sheet. The intake box is 760px
   "for Review first"; this one stayed 440px only because nobody moved it.
   Same number, same reason. */
#targetDlg .dlg-box{max-width:760px}
/* The sheet area, the intake dialog's .intake-review recipe: scrollable,
   framed, paper-ground. invoice-sheet.js, the home page's engine, paints
   into it the moment a file is read. */
.dlg-review{margin:14px 0 0; max-height:56vh; overflow:auto;
  border:1px solid var(--v5-line); border-radius:8px;
  background:var(--v5-bg); padding:14px}
.dlg-box{width:100%; max-width:440px; background:var(--v5-surface);
  border:1px solid var(--v5-line-strong); border-radius:12px; padding:22px;
  box-shadow:0 0 0 1px rgba(35,32,26,.05), 0 8px 20px -6px rgba(35,32,26,.18),
             0 28px 64px -16px rgba(35,32,26,.30);
  animation:dlgPop .2s cubic-bezier(.23,1,.32,1) both}
@keyframes dlgPop{from{opacity:0; transform:scale(.96) translateY(6px)} to{opacity:1; transform:none}}
.ans-verdict{border-left:3px solid var(--v5-teal); border-radius:0 7px 7px 0;
  background:var(--v5-sand); padding:11px 14px}
.ans-v1{font-weight:600; font-size:15px; color:var(--v5-ink); text-wrap:pretty;
  overflow-wrap:anywhere}
.ans-tick{color:var(--v5-teal); font-weight:700}
.ans-v2{color:var(--v5-muted); font-size:12.5px; margin-top:3px; line-height:1.45}
/* The same recipe as .ans-ask in app.css: the dialog asks a section-heading
   question, so it speaks in the section heading's voice. */
.ans-ask{font-family:var(--display); font-weight:500; font-size:21px; line-height:1.14;
  letter-spacing:-.02em; color:var(--v5-ink); margin:22px 0 4px; text-wrap:balance}
.ans-ask:focus,
.ans-ask:focus-visible{outline:none}
.ans-sub{color:var(--v5-muted); font-size:13.5px; margin:0 0 18px}
/* Two columns, not the home row's four: these labels are full format names
   ("Peppol BIS Billing 3.0 (UBL)"), and the note under one needs the width.
   Left-aligned, because a two-line chip reads from its label, not its center. */
.tgt-list{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px}
.tgt-chip{font-family:var(--sans); text-align:left; cursor:pointer;
  background:var(--v5-amber); border:1px solid var(--v5-amber-deep); border-radius:9px;
  padding:10px 12px; display:flex; flex-direction:column; gap:3px;
  transition:background-color .12s, transform .12s}
.tgt-chip:hover{background:var(--v5-amber-hover)}
.tgt-chip:active{transform:translateY(1px)}
.tgt-chip:focus-visible{outline:2px solid var(--v5-ink); outline-offset:3px}
.tgt-label{font-size:13px; font-weight:600; line-height:1.25; color:var(--v5-ink)}
.tgt-note{font-size:11.5px; line-height:1.3; color:var(--v5-ink-2)}
/* The intake dialog's two halves. The formats answer the question, so they
   take the same two-column grid the converter's dialog gives them; the row
   under is for the actions that only leave the question, and they wear the
   locked chip's sand, not amber, because amber is what "this is the answer"
   looks like here. One container, two children: flex-basis 100% gives each
   its own lines inside .ans-row. */
.ans-row .tgt-list{flex-basis:100%}
.ans-quiet{flex-basis:100%; display:flex; flex-wrap:wrap; gap:10px; margin-top:2px}
.ans-quiet .ans-chip{background:var(--v5-sand); border-color:var(--v5-line-strong);
  --frame-ring:var(--v5-line); color:var(--v5-ink-2)}
.ans-quiet .ans-chip:hover{background:var(--v5-line); color:var(--v5-ink)}
/* The page's own format is the pressed answer: ink inverse with the
   verdict's tick, first in reading order, unmistakable at a glance. */
.tgt-chip[aria-pressed="true"]{background:var(--v5-ink); border-color:var(--v5-ink)}
.tgt-chip[aria-pressed="true"]:hover{background:var(--v5-ink)}
.tgt-chip[aria-pressed="true"] .tgt-label{color:var(--v5-bg)}
.tgt-chip[aria-pressed="true"] .tgt-note{color:var(--v5-sand)}
.tgt-tick{color:var(--v5-amber); font-weight:700}
/* The OCR page's save dialog wears the home answer dialog's clothes (owner,
   2026-09-18: the first take kept amber chips and only resembled home; this
   one IS home's dialog). Marked .dlg-tiles on the box at open time, and every rule is
   prefixed #intakeDlg: app.css also loads on this page and its own
   #intakeDlg row and chip rules (1,1,0) would beat a bare class (0,2,0). The rules mirror app.css's
   #answer block rule for rule - this page loads page.css without app.css,
   and a class defined in one skin and not the other is how chrome goes
   missing. */
#intakeDlg .dlg-tiles .ans-verdict{
  border-left:0; border-radius:10px; background:var(--v5-sand);
  padding:12px 14px; margin-bottom:4px;
}
#intakeDlg .dlg-tiles .ans-grp{padding:14px 0}
#intakeDlg .dlg-tiles h3{
  font:600 12px/1 var(--sans); letter-spacing:.5px; text-transform:uppercase;
  color:var(--v5-muted); margin:0 0 7px;
}
/* #intakeActs itself carries .ans-row in the template, so the container is
   squared off to a plain stack and only the group's row becomes the grid -
   otherwise the footer link lands in the grid as a fourth-column item. */
#intakeDlg .dlg-tiles > .ans-row{display:block}
/* Two icons do not need a 760px intake box (app.css #intakeDlg .dlg-box).
   .dlg-tiles sits ON .dlg-box, so the selector must be .dlg-box.dlg-tiles,
   not a descendant chain. Review first widens via .review-open on #intakeDlg. */
#intakeDlg .dlg-box.dlg-tiles{max-width:420px}
#intakeDlg.review-open .dlg-box{max-width:760px}
#intakeDlg .intake-pdf{
  display:block; width:100%; height:70vh; margin-top:10px;
  border:1px solid var(--line-strong); border-radius:8px; background:#fff;
}

#intakeDlg .dlg-tiles .ans-over:has(#intakeClose){display:none}
#intakeDlg .dlg-tiles .ans-pair{
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  gap:10px; row-gap:10px; margin-top:8px;
}
/* Refusal: one primary action, centered under the warning. */
#intakeDlg .dlg-tiles .ans-pair-center{
  grid-template-columns:1fr;
  justify-items:center;
  margin-top:14px;
}
#intakeDlg .dlg-tiles .ans-pair-center .ans-chip{
  justify-self:center;
  max-width:240px;
  width:100%;
}
#intakeDlg .intake-warn{
  color:#B91C1C;
  font-family:var(--sans);
  font-weight:600;
  font-size:14px;
  line-height:1.35;
  letter-spacing:0;
  margin:10px 0 6px;
  text-align:center;
  text-wrap:balance;
}
#intakeDlg .intake-note{
  color:var(--v5-muted);
  text-align:center;
  margin:0 0 4px;
  font-size:13.5px;
  line-height:1.4;
  text-wrap:balance;
}
/* The findings line, both flows that fill it: the refusal's row, column and
   cell sentence, and the partial-success failNote under the chips. Muted
   with room to breathe, left-aligned because a findings list is read, not
   glanced at, and long cell text wraps inside the box instead of into its
   wall. */
#intakeDlg .ans-why{
  color:var(--v5-muted);
  font-size:13px;
  line-height:1.5;
  margin:12px 0 0;
  text-align:left;
  white-space:pre-wrap;
  overflow-wrap:anywhere;
  text-wrap:pretty;
}
#intakeDlg .dlg-tiles .ans-grp > .ans-row{
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:10px; row-gap:10px;
}
/* A four-choice ask (the ZUGFeRD rungs) sits 2x2; three columns would
   orphan the fourth tile. Opt-in from offerChoicesFirst's columns: 2. */
#intakeDlg .dlg-tiles .ans-grp > .ans-row.two{
  grid-template-columns:repeat(2,minmax(0,1fr));
}
#intakeDlg .dlg-tiles .ans-chip{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:7px; width:auto; min-height:80px; padding:12px 6px;
  background:none; border:0; border-radius:10px; box-shadow:none;
  outline:0; --frame-ring:transparent;
  color:var(--v5-ink-2);
  font-family:var(--sans); font-size:11px; font-weight:600; line-height:1.2;
  text-align:center; white-space:normal; cursor:pointer;
  transition:background-color .12s, transform .12s, color .12s;
}
#intakeDlg .dlg-tiles .ans-chip svg{width:30px; height:33px; display:block; flex:0 0 auto}
#intakeDlg .dlg-tiles .ans-chip span{display:block; max-width:100%}
#intakeDlg .dlg-tiles .ans-chip:hover{background:var(--v5-sand); color:var(--v5-ink)}
#intakeDlg .dlg-tiles .ans-chip:active{transform:scale(.96)}
#intakeDlg .dlg-tiles .ans-chip.ans-lead{color:var(--v5-ink); font-weight:700}
#intakeDlg .dlg-tiles .ans-chip:focus-visible{outline:2px solid var(--v5-amber); outline-offset:3px}
#intakeDlg .dlg-tiles .ans-chip .tgt-tick{color:var(--v5-amber)}
#intakeDlg .dlg-tiles .ans-chip .tgt-note{
  font:500 10.5px/1.3 var(--sans); color:var(--v5-muted);
}
#intakeDlg .dlg-tiles .ans-over{margin-top:16px; padding-top:12px}
@media (max-width:640px){
  #intakeDlg .dlg-tiles .ans-grp > .ans-row{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* The CONVERTER's target dialog (#targetDlg) also wears .dlg-tiles and the
   same ans-chip + tgt-note markup, but this page loads page.css WITHOUT
   app.css. The tile recipe above was written only under #intakeDlg, so every
   convert-invoice chip fell through to an unstyled button: the SVG drew at
   natural size (a huge XR monogram) and "XRechnung 3.0 (UBL)needs 3 fields…"
   ran together because neither span nor .tgt-note was display:block.

   THREE COLUMNS (owner, 2026-09-18: "jadi 3 baris saja agar menghindari
   space kosong"). Nine targets on the converter = exactly three rows, no
   orphan in a fifth row of a two-column grid. Same home-tile numbers as
   #answer in app.css, scoped to this box so quiet Excel chips stay untouched.
   min-height is fixed so a tile without a gap note does not leave a hole
   beside a neighbour that has one: every chip reserves icon + two label
   lines + one note line. */
#targetDlg .dlg-tiles .ans-verdict{
  border-left:0; border-radius:10px; background:var(--v5-sand);
  padding:12px 14px; margin-bottom:4px;
}
#targetDlg .dlg-tiles .ans-row{
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:8px; row-gap:8px;
}
#targetDlg .dlg-tiles .ans-chip{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:6px; width:auto; min-height:104px; padding:10px 6px;
  background:none; border:0; border-radius:10px; box-shadow:none;
  outline:0; --frame-ring:transparent;
  color:var(--v5-ink-2);
  font-family:var(--sans); font-size:11px; font-weight:600; line-height:1.25;
  text-align:center; white-space:normal; cursor:pointer;
  transition:background-color .12s, transform .12s, color .12s;
}
#targetDlg .dlg-tiles .ans-chip svg{
  width:28px; height:30px; display:block; flex:0 0 auto;
}
#targetDlg .dlg-tiles .ans-chip span{
  display:block; max-width:100%; overflow-wrap:anywhere;
}
/* Always present in the DOM after the fix (empty when the target converts
   cleanly), and always one line tall, so every tile in the row is the same
   height and the grid does not leave a ragged hole under the short ones. */
#targetDlg .dlg-tiles .ans-chip .tgt-note{
  display:block; max-width:100%; min-height:1.3em;
  font:500 10.5px/1.3 var(--sans); color:var(--v5-muted);
  margin-top:0;
}
#targetDlg .dlg-tiles .ans-chip:hover{
  background:var(--v5-sand); color:var(--v5-ink);
}
#targetDlg .dlg-tiles .ans-chip:active{transform:scale(.96)}
#targetDlg .dlg-tiles .ans-chip:focus-visible{
  outline:2px solid var(--v5-amber); outline-offset:3px;
}
/* Breakpoints follow the DIALOG, not a tablet viewport: the box is
   max-width 760px, so three columns stay readable until the viewport is
   narrower than the dialog plus scrim padding (~700px). A 900px cut put
   every desktop-sized preview into two columns and five ragged rows. */
@media (max-width:700px){
  #targetDlg .dlg-tiles .ans-row{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:520px){
  #targetDlg .dlg-tiles .ans-row{grid-template-columns:1fr}
  #targetDlg .dlg-tiles .ans-chip{min-height:88px}
}
.ans-over{display:flex; justify-content:flex-end; margin-top:20px; padding-top:14px;
  border-top:1px solid var(--v5-line)}
@media (max-width:520px){
  .dlg-scrim{padding:14px}
  .dlg-box{padding:20px; max-width:none}
  .tgt-list{grid-template-columns:1fr}
  .ans-ask{font-size:19px}
}
@media (prefers-reduced-motion:reduce){
  .dlg-box{animation:none}
  .tgt-chip{transition:none}
  .tgt-chip:hover{transform:none}
  .tgt-chip:active{transform:none}
}

/* ---- the converter's instrument panel -------------------------------------

   The dashed drop zone sat alone in the page column at its full 1096px: a
   frame around empty air under a 640px intro. The home page wraps the same
   control in the instrument panel, and this card is now the home drop's
   plate itself (owner 2026-09-25: one dropzone frame site-wide, card
   included): 20px radius, 16px rim, the premium float, the same numbers as
   the v5 block in app.css. No format cloud under it either - the dialog
   that answers a drop already carries every format that matters. */
.tool-stage{position:relative; max-width:900px; margin:0 auto; width:100%}
.tool-card{
  background:var(--v5-surface); border:1px solid var(--v5-line-strong);
  border-radius:20px;
  box-shadow:0 1px 2px rgba(23,33,43,.05), 0 24px 60px -12px rgba(23,33,43,.18);
  padding:16px;
}
/* The drop's own 24px top margin was spacing for a cardless column; inside
   the panel it would sit 56px from the top edge and 32px from every other
   side. The panel's padding is the spacing now. */
body[data-tool="convert-invoice"] .drop{margin-top:0}
body[data-tool="attach-xml-to-pdf"] .drop{margin-top:0}
/* One dropzone, one size. The home drop draws 48/24/32 with its hint capped
   at 54ch (app.css, the v5 block); the converter's drew 32/22 with the hint
   running edge to edge, so the same component was two sizes on the two
   doors of one job. The home numbers win; the Excel pages already draw them
   because they load app.css after this sheet. */
body[data-tool="convert-invoice"] .drop{padding:24px 24px 20px}
body[data-tool="convert-invoice"] .drop-sub{max-width:54ch; margin-left:auto; margin-right:auto}
/* The refusal list belongs to the card above it: the same 900px stage, not
   the 840px column the base notice keeps for narrow-form pages. */
body[data-tool="convert-invoice"] .notice{max-width:900px}

/* The closing line sat between two hairlines: this sheet's own border-top
   above it, and the related section's border-top below it. One sentence in a
   box drawn by two rules that each belonged to a neighbour. The related
   section keeps the separation job; the lines around the sentence go. The
   row rules INSIDE the related list stay - they are the component, and they
   are the same on every page that has one. The converter-scoped overrides
   that did this here first became the base rules on 2026-09-09. */

/* =========================================================================
   THE INVOICE SHEET, drawn by invoice-sheet.js.

   MIRRORS app.css. The reader pages and the writing tools now draw the
   invoice with one engine, so they wear one set of rules; page.css cannot
   import app.css because the two skins are isolated on purpose, so the values
   are restated here rather than borrowed. If you change one, change both --
   .gen/ui/test_components.mjs cites both.
   ========================================================================= */
.invoice-render{min-width:0}
.pdf-page{
  display:block; width:100%; height:auto;
  background:oklch(100% 0 0);
  border:1px solid var(--v5-line-strong); border-radius:6px;
}
.pdf-page + .pdf-page{margin-top:14px}
.ops-page{
  position:relative;
  display:flex; flex-direction:column; box-sizing:border-box;
  padding-bottom:var(--pad-card, 0px);
  font-family:Helvetica, Arial, sans-serif;
  color:#111;
}
.ops-page .ox{position:absolute; white-space:pre; line-height:1}
.ops-page .o-rule{position:absolute}
.ops-page .o-band{position:absolute}

/* ---- touch floors (Web Interface Guidelines audit, 2026-09-12) ----

   Hit areas under about 40px on a phone: the header icon buttons, the nav
   links, the client-picker chips and the primary button. One block at the
   end of the sheet, so the floors all live together and cannot drift. */
@media (max-width:960px){
  .icon-btn{min-width:40px; min-height:40px}
  .site-nav a{padding:10px 0}
}
@media (max-width:560px){
  .btn{padding:12px 18px}
  .chip{padding:10px 16px}
}

/* The about family's body: one centered column, the same width the legal
   and help pages read best at. Introduced when the about pages moved from
   the founder story to the product story (2026-09-12) and their old
   half-width card left the right half of the page empty. */
.prose{max-width:720px; margin:28px auto 0}
.prose h2{margin-top:34px}
.prose h2:first-child{margin-top:0}

/* The audit archive, opt-in and local: a small toolbar row above the status
   line and one dialog of fact rows. Names, numbers and hash prefixes only,
   because the promise on the page is that no file content is stored.
   The bar is secondary chrome: when the archive is off it must not read as
   another pair of export actions next to Download. */
.arch-bar{display:flex; gap:14px; align-items:center; margin:10px 0 0; flex-wrap:wrap}
/* inside the intake/target dialog the switch reads as one of the dialog's
   own controls, so it centres under the action chips (owner 2026-09-17) */
.dlg-box .arch-bar{justify-content:center; margin:16px 0 0}
/* The conversion strip inside the drop zone (owner 2026-09-17). The
   converter and validator pages load page.css WITHOUT app.css, so this
   rule must live here: without it the strip's 20px SVG stretches to the
   full card width, which is exactly the giant INV the owner caught. */
/* labels are absolutely centred under their icon so a wide label cannot
   widen its cell and pull the arrow off the icon-line midpoint (owner
   2026-09-17: "tanda panah kurang ke kanan"). */
.conv-strip{
  display:flex; align-items:flex-start; justify-content:center; gap:28px;
  margin:26px 0 2px; padding-bottom:26px;
}
.conv-strip .cv{
  position:relative; display:block; width:44px; height:48px;
  font:600 13px/1.2 var(--sans); color:var(--v5-ink);
}
.conv-strip .cv svg{width:44px; height:48px; display:block}
.conv-strip .cv b{
  position:absolute; top:52px; left:50%; transform:translateX(-50%);
  white-space:nowrap; font-weight:600;
}
.conv-strip .cva{width:48px; height:24px; margin-top:12px; color:var(--v5-ink); flex:0 0 auto}
/* the many-formats converter wears the home chip wall itself, so the wall's
   own styles are mirrored here: that page loads page.css without app.css */
.conv-strip.conv-multi{display:block}
.conv-strip.conv-multi .fmt-row{display:flex; flex-wrap:wrap; justify-content:center; gap:8px}
.conv-strip.conv-multi .fmt-row + .fmt-row{margin-top:8px}
.conv-strip.conv-multi span{
  display:inline-flex; align-items:center; gap:7px;
  font:500 12.5px/1 var(--sans); color:var(--v5-ink-2);
  border:1px solid var(--v5-line-strong); border-radius:6px;
  background:var(--v5-surface); padding:6px 12px 6px 7px;
}
.conv-strip.conv-multi span svg{width:20px; height:22px; display:block}
/* Audit archive controls: secondary by design. Quiet when off; only the
   on-state keeps weight so "this is recording" is visible. */
.arch-bar .btn{
  display:inline-flex; align-items:center; gap:6px;
  border:0; border-radius:0; background:none; color:var(--v5-muted);
  cursor:pointer; font:500 13px/1.2 var(--sans); padding:4px 0;
  text-decoration:underline; text-underline-offset:3px;
  text-decoration-color:var(--v5-line-strong);
}
.arch-bar .btn:hover{
  border:0; background:none; color:var(--v5-ink);
  text-decoration-color:var(--v5-amber);
}
.arch-toggle.on{
  color:var(--v5-ink); font-weight:600; text-decoration-color:var(--v5-amber);
}
.ans-chip.on{border-color:var(--v5-amber); color:var(--v5-amber)}
/* The dialog's own foot. .ans-over was built for the target dialog, where a
   single Close link is its only child, so it never needed a gap: given four
   buttons it rendered them joined edge to edge. Scoped to this dialog the
   row now breathes: exports left, the destructive Clear and Close pushed
   right as their own group, ten pixels between neighbours. */
#archDlg .ans-over{display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  justify-content:flex-start; margin-top:18px; padding-top:14px;
  border-top:1px solid var(--v5-line)}
#archDlg .ans-over .btn{
  display:inline-flex; align-items:center; gap:7px;
  border:1px solid var(--v5-line-strong); border-radius:5px;
  background:var(--v5-surface); color:var(--v5-ink); cursor:pointer;
  font:600 13.5px/1 var(--sans); padding:8px 12px;
}
#archDlg .ans-over .btn:hover{border-color:var(--v5-amber); color:var(--v5-ink)}
#archDlg .ans-over .btn:nth-child(3){margin-left:auto}
#archDlg:not([hidden]){display:grid}
#archDlg .dlg-box{max-width:640px}
.arch-note{color:var(--v5-muted); font-size:13px; line-height:1.5; margin:6px 0 14px}
.arch-list{max-height:min(52vh, 420px); overflow:auto; display:flex; flex-direction:column;
  border:1px solid var(--v5-line); border-radius:10px}
.arch-row{display:flex; gap:10px; align-items:baseline; padding:9px 12px;
  border-bottom:1px solid var(--v5-line); font-size:13px}
.arch-row:last-child{border-bottom:none}
.arch-main{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  color:var(--v5-ink); font-weight:600}
.arch-hash{color:var(--v5-muted); font-family:var(--sans); font-size:11px; letter-spacing:.03em}
.arch-ts{color:var(--v5-muted); font-size:12px; white-space:nowrap}
.arch-empty{padding:18px 14px; color:var(--v5-muted); font-size:13px; line-height:1.5}

/* Code blocks inside prose pages: long curl lines scroll inside the block
   instead of pushing the page wide. */
.prose pre{overflow-x:auto}

/* =========================================================================
   PAPER, and why this block is the LAST thing in the sheet.

   It used to sit in the middle, above the v5 island. @media print adds no
   specificity, so every v5 rule that named the same element at the same
   weight simply won by coming later: the sheet printed on the cream paper
   ground instead of white, the form inputs kept their 1px boxes, and the
   toolbars the block names came back. Nothing failed loudly; print just
   stopped being an override layer. Measured 2026-09-10 on /create-xrechnung
   with Chrome print emulation: body background rgb(253,251,244), input
   border-bottom 1px, with this block still in the file.

   Print is the last word about the document. Keep it last in the file, and
   append new sections ABOVE this line, never below it.
   ========================================================================= */

@media print{
  /* THE HEXES IN THIS BLOCK ARE NOT LAZINESS, and they are not tokens for a
     reason worth keeping. The print palette is pinned on :root, so it reaches
     an element by INHERITANCE, and inheritance stops at any component that
     declares the same token for its own subtree. page.css has exactly such a
     component: the .form-wrap island resolves the light sheet's tokens inside
     the writer form. Reading var(--ink) here therefore prints #0C1522 inside
     the form and #111 outside it, which a screenshot diff caught after the
     first pass of this refactor did exactly that. Ink on paper is black.
     State black. */
  html, body{background:#fff; color:#111}

  /* No animation on paper. The results list fades its children in with
     animation-fill-mode:both and a stagger, and `both` holds an element at its
     FROM keyframe whenever the animation is not running. Print does not run
     animations, so the last child in the list, which is the brand line, was
     held at opacity 0 and never reached the sheet. Nothing on paper moves, so
     nothing on paper needs an animation. */
  *, *::before, *::after{
    animation:none !important; transition:none !important; opacity:1;
  }

  .aura, .gridlines, .skip, .site-head, .site-foot,
  .page-head, .toolbar, .steps, .actions, .notice,
  section.block, .tc-tabs, dialog, .pw-dlg,
  .arch-bar, #archDlg, .dlg-scrim, .conv-strip{display:none !important}

  .wrap, .wrap.page{max-width:none; width:auto; margin:0; padding:0 4mm}

  /* The boxes come off, the values stay. width:auto stops a full-width field
     leaving half a line of white where a short value sits. */
  .input, input, select, textarea{
    border:0; background:transparent; color:#111; padding:0;
    font:inherit; width:auto; min-width:0; appearance:none;
  }
  .field{break-inside:avoid; page-break-inside:avoid}
  .field label{color:#555}

  /* Everything that instructs the person filling the form rather than stating
     what the invoice says: the required-field asterisk, the helper notes, and
     the date picker's own little calendar button, which a browser draws inside
     the input and which prints as a stray glyph next to the date. */
  .req, .note, .field .hint{display:none}
  input[type="date"]::-webkit-calendar-picker-indicator{display:none}

  .form-wrap, .card{
    background:#fff; border:1px solid var(--line); border-radius:0;
    box-shadow:none;
  }

  /* ---- the writing tools print the INVOICE, not the form ----------------

     Reported 2026-09-10 by the owner from a real print preview of
     /create-xrechnung: five sheets of paper carrying "Fill it in from top to
     bottom", "Fill with an example", every field label, every helper note and
     the boxes around the inputs. The document the visitor came to produce was
     on the other half of the screen and never reached the paper on its own
     terms.

     The block above was written when this page was a form on paper: it took
     the boxes off the inputs and let the fields print as text. That was a
     reasonable answer before the editor had a live sheet beside it. It is the
     wrong answer now, because the sheet IS the invoice, rendered from the same
     data the XML is written from, and a form is an input surface. Printing an
     input surface hands somebody a filled-in questionnaire and calls it an
     invoice.

     So the left half of the editor comes off the paper and the right half
     becomes the page. Only the sheet is the document; the badge that says
     "Checked as you type", the download buttons and the help line are all
     things you can do on the site, and none of them is something a printed
     invoice says. */
  .ed-grid{display:block; margin:0}
  .ed-form, .form-topbar, .ed-actions, .ed-badge, .ed-note,
  .toast, .guide{display:none}
  /* PARENT CONTRACT: the editor is form + paper; only paper is a document. */
  .ed-grid > *:not(.ed-preview){display:none !important}
  .ed-preview > *:not(.ed-paper){display:none !important}

  /* Sticky is a scroll behaviour and there is no scrolling on paper; left in
     place it makes Chrome reserve the 80px offset on the first sheet. */
  .ed-preview{position:static; display:block; min-width:0; gap:0}
  .ed-paper{min-width:0; width:auto}

  /* The sheet is already white paper with a hairline. On real paper the
     hairline is the edge of the page, so it comes off, and the padding with
     it -- @page already sets the margin. */
  .sheet{
    border:0; border-radius:0; padding:0; font-size:11pt; background:#fff;
    color:#111;
  }

  /* Nothing to print. The handler refuses before it reaches the dialog (see
     page-ui.js), so this only ever shows if a browser prints by menu or by
     Ctrl+P with an empty form: one honest line beats a blank sheet. */
  .ed-paper-empty{
    border:0; padding:0; min-height:0; text-align:left; color:#555;
    font-size:10pt;
  }
  table{width:100%; border-collapse:collapse}
  th, td{border-bottom:1px solid var(--line); color:#111}
  tr{break-inside:avoid; page-break-inside:avoid}
  a{color:inherit; text-decoration:none}

  /* One line, once, at the foot of the last sheet, hard right. In the document
     flow rather than position:fixed, and that is not a preference: Chrome does
     not paint a fixed element into a printed page at all, so the first version
     of this shipped a mark that never once reached paper. Proved by printing
     the page to PDF and searching it: the word Tallystick was not in the file. */
  .print-brand{
    display:block; margin:12px 0 0; text-align:right;
    /* One-off: the brand line on paper sits between --muted (#555 in
       print) and the rule, and reads as ink at #555. */
    font-size:8pt; color:#666; letter-spacing:.02em;
  }

  /* Paid takes the line off. Nothing else about the sheet changes, so what Pro
     removes is the advertising and not a feature of the document. */
  :root[data-pro="1"] .print-brand{display:none}

/* The sheet on paper. The engine repaints it at 210mm for print, so the
     page box is the document's own and the card carries no furniture: the
     border and the radius are screen chrome around a picture of a page, and
     on paper the page is the page. Matches app.css, where the reader pages
     reach the same state. */
/* The sheet fills the page and the mark sits at its foot. min-height in vh,
     not a 297mm page: in print vh is the PRINTABLE height, so this survives a
     dialog that adds its own margin instead of spilling a second, near empty
     sheet. The bottom inset is the footer's own offset out of the layout,
     about 20mm, so the line is at the foot of the paper without being on the
     edge of it. */
  /* Paper fills the page and the footer goes to its foot, same reasoning
     as app.css's print block (owner correction 2026-09-13 10:06): the
     engine's .ops-gap stretches, .ops-foot lands at the bottom edge, and
     --pad-paper keeps the layout's own paper inset. calc(100vh - 1px)
     because a block exactly one printable page tall can round onto a
     second sheet. The screen preview is content-sized and untouched. */
  .ops-page{min-height:calc(100vh - 1px); padding-bottom:var(--pad-paper, 16mm)}

  @page{margin:0}
  .ed-grid, .ed-preview, .ed-paper, .invoice-render{
    display:block; margin:0; padding:0; border:0; background:none;
  }
  .pdf-page{border:0; border-radius:0; box-shadow:none; background:#fff}

  /* The engine stamps the provenance and the mark in the sheet's own footer,
     at the foot of the A4 page. The page-level .print-brand paragraph is the
     same line a second time, and because the sheet now fills the page exactly
     it landed on a second sheet of paper with nothing else on it. app.css
     stands it down the same way for the reader pages. */
  body:has(.ops-page) .print-brand{display:none}

  /* The sheet carries its own 48pt paper margin in every op, so the 4mm
     gutter .wrap keeps on paper is a second inset the document never asked
     for: laid at 793.7px into the 764px box, the printed sheet sat 20.9mm
     from the left edge and 13.1mm from the right. app.css zeroes the same
     padding for its reader sheet (.sheet-mode .wrap); this is the writer
     twin, under the guard the rule above already uses: only when an ops
     sheet is actually on the page, so the .ed-paper-empty line, which has
     no margin of its own, keeps the gutter. */
  body:has(.ops-page) .wrap,
  body:has(.ops-page) .wrap.page{padding:0}

  /* ---- /pdf-to-xrechnung prints the invoice its form renders -------------

     The review is form + scan; neither is a document. The hidden .ed-print
     host carries the sheet liveSheet paints from the same form data the
     download writes, so the printed page is the invoice and never the
     input surface or the picture of the old one. Scoped by the page's own
     data-tool so the writer forms' contracts above stay untouched. */
  body[data-tool="pdf-to-xrechnung"] .hero,
  body[data-tool="pdf-to-xrechnung"] .notice-col,
  body[data-tool="pdf-to-xrechnung"] .pdfx-split,
  body[data-tool="pdf-to-xrechnung"] .toast{display:none !important}
  body[data-tool="pdf-to-xrechnung"] .ed-print{
    display:block !important; position:static; left:0; width:auto;
  }
  body[data-tool="pdf-to-xrechnung"] .ed-print > *:not(.ed-paper){display:none !important}

}
