/* site.css — the one hand-written stylesheet (spec §3, §9). Palette as CSS custom properties.
   Self-hosted WOFF2 fonts (Archivo / IBM Plex Sans / IBM Plex Mono). No Google Fonts CDN, ever
   (spec §3, §13 — DSGVO). */

/* Self-hosted fonts (spec §9, §13). Both faces are variable, so one file covers each weight range.
   Split latin / latin-ext by unicode-range as Google's own subsets are: German (ä ö ü ß) lives in
   latin, Turkish (ı ş ğ İ) in latin-ext, so a German visitor never downloads the Turkish glyphs
   and vice versa. Files are the SIL Open Font License releases, committed here — see
   wwwroot/fonts/README.md for provenance. */
@font-face {
    font-family: "Archivo"; font-weight: 600 700; 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: "Archivo"; font-weight: 600 700; font-display: swap;
    src: url("/fonts/archivo-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;
}
@font-face {
    font-family: "IBM Plex Sans"; font-weight: 400 500; font-display: swap;
    src: url("/fonts/ibm-plex-sans-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: "IBM Plex Sans"; font-weight: 400 500; font-display: swap;
    src: url("/fonts/ibm-plex-sans-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;
}
@font-face {
    font-family: "IBM Plex Mono"; font-weight: 500; font-display: swap;
    src: url("/fonts/ibm-plex-mono-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: "IBM Plex Mono"; font-weight: 500; font-display: swap;
    src: url("/fonts/ibm-plex-mono-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;
}

:root {
    /* The bench, not a page with a bench on it (design 2026-08-12, direction A). The housing is the
       ground; panels are milled plates set into it; one display is lit. Before this, the site was
       paper with a single dark band, which is why every page read as white boxes and grey rules.

       --ink stays a DARK SURFACE, not a text colour: the header, the footer, the readout well and
       the active chip are all cut from it. Type is set in --text. Those two were one token before
       and could not both survive the inversion. */
    --ink:     #15181C;  /* housing — the ground, and any surface cut deeper than a plate */
    --plate:   #20262D;  /* a milled plate set into the housing */
    --edge:    #39434E;  /* the lit top edge of a plate, 1px */
    --well:    #07090A;  /* the display well and input troughs — deeper than everything */
    --text:    #E4E9ED;  /* primary type on the housing */
    --steel:   #7E8A96;  /* engraved label, secondary type */
    --rail:    #2E3740;  /* hairline on dark */
    --paper:   #15181C;  /* page ground — now the housing itself */
    --white:   #20262D;  /* "raised surface" — now a plate. Name kept; ~40 rules speak it. */
    --readout: #FFB020;  /* the display amber — CTA + estimate ONLY */
    --ok:      #4FA77B;  /* paid / accepted — 5.19:1 on the lightest dark plate */
    --warn:    #E8754D;  /* disputed / do-not-buy — 5.15:1 on the lightest dark plate */

    /* Per-site accent, set on .ia-shell from Site.ThemeAccentColor — four properties, not one.
       PublicLayout derives the other three through SiteAccent, because whether an accent carries
       black text or white is arithmetic the stylesheet cannot do. These defaults are the German
       amber's own answers, so a page rendered without a site (the error page) still looks right.
         --ia-accent-fill     the accent, nudged until its own label clears 4.5:1
         --ia-accent-on-fill  that label's colour
         --ia-accent-bright   the accent, lightened until it clears 4.5:1 on --ink */
    --ia-accent: var(--readout);
    --ia-accent-fill: var(--readout);
    --ia-accent-on-fill: #101418;
    --ia-accent-bright: var(--readout);

    --font-display: "Archivo", system-ui, sans-serif;
    --font-body: "IBM Plex Sans", system-ui, sans-serif;
    --font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", monospace;

    /* The brand mark (design 2026-08-12) — the nozzle tip from InjectorDrawing, as a mask source
       for .ia-brand::before below, where the comment explains the mask-over-<img> choice. */
    --ia-mark: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 48"><path d="M6 0h16v28l-8 20-8-20z" fill="black"/></svg>');

    /* The sticky site header's own height above 900px (design 2026-08-12, code review). Two readers:
       .ia-header-inner's min-height, which sets it, and .ia-pricelist th's sticky top, which has to
       clear it or render behind it. One token instead of two literals, so they cannot drift apart —
       see the review comment on .ia-pricelist th below for what this does not fix. */
    --header-height: 72px;
}

* { box-sizing: border-box; }
.ia-visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }

body {
    margin: 0;
    font-family: var(--font-body);
    color: var(--text);
    background: var(--paper);
    line-height: 1.5;
}

h1, h2, h3 { font-family: var(--font-display); font-weight: 700; letter-spacing: -0.01em; }

.ia-container { max-width: 1120px; margin-inline: auto; padding-inline: 1rem; }

/* Header — the bench housing (§9). Grayscale on ink by design: --readout belongs to the price and
   the primary CTA and nowhere else, and an amber rule up here is exactly the generic look §9 warns
   against. The weight comes from the dark bar and the type, not from colour. */
.ia-header { background: var(--ink); position: sticky; top: 0; z-index: 30; }
.ia-header-inner { display: flex; align-items: center; gap: 2rem; min-height: var(--header-height); }
.ia-brand { display: inline-flex; align-items: center; gap: .6rem; font-family: var(--font-display);
    font-weight: 700; font-size: 1.2rem; letter-spacing: -.02em; color: var(--text); text-decoration: none; }
/* The mark is the nozzle tip from InjectorDrawing, cut down to a glyph — the same shape the site
   draws everywhere else, at the size a header can carry. Inline SVG in a mask rather than an <img>,
   so it follows currentColor and needs no asset. It renders from BrandName, which is a Site column
   the owner can still change (the three businesses are not named yet), so nothing here assumes a
   name or a length. */
.ia-brand::before {
    content: ""; width: 14px; height: 24px; flex: none; background: currentColor;
    mask: var(--ia-mark) center / contain no-repeat;
    -webkit-mask: var(--ia-mark) center / contain no-repeat;
}
.ia-nav { display: flex; gap: 1.5rem; flex-wrap: wrap; }
.ia-nav a { position: relative; color: var(--steel); text-decoration: none; font-size: .8rem; font-weight: 500;
    text-transform: uppercase; letter-spacing: .07em; padding-block: .25rem; }
.ia-nav a:hover { color: var(--text); }
.ia-nav a[aria-current="page"] { color: var(--text); }
.ia-nav a[aria-current="page"]::after { content: ""; position: absolute; left: 0; right: 0; bottom: -.45rem;
    height: 2px; background: var(--ia-accent-bright); }
.ia-mobile-nav { display: none; }
.ia-header-actions { margin-left: auto; display: flex; align-items: center; gap: 1.5rem; }
/* The phone number is the trust signal in this trade — a workshop calls before it ships a crate. */
.ia-phone { color: var(--text); text-decoration: none; font-family: var(--font-mono);
    font-variant-numeric: tabular-nums; font-size: .95rem; white-space: nowrap; }
.ia-phone:hover { text-decoration: underline; text-underline-offset: 3px; }
.ia-lang { color: var(--steel); text-decoration: none; font-weight: 500; font-size: .9rem; }
.ia-lang:hover { color: var(--text); }

/* Main is a three-column grid: gutter, container, gutter. Ordinary content sits in the middle
   column exactly as it did when main carried .ia-container; a .ia-band spans all three and repeats
   the grid inside itself, so a full-width coloured band costs one class instead of a rewrite. */
.ia-main { display: grid; grid-template-columns: 1fr min(1120px, 100% - 2rem) 1fr; min-height: 60vh; }
.ia-main > * { grid-column: 2; }
.ia-main > .ia-band { grid-column: 1 / -1; display: grid; grid-template-columns: inherit; }
.ia-main > .ia-band > * { grid-column: 2; }

/* The bench housing (§9). One dark band at the top holds the headline and the form, so the page
   opens on something built rather than on grey paper — and the readout, the one place §9 spends any
   boldness, finally sits against the dark the whole design direction is named after. */
.ia-band { padding-block: 3.5rem; }
/* The band was a dark rectangle on paper. The page is now the housing itself, so this
   paints nothing and exists only so the eleven pages that name it keep their spacing. */
.ia-band--ink { background: transparent; color: var(--text); }
.ia-band--ink h1 { color: var(--text); }
.ia-band--ink .ia-lead { color: var(--steel); }
.ia-band--white { background: var(--white); }
.ia-main > .ia-band + .ia-band { padding-top: 3.5rem; }

.ia-hero { padding-bottom: 2rem; }
.ia-hero h1 { font-size: clamp(2rem, 5vw, 3.1rem); line-height: 1.08; margin: 0 0 1rem; max-width: 20ch; }
.ia-lead { color: var(--steel); font-size: 1.15rem; max-width: 52ch; }
.ia-hero-actions { display: flex; align-items: center; flex-wrap: wrap; gap: .75rem 1rem; margin-top: 1.4rem; }
.ia-hero-actions > span { color: var(--steel); font-size: .85rem; }
.ia-hero-cta { min-height: 48px; }
.ia-offer-form-anchor { scroll-margin-top: calc(var(--header-height) + 1rem); }
.ia-trust-strip { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem;
    list-style: none; margin: 1.5rem 0 0; padding: 0; max-width: 860px; }
.ia-trust-strip li { position: relative; padding-left: 1.5rem; }
.ia-trust-strip li::before { content: "✓"; position: absolute; left: 0; top: .05rem;
    color: var(--ia-accent-bright); font-weight: 700; }
.ia-trust-strip strong, .ia-trust-strip span { display: block; }
.ia-trust-strip strong { font-size: .9rem; color: var(--text); }
.ia-trust-strip span { margin-top: .2rem; color: var(--steel); font-size: .82rem; line-height: 1.4; }

/* Inside the dark band the hero and the form are one unit, not two stacked blocks. */
.ia-band--ink .ia-hero { padding-bottom: 2.5rem; }
/* On a page whose band holds nothing but the heading, that padding stacks on the band's own and
   leaves a hole under the title. The band's padding is enough on its own. */
.ia-band--ink .ia-hero:last-child { padding-bottom: 0; }
/* The band paints its text white, and that inherits straight into the white form card — the step
   heading was white on white and simply disappeared. The card sets its own colour back. */
.ia-band--ink .ia-panel { border-color: transparent; box-shadow: 0 12px 32px rgba(0,0,0,.35); }
.ia-band--ink .ia-panel h2 { color: var(--text); }
.ia-band--ink .ia-readout-panel { background: rgba(255,255,255,.04); border-color: rgba(255,255,255,.16); }
.ia-band--ink .ia-readout-title { color: var(--steel); }
.ia-band--ink .ia-readout-lines { border-top-color: rgba(255,255,255,.16); }
.ia-band--ink .ia-readout-lines li { color: var(--steel); }
/* --steel is a mid grey chosen against paper; on ink it is barely legible. Everything the band
   paints over gets the light equivalent. */
.ia-band--ink .ia-hint,
.ia-band--ink .ia-content-text { color: var(--steel); }
.ia-band--ink .ia-readout { margin-top: 1.5rem; }
.ia-band--ink strong { color: var(--text); }

/* Homepage storefront. The first viewport is a product promise and a real workshop, while the
   longer four-step offer flow begins in its own anchored section below the catalogue preview. */
.ia-home-hero-band { padding-block: clamp(2.5rem, 6vw, 5.25rem); overflow: hidden; }
.ia-home-hero { display: grid; grid-template-columns: minmax(0, .92fr) minmax(400px, 1.08fr);
    align-items: center; gap: clamp(2rem, 6vw, 5rem); }
.ia-home-hero h1 { max-width: 16ch; margin: 0 0 1rem; font-size: clamp(2.35rem, 5vw, 4rem);
    line-height: .99; letter-spacing: -.045em; }
.ia-home-hero .ia-lead { max-width: 49ch; font-size: clamp(1rem, 1.7vw, 1.18rem); }
.ia-home-hero-copy > .ia-eyebrow { color: var(--ia-accent-bright); }
.ia-quick-quote { max-width: 620px; margin-top: 1.75rem; padding: 1rem;
    background: color-mix(in srgb, var(--plate) 86%, transparent); border: 1px solid var(--edge);
    border-radius: 10px; box-shadow: 0 18px 38px -28px #000; }
.ia-quick-quote label { display: block; margin-bottom: .55rem; color: var(--text); font-weight: 650; }
.ia-quick-quote-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .65rem; }
.ia-quick-quote input { width: 100%; min-width: 0; min-height: 50px; padding: .75rem .9rem;
    color: var(--text); background: var(--well); border: 1px solid var(--rail); border-radius: 6px;
    font-family: var(--font-mono); font-size: 1rem; text-transform: uppercase; }
.ia-quick-quote input:focus { border-color: var(--ia-accent-bright); outline: 3px solid color-mix(in srgb, var(--ia-accent) 22%, transparent); }
.ia-quick-quote .btn { min-height: 50px; white-space: nowrap; }
.ia-quick-quote small { display: block; margin-top: .55rem; color: var(--steel); }
.ia-home-hero > .ia-trust-strip { grid-column: 1 / -1; grid-template-columns: repeat(3, minmax(0, 1fr));
    max-width: none; gap: 0; margin: 0; padding: 1rem 1.15rem; background: var(--plate);
    border: 1px solid var(--edge); border-radius: 8px; }
.ia-home-hero > .ia-trust-strip li { padding: 0 1.25rem 0 2.1rem; }
.ia-home-hero > .ia-trust-strip li + li { border-left: 1px solid var(--rail); }
.ia-home-hero > .ia-trust-strip li::before { left: .8rem; }
.ia-home-hero-media { position: relative; min-width: 0; }
.ia-photo.ia-home-hero-photo { margin: 0; }
.ia-home-hero-photo img { aspect-ratio: 4 / 3; object-fit: cover; border-radius: 8px;
    border: 1px solid var(--edge); box-shadow: 0 24px 55px -38px #000; }
.ia-home-hero-media::before { content: ""; position: absolute; z-index: -1; inset: 1.25rem -1.25rem -1.25rem 1.25rem;
    border: 1px solid color-mix(in srgb, var(--ia-accent-bright) 45%, var(--edge)); border-radius: 8px; }
.ia-section-lead { max-width: 62ch; margin: -.65rem 0 0; color: var(--steel); line-height: 1.65; }
.ia-section-heading-row { display: flex; justify-content: space-between; align-items: end; gap: 1.5rem; margin-bottom: 1.5rem; }
.ia-section-heading-row h2 { margin-bottom: 1rem; }
.ia-section-heading-row .ia-btn-secondary { flex: none; }
.ia-home-band { margin-top: 2.75rem; padding-block: clamp(2.75rem, 6vw, 4.5rem); border-block: 1px solid var(--edge); }
.ia-home-band--plate { background: color-mix(in srgb, var(--plate) 76%, var(--well)); }
.ia-home-band--offer { background: var(--well); scroll-margin-top: calc(var(--header-height) + 1rem); }
.ia-category-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 320px)); gap: .75rem; }
.ia-category-card { min-width: 0; color: var(--text); background: var(--plate); border: 1px solid var(--edge);
    border-radius: 10px; overflow: hidden; text-decoration: none; transition: border-color .18s, transform .18s; }
.ia-category-card:hover { color: var(--text); border-color: var(--ia-accent-bright); transform: translateY(-3px); }
.ia-category-card-media { position: relative; display: grid; min-height: 130px; aspect-ratio: 2 / 1; place-items: center;
    background: var(--well); overflow: hidden; }
.ia-category-card-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain;
    padding: .75rem; background: var(--well); transition: transform .25s; }
.ia-category-card:hover img { transform: scale(1.035); }
.ia-category-card:only-child { width: min(100%, 560px); display: grid; grid-template-columns: minmax(150px, 42%) 1fr; }
.ia-category-card:only-child .ia-category-card-media { min-height: 138px; aspect-ratio: auto; }
.ia-category-card-placeholder { color: var(--rail); font-family: var(--font-mono); font-size: 3rem; letter-spacing: .08em; }
.ia-category-card-body { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .25rem 1rem;
    align-items: center; padding: 1rem 1.1rem; }
.ia-category-card-body strong { overflow-wrap: anywhere; }
.ia-category-card-body small { grid-column: 1; color: var(--steel); }
.ia-category-card-body > span { grid-column: 2; grid-row: 1 / span 2; color: var(--ia-accent-bright); font-size: 1.35rem; }
.ia-home-products-heading { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    margin: 2.75rem 0 1rem; padding-top: 2rem; border-top: 1px solid var(--edge); }
.ia-home-products-heading h3 { margin: 0; font-size: 1.15rem; }
.ia-home-products-heading a { color: var(--ia-accent-bright); font-size: .86rem; }
.ia-home-products { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .8rem; }
.ia-home-product { min-width: 0; color: var(--text); background: var(--well); border: 1px solid var(--edge);
    border-radius: 7px; overflow: hidden; text-decoration: none; transition: border-color .18s, transform .18s;
    display: grid; grid-template-columns: 128px minmax(0, 1fr); min-height: 118px; }
.ia-home-product:hover { color: var(--text); border-color: var(--ia-accent-bright); transform: translateY(-2px); }
.ia-home-product--text { grid-template-columns: 1fr; }
.ia-home-product-media { display: grid; place-items: center; overflow: hidden; background: #111518; border-right: 1px solid var(--rail); }
.ia-home-product-media img { width: 100%; height: 100%; padding: .55rem; object-fit: contain; }
.ia-home-product-copy { display: grid; align-content: center; padding: .85rem 1rem; }
.ia-home-product-copy small { color: var(--steel); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ia-home-product-copy strong { margin-top: .3rem; font-family: var(--font-mono); overflow-wrap: anywhere; }
.ia-home-product-copy b { margin-top: .6rem; color: var(--ia-accent-bright); font-family: var(--font-mono); font-size: 1rem; }
.ia-home-offer { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.5rem; align-items: start; }
.ia-home-offer-intro { max-width: 720px; margin-bottom: 0; }
.ia-home-offer-intro h2 { margin-bottom: 1rem; font-size: clamp(1.6rem, 3vw, 2.4rem); }
.ia-home-offer .ia-offer-form-anchor { min-width: 0; padding: .5rem; border: 1px solid var(--edge);
    border-radius: 10px; background: var(--plate); }
.ia-home-proof { display: grid; grid-template-columns: minmax(0, .95fr) minmax(0, 1.05fr);
    gap: clamp(2rem, 6vw, 5rem); align-items: center; }
.ia-photo.ia-home-proof-photo { margin: 0; }
.ia-home-proof-photo img { aspect-ratio: 4 / 3; object-fit: cover; border-radius: 10px; border: 1px solid var(--edge); }
.ia-home-proof-copy h2 { max-width: 19ch; margin-bottom: 1rem; font-size: clamp(1.6rem, 3.2vw, 2.5rem); }
.ia-home-proof-list { margin: 1.75rem 0 0; }
.ia-home-proof-list > div { display: grid; grid-template-columns: minmax(8rem, .7fr) minmax(0, 1.3fr);
    gap: 1rem; padding: .85rem 0; border-top: 1px solid var(--rail); }
.ia-home-proof-list dt { color: var(--steel); font-size: .82rem; }
.ia-home-proof-list dd { margin: 0; color: var(--text); overflow-wrap: anywhere; }
.ia-home-proof-list a { color: var(--text); }
.ia-home-faq { max-width: 900px; }
.ia-faq-list { border-top: 1px solid var(--rail); }
.ia-faq-list details { border-bottom: 1px solid var(--rail); }
.ia-faq-list summary { position: relative; padding: 1.15rem 2.5rem 1.15rem 0; cursor: pointer;
    color: var(--text); font-weight: 650; list-style: none; }
.ia-faq-list summary::-webkit-details-marker { display: none; }
.ia-faq-list summary::after { content: "+"; position: absolute; right: .25rem; top: .9rem;
    color: var(--ia-accent-bright); font-family: var(--font-mono); font-size: 1.4rem; }
.ia-faq-list details[open] summary::after { content: "−"; }
.ia-faq-list p { max-width: 68ch; margin: -.25rem 0 1.25rem; color: var(--steel); line-height: 1.65; }
.ia-home-final-cta { display: flex; justify-content: space-between; align-items: center; gap: 2rem;
    margin: 3rem 0; padding: clamp(1.5rem, 4vw, 2.75rem); background: var(--plate);
    border: 1px solid var(--edge); border-left: 4px solid var(--ia-accent-bright); border-radius: 10px; }
.ia-home-final-cta h2 { margin: 0 0 .5rem; font-size: clamp(1.5rem, 3vw, 2.25rem); }
.ia-home-final-cta p:not(.ia-eyebrow) { max-width: 58ch; margin: 0; color: var(--steel); }
.ia-home-final-actions { display: flex; flex: none; flex-wrap: wrap; gap: .75rem; }

@media (max-width: 860px) {
    .ia-home-hero { grid-template-columns: 1fr; }
    .ia-home-hero-copy { order: 1; }
    .ia-home-hero-media { order: 2; max-width: 680px; }
    .ia-home-hero-photo img { aspect-ratio: 16 / 9; }
    .ia-home-proof { grid-template-columns: 1fr; }
    .ia-home-proof-photo img { max-height: 430px; }
    .ia-home-final-cta { align-items: flex-start; flex-direction: column; }
}

@media (max-width: 560px) {
    .ia-home-hero h1 { font-size: clamp(2.15rem, 11vw, 3rem); }
    .ia-quick-quote-row { grid-template-columns: 1fr; }
    .ia-quick-quote .btn { width: 100%; }
    .ia-home-hero > .ia-trust-strip { order: 3; grid-template-columns: 1fr; padding: .35rem 1rem; }
    .ia-home-hero > .ia-trust-strip li { padding: .75rem 0 .75rem 1.75rem; }
    .ia-home-hero > .ia-trust-strip li + li { border-left: 0; border-top: 1px solid var(--rail); }
    .ia-home-hero > .ia-trust-strip li::before { left: .2rem; top: .8rem; }
    .ia-section-heading-row { align-items: flex-start; flex-direction: column; }
    .ia-section-heading-row .ia-btn-secondary { width: 100%; justify-content: center; }
    .ia-home-proof-list > div { grid-template-columns: 1fr; gap: .25rem; }
    .ia-home-final-actions { display: grid; width: 100%; }
    .ia-home-final-actions > * { width: 100%; justify-content: center; }
}

@media (max-width: 620px) {
    .ia-home-products { grid-template-columns: 1fr; }
    .ia-home-product { grid-template-columns: 96px minmax(0, 1fr); min-height: 96px; }
    .ia-home-product--text { grid-template-columns: 1fr; }
}

/* Ordinary pages still need breathing room above their first element now that main has no padding. */
.ia-main > *:first-child:not(.ia-band) { margin-top: 2.5rem; }
.ia-main > *:last-child:not(.ia-band) { margin-bottom: 2.5rem; }

/* Section rhythm. Without these every page was one undivided column of grey. */
.ia-section { padding-block: 2.75rem; border-top: 1px solid var(--rail); margin-top: 2.75rem; }
.ia-section h2 { font-size: clamp(1.35rem, 2.4vw, 1.8rem); margin: 0 0 1.5rem; }
.ia-eyebrow { font-family: var(--font-mono); font-size: .72rem; letter-spacing: .14em;
    text-transform: uppercase; color: var(--steel); margin: 0 0 .5rem; }
.ia-panel { background: var(--plate); border: 0; border-radius: 10px;
    box-shadow: inset 0 1px 0 var(--edge), 0 24px 48px -28px #000;
    padding: clamp(1.25rem, 3vw, 2rem); }
.ia-section-cta { margin: 1.75rem 0 0; }
.ia-next-step { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2rem;
    align-items: center; margin-top: 3.5rem; padding-block: 2rem;
    border-top: 1px solid var(--edge); border-bottom: 1px solid var(--edge); }
.ia-next-step h2 { margin: .25rem 0 .65rem; }
.ia-next-step .ia-content-text { margin-bottom: 0; max-width: 62ch; }
.ia-next-step-actions { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: .75rem; }

/* ---- Page opening (design 2026-08-12) ----
   Paper, a rule and an eyebrow. Quieter than the band by design: the band is the homepage's
   signature and stops being one the moment every page carries it. */
.ia-page-opening { padding-block: 2.5rem 1.75rem; border-bottom: 1px solid var(--rail);
    margin-bottom: 2.25rem; }
.ia-page-opening h1 { font-size: clamp(1.9rem, 4vw, 2.6rem); line-height: 1.1; margin: 0;
    max-width: 24ch; }
.ia-page-opening .ia-lead { margin-bottom: 0; margin-top: 1rem; }
.ia-page-opening--with-image { display: grid; grid-template-columns: minmax(0, 1fr) minmax(240px, 38%);
    gap: 2.5rem; align-items: center; }
.ia-page-opening-photo { margin: 0; min-width: 0; }
.ia-page-opening-photo img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: contain;
    border-radius: 8px; }
/* The opening replaces .ia-band as the first child, so it takes over the top-margin rule. */
.ia-main > .ia-page-opening:first-child { margin-top: 0; }
/* The first .ia-section after an opening already has the opening's rule above it. */
.ia-page-opening + .ia-section { border-top: none; margin-top: 0; padding-top: 0; }

/* A privacy policy set across the full 1120px container is unreadable — legal text gets a measure.
   counter-reset and the steel body colour join it here too (design 2026-08-12) rather than in a
   second .ia-legal rule further down, so this class has exactly one place that sets it. The rest of
   the legal-pages treatment — the numbered <h2>, the table of contents — is with the other
   design-2026-08-12 rules near the end of the file. */
.ia-legal { max-width: 74ch; counter-reset: ia-legal-section; color: var(--steel); }

/* Content links read as ink with a machined underline, not as default browser blue.
   .ia-card-link is the exception a card makes: the whole box is the target, and an underline through
   a bordered card reads as a mistake rather than as an affordance.
   :not(.ia-chip) (final whole-branch review, 2026-08-12): without it this rule is (0,5,1) and beats
   .ia-chip.active's (0,2,0), so an active chip's own white got overridden back to --ink — ink text on
   the --ink fill .ia-chip.active sets two lines down, contrast 1:1. Same specificity trap Task 15 hit
   and fixed for the legal ToC link, never back-applied here. The exclusion does not need to out-rank
   .ia-chip.active; it removes .ia-chip from this selector's match set entirely, so colour on a chip is
   settled between .ia-chip (0,1,0) and .ia-chip.active (0,2,0) alone. */
/* Six exclusions is a smell, and the list is the smell's cause: this rule claims every <a> under
   <main> and then carves out the controls one at a time, so any new link-shaped control is wrong
   until someone notices. .btn-readout was the third to be caught — and it was wrong from the day
   it was written, hidden only because the old --ink happened to be dark and dark-on-amber is what
   the button wanted anyway. Inverting the palette made a latent bug visible rather than causing it.
   The right shape is to scope this to prose instead of to <main>; that is a change with its own
   blast radius and is recorded in docs/FOLLOW-UPS.md. */
.ia-main a:not(.ia-btn-secondary):not(.ia-pager-link):not(.ia-brand):not(.ia-card-link):not(.ia-chip):not(.btn-readout) {
    color: var(--text); text-decoration-color: var(--edge); text-underline-offset: 3px;
}
.ia-btn-secondary { display: inline-flex; align-items: center; min-height: 44px; padding: .6rem 1.15rem;
    border: 1px solid var(--edge); border-radius: 6px; color: var(--text); text-decoration: none;
    font-weight: 600; background: transparent; }
.ia-btn-secondary:hover { background: var(--ink); color: var(--text); }
.ia-btn-sm { min-height: 36px; padding: .3rem .8rem; font-size: .88rem; font-weight: 500; }

/* Test-bench readout — the one place boldness is spent (spec §9). Amber-on-dark, tabular mono. */
.ia-readout {
    position: relative;   /* anchors the scanline overlay */
    overflow: hidden;     /* clips the digit roll, which is what makes it read as an odometer */
    display: inline-block;
    margin-top: 2rem;
    padding: 0.75rem 1.25rem;
    background: var(--ink);
    border: 1px solid #000;
    border-radius: 6px;
    box-shadow: inset 0 0 0 2px rgba(255,255,255,0.04), inset 0 2px 8px rgba(0,0,0,0.6);
}

/* The scanline (spec §9). A Prüfstand display is read through a glass front, and the faint
   horizontal banding is what the eye recognises. Kept under 20% opacity so it never competes with
   the digits, and pointer-events:none so it cannot swallow a click. */
.ia-readout::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: repeating-linear-gradient(to bottom,
        rgba(0, 0, 0, 0) 0 2px,
        rgba(0, 0, 0, 0.18) 2px 3px);
}
.ia-readout-value {
    display: inline-block;   /* transform has no effect on an inline box */
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    /* A range with two three-digit amounts used to wrap the "€" onto a second line inside the
       bezel. A bench readout does not wrap: it scales to the panel it is set into. */
    white-space: nowrap;
    font-size: clamp(1.3rem, 2.4vw, 2rem);
    /* The bright variant, not the raw accent: these digits sit on near-black, where the British blue
       and the Turkish red measured 3.5:1 against the bezel. The glow follows currentColor for the
       same reason — it was hard-coded amber, so both non-German sites glowed the wrong colour. */
    color: var(--ia-accent-bright);
    text-shadow: 0 0 8px color-mix(in srgb, currentColor 35%, transparent);
    animation: ia-readout-roll 400ms cubic-bezier(.2, .8, .2, 1);
}

/* The one orchestrated moment in the whole site (spec §9): the estimate rolls into place rather
   than blinking. OfferForm gives the span @key="ReadoutText", so Blazor discards and recreates the
   element whenever the value changes — which restarts this animation without a line of JavaScript.
   The parent's overflow:hidden clips the incoming digits, giving the odometer read. */
@keyframes ia-readout-roll {
    from { transform: translateY(-0.4em); opacity: 0; }
    to   { transform: translateY(0);      opacity: 1; }
}

/* Nothing else on the site animates, so this is the only rule that needs the opt-out (spec §9). */
@media (prefers-reduced-motion: reduce) {
    .ia-readout-value { animation: none; }
}

/* Footer — bookends the header so the page sits between two dark rules instead of trailing off. */
.ia-footer { background: var(--ink); color: var(--steel); padding-block: 2.75rem 1.75rem; margin-top: 4rem; font-size: .9rem; }
.ia-footer-cols { display: grid; grid-template-columns: 1.6fr 1fr 1fr; gap: 2.5rem; }
.ia-footer h3 { font-family: var(--font-body); font-size: .72rem; font-weight: 600; letter-spacing: .14em;
    text-transform: uppercase; color: var(--text); margin: 0 0 .9rem; }
.ia-footer a { color: var(--steel); text-decoration: none; }
.ia-footer a:hover { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
.ia-footer-addr { font-style: normal; line-height: 1.8; }
.ia-footer-nav { display: flex; flex-direction: column; gap: .6rem; }
.ia-footer-bottom { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
    margin-top: 2.5rem; padding-top: 1.25rem; border-top: 1px solid rgba(255,255,255,.14); font-size: .85rem; }

/* Quality floor (spec §9): visible focus rings, adequate targets.
   Ink on paper, not the accent: a focus ring has to be visible against the surface it is drawn on,
   and amber on white measures 1.8:1 — the ring was there and could not be seen. On the dark bands
   the same logic inverts, so those surfaces take the bright accent instead. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible {
    outline: 2px solid var(--ia-accent-bright);
    outline-offset: 2px;
}
.ia-band--ink a:focus-visible, .ia-band--ink button:focus-visible,
.ia-header a:focus-visible, .ia-footer a:focus-visible {
    outline-color: var(--ia-accent-bright);
}

/* Blazor focuses the <h1> on navigation so a screen reader announces the new page, and the browser
   draws a ring around the heading for it — Bootstrap's reset used to hide that. Suppressed outright
   rather than through :not(:focus-visible), which does not help here: Chrome reports the heading as
   focus-visible for this programmatic focus. Nothing is lost, because Blazor marks it tabindex="-1"
   and an element with a negative tabindex cannot be reached by Tab in the first place. */
h1[tabindex="-1"]:focus { outline: none; }

@media (max-width: 900px) {
    /* The header needs three rows at this width. Sticky, that is a fifth of a phone screen held
       permanently — it scrolls away instead. */
    .ia-header { position: static; }
    .ia-header-inner { flex-wrap: wrap; min-height: 0; padding-block: .6rem 0; gap: .35rem 1.25rem; }
    /* A tap target too, not just a wordmark. */
    .ia-brand { min-height: 44px; }
    /* Brand and phone share the first row rather than taking one each. Four stacked rows spent
       roughly 130px — a sixth of a phone screen — before the headline, and the phone number is the
       one thing up here a workshop actually reaches for. */
    .ia-header-actions { margin-left: auto; width: auto; order: 2; }

    /* One row that scrolls, not a paragraph of links that wraps. Five items at .8rem wrapped into
       two ragged rows of 19px-tall text: nothing here could be hit accurately with a thumb, the
       second row read as an afterthought, and the whole header still cost 114px. A single row of
       proper 44px targets is shorter than that and hittable. */
    .ia-nav { display: none; }
    .ia-mobile-nav { display: block; order: 3; width: 100%; border-top: 1px solid var(--rail); }
    .ia-mobile-nav summary { min-height: 44px; display: flex; align-items: center; cursor: pointer;
        color: var(--steel); font-size: .8rem; font-weight: 600; text-transform: uppercase;
        letter-spacing: .08em; list-style: none; }
    .ia-mobile-nav summary::-webkit-details-marker { display: none; }
    .ia-mobile-nav summary::after { content: "+"; margin-left: auto; color: var(--text); font-size: 1.2rem; }
    .ia-mobile-nav[open] summary::after { content: "−"; }
    .ia-mobile-nav nav { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
        padding-bottom: .75rem; gap: .25rem .75rem; }
    .ia-mobile-nav a { min-height: 44px; display: flex; align-items: center; color: var(--text);
        text-decoration: none; font-size: .9rem; padding-inline: .65rem; border-left: 2px solid transparent; }
    .ia-mobile-nav a[aria-current="page"] { border-left-color: var(--ia-accent-bright);
        background: color-mix(in srgb, var(--ia-accent) 10%, transparent); font-weight: 700; }

    /* The one control on this header a workshop actually presses. It was 23px of plain text. */
    .ia-phone { display: inline-flex; align-items: center; min-height: 44px; }

    .ia-footer-cols { grid-template-columns: 1fr 1fr; }

    /* Footer links sat at 19px too, stacked close enough together to hit the wrong one. */
    .ia-footer-cols a { display: inline-flex; align-items: center; min-height: 44px; }

    /* The address block's mail and phone links are the two things this page is for, and they were
       20px of underlined text stacked on consecutive lines — the easiest mis-tap on the site. Left
       inline (they read as part of the address, not as buttons), but given the height. */
    .ia-contact a[href^="mailto:"], .ia-contact a[href^="tel:"],
    address a[href^="mailto:"], address a[href^="tel:"],
    /* The Impressum prints the same address as a <p> beside its <address>, one link per line. */
    .ia-legal p > a[href^="mailto:"], .ia-legal p > a[href^="tel:"] {
        display: inline-flex; align-items: center; min-height: 44px;
    }

    /* Page numbers on the price list: 34px squares set side by side, so the neighbouring page was
       as easy to hit as the one meant. */
    .ia-pager-link { min-height: 44px; min-width: 44px; display: inline-flex; align-items: center; justify-content: center; }

    /* The part number is the row's link, and on a touch screen it is the whole row's target: at
       .35rem of padding it measured 35px, under the 44 a thumb needs, in a list of eighty-odd rows
       sitting one under another. Scoped to 900px, not 560, because a tablet is a touch screen too
       and between those two widths the table keeps its full desktop layout. */
    .ia-pricelist-number a,
    .ia-pricelist .ia-btn-secondary {
        display: inline-flex; align-items: center; min-height: 44px;
    }
    .ia-pricelist-number a { display: flex; }

    /* A paragraph whose whole content is one link is a navigation step standing on its own line —
       "Alle Ankaufspreise ansehen" at the foot of a part page — not a word inside a sentence, so it
       gets a target. Links genuinely inline in prose are left alone: WCAG exempts them, and giving
       them a height would push the lines of the paragraph apart. */
    .ia-main p > a:only-child { display: inline-flex; align-items: center; min-height: 44px; }
}

@media (max-width: 560px) {
    /* Keep the first form control inside the initial phone viewport. The assurances still sit
       between the promise and the form, but do not spend desktop-sized vertical rhythm. */
    .ia-band { padding-block: 2.25rem; }
    .ia-band--ink .ia-hero { padding-bottom: 1.75rem; }
    .ia-hero-actions { align-items: flex-start; flex-direction: column; margin-top: 1.1rem; }
    .ia-hero-cta { width: 100%; }
    .ia-next-step { grid-template-columns: 1fr; gap: 1.25rem; margin-top: 2.75rem; }
    .ia-next-step-actions { display: grid; justify-content: stretch; }
    .ia-next-step-actions .btn,
    .ia-next-step-actions .ia-btn-secondary { width: 100%; }
    .ia-trust-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .65rem 1rem; margin-top: 1.1rem; }
    .ia-trust-strip span { display: none; }
    .ia-trust-strip strong { font-size: .8rem; line-height: 1.35; }
    .ia-footer-cols { grid-template-columns: 1fr; gap: 1.75rem; }
}
.ia-brand--logo::before { display: none; }
.ia-brand-logo { display: block; width: auto; max-width: 112px; height: 36px; object-fit: contain; }
.ia-brand-logo--wordmark { width: 146px; max-width: min(146px, 38vw); }

/* CoreWerk has a separate host and identity from the three public companies. The login page uses
   the public site's lightweight CSS, but none of its navigation or legal footer. */
.ia-admin-login-shell { min-height: 100vh; }
.ia-admin-login-header { position: static; border-bottom: 1px solid var(--line); }
.ia-admin-login-product {
    margin-left: auto;
    color: var(--muted);
    font-family: var(--font-mono);
    font-size: .72rem;
    letter-spacing: .12em;
    text-transform: uppercase;
}
.ia-admin-login-main {
    display: grid;
    min-height: calc(100vh - var(--header-height));
    place-items: center;
    padding: clamp(2rem, 7vw, 5rem) 1rem;
}
.ia-login {
    width: min(100%, 430px);
    padding: clamp(1.5rem, 5vw, 2.5rem);
    border: 1px solid var(--edge);
    border-radius: 8px;
    background: var(--plate);
    box-shadow: 0 18px 50px rgba(0, 0, 0, .28);
}
.ia-login-kicker {
    margin: 0 0 .75rem;
    color: var(--ia-accent-bright);
    font-family: var(--font-mono);
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .13em;
    text-transform: uppercase;
}
.ia-login h1 { margin: 0; }
.ia-login-intro { margin: .5rem 0 1.75rem; color: var(--muted); }
.ia-login-form { display: grid; gap: 1.1rem; }
.ia-login-form .ia-field { display: grid; gap: .45rem; }
.ia-login-form label { color: var(--muted); font-size: .86rem; }
.ia-login-form .form-control {
    width: 100%;
    min-height: 48px;
    padding: .75rem .85rem;
    border: 1px solid var(--edge);
    border-radius: 6px;
    background: var(--well);
    color: var(--text);
    font: inherit;
}
.ia-login-form .form-control:focus {
    border-color: var(--ia-accent-bright);
    outline: 3px solid color-mix(in srgb, var(--ia-accent-bright) 25%, transparent);
}
.ia-login-form .btn { justify-self: start; margin-top: .35rem; }

@media (max-width: 520px) {
    .ia-admin-login-product { display: inline; }
    .ia-admin-login-main { align-items: start; padding-top: 2rem; }
    .ia-login { padding: 1.35rem; }
    .ia-login-form .btn { width: 100%; }
}

/* ---- Offer form (spec §8.3, §9): form left, sticky readout right on desktop ---- */
/* 60%/40% plus a 2rem gap adds up to more than the container, so the readout panel hung off the
   right edge at every width between the two breakpoints. Fractions absorb the gap; minmax(0,…)
   stops a long <option> from pushing the column wider than its share. */
.ia-form-grid { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: 2rem; align-items: start; }
.ia-form-main { min-width: 0; }
/* The right column holds the estimate and whatever the page passes as Aside. It exists because the
   readout alone left roughly 250px of empty band under it on a wide screen. */
.ia-form-side { min-width: 0; display: flex; flex-direction: column; gap: 1.5rem; }
/* Below this width the side column cannot hold a non-wrapping currency range without pushing the
   readout beyond its panel. Stack it while there is still room for a normal, non-fixed panel; the
   phone breakpoint later turns the same panel into the fixed bottom summary bar. */
@media (max-width: 1050px) {
    .ia-form-grid { grid-template-columns: 1fr; }
}
.ia-progress-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    margin: 0 0 1.5rem; }
.ia-progress-copy { color: var(--steel); font-family: var(--font-mono); font-size: .78rem;
    font-variant-numeric: tabular-nums; white-space: nowrap; }
.ia-steps { display: flex; gap: .5rem; list-style: none; padding: 0; margin: 0; }
.ia-steps li { width: 44px; height: 44px; border-radius: 50%; background: var(--rail); color: var(--text);
    display: flex; align-items: center; justify-content: center; font-weight: 700; font-family: var(--font-mono); }
.ia-steps li.active { background: var(--ink); }
/* A completed step is a link back to itself, so it has to look like one. */
.ia-steps li.done { background: var(--steel); }
.ia-steps li button, .ia-steps li span { all: unset; width: 100%; height: 100%; display: flex;
    align-items: center; justify-content: center; }
.ia-steps li button { cursor: pointer; }
.ia-steps li.done:hover { background: var(--ink); }
.ia-steps li button:focus-visible { outline: 2px solid var(--ia-accent-bright); outline-offset: 2px; border-radius: 50%; }

/* Photo step: thumbnails of what was actually uploaded, each removable on its own. */
.ia-step-heading { display: flex; align-items: center; gap: .75rem; }
.ia-step-heading h2 { margin-bottom: 0; }
.ia-step-heading > span { padding: .2rem .55rem; border: 1px solid var(--edge); border-radius: 999px;
    color: var(--steel); font-size: .75rem; }
.ia-photo-picker { display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: .35rem; min-height: 132px; margin-top: 1rem; padding: 1.25rem; border: 1px dashed var(--steel);
    border-radius: 8px; background: var(--well); text-align: center; cursor: pointer; }
.ia-photo-picker:hover { border-color: var(--ia-accent-bright); background: var(--plate); }
.ia-photo-picker strong { color: var(--text); }
.ia-photo-picker span { color: var(--steel); font-size: .8rem; line-height: 1.4; }
.ia-photo-picker:focus-within { outline: 2px solid var(--ia-accent-bright); outline-offset: 2px; }
.ia-photo-input { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0);
    clip-path: inset(50%); white-space: nowrap; }
.ia-photo-grid { list-style: none; padding: 0; margin: 1rem 0 0;
    display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 1rem; }
.ia-photo-grid li { display: flex; flex-direction: column; gap: .4rem; }
.ia-photo-grid img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 6px; border: 1px solid var(--rail); background: var(--white); }
.ia-photo-name { font-size: .75rem; color: var(--steel); overflow-wrap: anywhere; }
.ia-photo-remove { min-height: 44px; padding: .35rem .65rem; font-size: .8rem; border: 1px solid var(--rail);
    background: var(--white); border-radius: 4px; cursor: pointer; }
.ia-photo-remove:hover:not(:disabled) { border-color: var(--warn); color: var(--warn); }
/* Top-aligned, not bottom-aligned. `align-items: end` lined the cells up by their bottom edges, so
   any cell that grew — a validation message, the grade's own description — lifted its control above
   the others. The misalignment was worst exactly where it mattered, since the cell that grows is
   the one the seller just got wrong. Aligning the tops puts the labels on one line and therefore
   the controls on one line; anything extra hangs below its own field, where it belongs. */
.ia-line-row { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, .8fr) minmax(0, 1.8fr) auto;
    gap: .75rem; align-items: start; padding: .75rem 0; border-bottom: 1px solid var(--rail); }
.ia-line-note { font-size: .85rem; color: var(--steel); margin-top: .25rem; }
.ia-line-note.warn { color: var(--warn); }
.ia-line-note.ok { color: var(--ok); }

/* A validation message belongs on the control that caused it, not only in a list at the bottom. */
.ia-field-error { font-size: .85rem; color: var(--warn); margin-top: .25rem; }
.form-control.is-invalid, .form-select.is-invalid { border-color: var(--warn); border-width: 2px; }

/* Per-line prices under the readout: the total does not tell a seller which part is worth sending. */
.ia-readout-lines { list-style: none; margin: 1rem 0 0; padding: 1rem 0 0; border-top: 1px solid var(--rail); font-size: .9rem; }
.ia-readout-lines li { display: flex; justify-content: space-between; gap: 1rem; padding: .3rem 0; color: var(--steel); }
.ia-suggest { font-size: .85rem; margin-top: .35rem; }
.ia-suggest-btn { display: inline-flex; align-items: center; min-height: 44px; border: 1px solid var(--rail);
    background: var(--white); color: var(--text); border-radius: 4px; padding: .4rem .65rem;
    margin: .25rem 0 .25rem .35rem; cursor: pointer; }
.ia-field { margin-bottom: 1rem; }
.ia-field label, .ia-line-row label { display: block; font-size: .85rem; color: var(--steel); margin-bottom: .25rem; }
.ia-field-split { display: grid; grid-template-columns: 1fr 2fr; gap: .75rem; }
.ia-radio { display: inline-flex !important; align-items: center; gap: .5rem; min-height: 44px;
    margin-right: 1.25rem; color: var(--text) !important; cursor: pointer; }
.ia-radio input, .ia-check input { width: 20px; height: 20px; flex: none; }
.form-control, .form-select { width: 100%; min-height: 44px; padding: .5rem .75rem; border: 1px solid var(--edge);
    border-radius: 6px; font-family: var(--font-body); font-size: 1rem; background: var(--well);
    color: var(--text); }
.ia-mono, input.ia-mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.ia-honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.ia-form-nav { display: flex; gap: .75rem; margin-top: 1.5rem; }
.ia-errors { color: var(--warn); margin-top: 1rem; padding-left: 1.1rem; }
.ia-errors-summary { color: var(--warn); margin-top: 1rem; font-weight: 500; }
/* Tinted from the site's own accent rather than a fixed amber wash — #FDF3E7 was the German tint
   printed on all three sites, so the British and Turkish pages carried a stray warm box. */
.ia-nonbinding { background: color-mix(in srgb, var(--ia-accent) 10%, var(--white));
    border: 1px solid var(--ia-accent); padding: .75rem; border-radius: 6px; }
.ia-check { display: flex; align-items: center; gap: .6rem; min-height: 44px; margin: 1rem 0; cursor: pointer; }
.ia-turnstile { margin: 1rem 0; min-height: 65px; }
/* Sits directly under the widget and explains the disabled send button, so it belongs to the box
   above it rather than floating between two controls. */
.ia-turnstile-note { margin: -.5rem 0 1rem; font-size: .9rem; }
.ia-turnstile-note.warn { color: var(--warn); }
/* ---- Part page (design 2026-08-11) ----
   These are the pages a search lands on. They opened with everything jammed against the left edge of
   a 1120px container — a heading wrapping onto three lines with nine hundred pixels of empty band
   beside it, a small readout, and nothing anywhere to look at. Two columns, a photograph, and the
   price at display size. */
/* margin-bottom: the page-rhythm pass (design 2026-08-12) removed the .ia-band wrapper this hero
   used to sit in, and that band's own padding-block: 3.5rem (not .ia-part-hero's — it declared none)
   was the entire gap down to "Zustandstabelle" below. .ia-part-hero is now a direct grid item of
   .ia-main, and grid items never collapse margins with each other or with a descendant's, so nothing
   stood in for that padding once the band was gone: the gap silently fell to whatever margin the
   condition-table heading happens to carry on its own (~20px, the UA default for h2), a 56px loss.
   .ia-page-opening got its own margin-bottom for exactly this reason when it replaced the same band
   on the other nine pages; this is that same fix, on the one page that kept its band-era layout
   instead of switching to PageOpening. 3.5rem reuses the band's own figure, so the total gap below
   (this margin plus the h2's own ~20px, which still applies and is unaffected by this rule) lands
   within a few pixels of the original ~76px rather than at today's ~20px.

   That "h2's own ~20px" assumed the bare <h2> standing here when this comment was written. The very
   next task put GradeScale — a .ia-section, with its own border-top, margin-top and padding-top —
   where that h2 stood, and grid items do not collapse margins with a descendant's (see above), so
   the two no longer summed to ~76px: they stacked, to 56+44+1+44 = 145px. The override just past
   .ia-part-hero's own rules below restores a single, uncollapsed gap. */
.ia-part-hero { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 40%); gap: 3rem;
    align-items: center; margin-bottom: 3.5rem; }
.ia-part-hero > :first-child { min-width: 0; }
/* The right column contains a real product photo or an explicit, quiet empty state. */
.ia-part-hero:not(:has(.ia-photo)):not(:has(.ia-part-photo-empty)) { grid-template-columns: minmax(0, 64ch); }
.ia-part-hero .ia-photo { margin: 0; }
.ia-part-hero .ia-photo img { border-radius: 8px; aspect-ratio: 4 / 3; object-fit: contain; background: var(--white); }
/* Two photo sources could both be set. Show the part's own — it is the more specific of the two. */
.ia-part-hero .ia-photo ~ .ia-photo { display: none; }
.ia-part-photo-empty { min-height: 260px; padding: 2rem; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: .5rem; text-align: center; color: var(--steel);
    border: 1px dashed var(--rail); border-radius: 8px; background: var(--plate); }
.ia-part-photo-empty-number { color: var(--text); font-family: var(--font-mono);
    font-variant-numeric: tabular-nums; font-weight: 700; }
.ia-part-hero .ia-photo ~ .ia-part-photo-empty { display: none; }

/* Same shape as .ia-page-opening + .ia-section above: the element before it already supplies the
   gap, so the first .ia-section after it asks for none of its own border or margin. .ia-partpage is
   this page's own article wrapper — used nowhere else in the codebase — and GradeScale's is the only
   .ia-section inside it; Fits/Aliases/Related below render bare headings, not sections. So this
   reaches exactly one element, on exactly one page.

   padding-top is NOT zeroed to match, on purpose: .ia-page-opening's margin-bottom (2.25rem) was
   tuned as the whole gap, so zeroing the section's own spacing beside it leaves exactly that total.
   .ia-part-hero's margin-bottom (3.5rem) was tuned as only PART of the gap — it stood in for the
   band, and the rest of the ~76px always came from the bare <h2> that used to sit here, carrying its
   own ~20px of UA top margin. GradeScale replaced that <h2>, and .ia-section h2 zeroes its heading's
   top margin unconditionally, so that ~20px has nowhere left to come from unless this rule supplies
   it. Zeroing padding-top the way the precedent does would therefore land 20px short by construction,
   not by accident — 1.25rem restores it instead. */
.ia-part-hero + .ia-partpage > .ia-section:first-child { border-top: none; margin-top: 0; padding-top: 1.25rem; }

/* The part number leads; the short, localised price label supplies its context underneath. */
.ia-part-title { margin: 0 0 1rem; }
.ia-part-number { display: block; font-family: var(--font-mono); font-variant-numeric: tabular-nums;
    font-size: clamp(1.8rem, 4.5vw, 3rem); line-height: 1.05; letter-spacing: -.02em; }
/* --steel, not --rail: the part hero lost its ink band in the page-rhythm pass (design 2026-08-12)
   and now sits on --paper like every other page, where --rail measures under 2:1. */
.ia-part-title-rest { display: block; margin-top: .5rem; font-family: var(--font-display);
    font-size: clamp(1rem, 1.8vw, 1.25rem); font-weight: 600; color: var(--steel); }

.ia-readout--lg { display: block; width: fit-content; max-width: 100%; box-sizing: border-box;
    margin-top: 1.25rem; padding: 1rem 1.5rem; }
.ia-readout--lg .ia-readout-value { max-width: 100%; font-size: clamp(1.5rem, 4vw, 2.75rem); }
.ia-part-cta { margin: 1.5rem 0 0; }
.ia-band--ink .ia-eyebrow { color: var(--steel); }
.ia-band--ink .ia-part-title-rest { color: var(--steel); }

/* Sibling part numbers. Was a bare row of underlined mono numbers that read as a directory listing;
   the links are the same, at a size a thumb can hit. */
.ia-related { list-style: none; padding: 0; margin: 0 0 1.5rem;
    display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: .75rem; }
.ia-related a { display: flex; flex-direction: column; gap: .15rem; min-height: 44px; padding: .8rem 1rem;
    background: var(--white); border: 1px solid var(--rail); border-radius: 8px; color: var(--text); text-decoration: none; }
.ia-related a:hover { border-color: var(--text); }
.ia-related-maker { font-size: .8rem; color: var(--steel); }

@media (max-width: 820px) {
    .ia-part-hero { grid-template-columns: 1fr; gap: 2rem; }
    .ia-part-hero .ia-readout--lg { width: 100%; text-align: center; }
    .ia-part-hero .ia-readout--lg .ia-readout-value { font-size: clamp(1.35rem, 7vw, 2rem); }
    .ia-part-photo-empty { min-height: 180px; }
    .ia-part-cta .btn-readout { width: 100%; }
}

/* Offer photographs in the admin. Sized for a tablet on a workbench: big enough to judge a nozzle
   tip at a glance, and a tap opens the full-resolution file. */
.ia-photostrip { display: flex; flex-wrap: wrap; gap: .75rem; margin: .75rem 0; }
.ia-photostrip img { width: 140px; height: 140px; object-fit: cover; border-radius: 6px;
    border: 1px solid var(--rail); background: var(--white); display: block; }
.ia-photostrip a:focus-visible img { outline: 2px solid var(--ia-accent-bright); outline-offset: 2px; }
.ia-hint { color: var(--steel); }

/* The one sentence that only applies to parcels posted from this seller's country. Set apart from
   the generic packing line, because it is the part they have not already read elsewhere. */
/* --amber has never existed: the variable is --readout, and CSS drops an unknown one silently, so
   this rule painted its 3px rule in currentColor on every site since it was written. */
.ia-country-note { border-left: 3px solid var(--ia-accent); padding: .5rem 0 .5rem .75rem; margin-top: .75rem; }
.ia-band--ink .ia-country-note { border-left-color: var(--ia-accent-bright); }

/* What the grade the seller just chose actually means. Sits under the select, quiet enough not to
   compete with the fields but present enough to be read before they move on — mis-grading is what
   turns an accepted offer into a dispute. */
.ia-grade-hint { color: var(--steel); font-size: .85rem; line-height: 1.35; margin-top: .3rem; }
.ia-band--ink .ia-grade-hint { color: var(--steel); }
.ia-photo-list { color: var(--steel); font-size: .9rem; }

/* What the public site actually used out of Bootstrap: six classes. Bootstrap was 227 KB of
   render-blocking CSS to supply them, against 24 KB for this entire stylesheet, and inputs, selects
   and every layout on the site were already defined here. Declared above .btn-readout on purpose —
   both are single-class selectors, so source order is what lets the accent button keep its own
   border, padding and colour. The admin keeps its utilities: d-flex, gap-*, mt-* and the rest are
   MudBlazor's, loaded only in AdminLayout, and are untouched by this. */
.btn { display: inline-flex; align-items: center; justify-content: center; min-height: 44px;
    padding: .5rem 1.25rem; border: 1px solid transparent; border-radius: 6px;
    font: inherit; font-weight: 600; line-height: 1.2; cursor: pointer; text-decoration: none; }
.btn-outline-secondary { border-color: var(--steel); background: var(--white); color: var(--text); font-weight: 500; }
.btn-outline-secondary:hover { border-color: var(--text); }
.btn-link { min-height: 44px; padding: 0; border: none; background: none; color: var(--steel);
    font-weight: 500; text-decoration: underline; text-underline-offset: 3px; }
.btn-link:hover { color: var(--warn); }
.table { width: 100%; border-collapse: collapse; }
.table th, .table td { padding: .6rem .5rem; border-bottom: 1px solid var(--rail); text-align: left; }
.text-end { text-align: right; }
.text-danger { color: var(--warn); }

.btn-readout { background: var(--ia-accent-fill); color: var(--ia-accent-on-fill); border: none; font-weight: 600;
    min-height: 44px; padding: .5rem 1.25rem; border-radius: 6px; cursor: pointer; }
.btn-readout:hover:not(:disabled) { filter: brightness(.95); }

/* Nothing said "disabled" anywhere in this stylesheet, so a button that refuses to press looked
   exactly like one that works — including the one the whole form ends at, while it waits for the
   robot check. The seller pressed a full-strength amber button and nothing happened. */
.btn:disabled, .btn-readout:disabled {
    opacity: .5;
    cursor: not-allowed;
    filter: grayscale(.35);
}

.ia-readout-panel { position: sticky; top: 1.5rem; background: var(--white); border: 1px solid var(--rail);
    border-radius: 8px; padding: 1.25rem; }
@media (max-width: 1050px) {
    .ia-readout-panel { position: static; }
}
.ia-readout-title { font-size: .85rem; color: var(--steel); text-transform: uppercase; letter-spacing: .04em; margin-bottom: .75rem; }
.ia-readout-title-short { display: none; }
.ia-min-warn { color: var(--warn); font-size: .85rem; margin-top: .75rem; }

/* ---- Confirmation (spec §8.3) ----
   The reference code used to get its own flex block with a 2rem code display (.ia-confirm-ref /
   .ia-confirm-ref-label / .ia-confirm-code); it is a SpecTable row now, the same as every other
   fact on this page's neighbours (design 2026-08-12 datasheet). Those three rules are gone with it. */
.ia-confirm-putcode { font-size: 1.2rem; }
/* --white, not --paper: the body background IS --paper, so these boxes were invisible — an address
   block that reads as a card is the one a seller copies from. */
.ia-address, .ia-imprint { background: var(--white); border: 1px solid var(--rail); padding: 1rem; border-radius: 6px; font-family: var(--font-body); white-space: pre-wrap; }
.ia-onrequest { background: color-mix(in srgb, var(--warn) 8%, var(--white)); border: 1px solid var(--warn); padding: 1rem; border-radius: 6px; }

/* ---- Status + price list ---- */
.ia-status-lookup-row, .ia-pricelist-filter { display: flex; gap: .5rem; flex-wrap: wrap; margin: 1.5rem 0; align-items: center; }
/* select dropped (design 2026-08-12): the manufacturer filter is .ia-chips now, and neither form
   here has a <select> left — .ia-status-lookup-row never did either. */
.ia-status-lookup-row input, .ia-pricelist-filter input { max-width: 260px; }
.ia-status-missing { max-width: 42rem; margin-top: 1.25rem; padding: 1rem 1.1rem;
    border-left: 3px solid var(--warn); background: color-mix(in srgb, var(--warn) 7%, var(--white)); }
.ia-status-missing p { margin: 0; }
.ia-status-missing .ia-hint { margin-top: .45rem; }
@media (max-width: 560px) {
    /* Kept beside the base max-width rule so source order cannot silently cap only the input while
       the submit button stretches across the phone. */
    .ia-status-lookup-row { display: grid; }
    .ia-status-lookup-row input,
    .ia-status-lookup-row .btn-readout { width: 100%; max-width: none; }
}
.ia-filter-clear { min-height: 44px; display: inline-flex; align-items: center; padding-inline: .4rem;
    color: var(--steel); font-size: .88rem; }
.ia-filter-summary { max-width: 780px; margin: -.5rem 0 .75rem; color: var(--steel); font-size: .85rem; }

/* ---- Filter chips (design 2026-08-12) ----
   Links, not a <select>: they work without JavaScript, they show the current filter without being
   opened, and they are real 44px targets on a phone — the quality floor (spec §9), not the 36px a
   first draft of this rule had. */
.ia-chips { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1.5rem; }
/* min-width as well as min-height: the shortest label today is "Alle" at 54px, so nothing changes
   now, but a one-word maker or a two-letter group would drop under the floor in the one dimension
   nothing was guarding. */
.ia-chip { display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px; min-width: 44px; padding: .25rem .85rem;
    border: 1px solid var(--rail); border-radius: 999px; color: var(--text);
    text-decoration: none; font-size: .9rem; }
.ia-chip:hover { border-color: var(--text); }
/* --ink fill with --white text: the same pairing .ia-pager-link.active already uses, and it is the
   one combination on this palette guaranteed to clear AA regardless of site accent (spec §9, D-028
   — the accent itself is per-site and not every site's clears 4.5:1 on white). */
.ia-chip.active { background: var(--ink); color: var(--text); border-color: var(--text); }

/* The price table is the page's whole content — it reads as a document, not as loose rows.
   Capped rather than stretched: four short columns spread across 1120px broke every row into four
   islands with three hundred pixels of nothing between the price and the button it belongs to.
   No border-radius/overflow:hidden (design 2026-08-12): that rounded-box clip made the table's own
   <thead> the nearest ancestor with a non-visible overflow, which silently pins position:sticky to
   the table's own box instead of the viewport — the header would never have actually stuck. Hairline
   ruling replaces the box instead: a flat edge, an ink rule under the header, rail rules under a row. */
.ia-pricelist { width: 100%; max-width: 780px; border-collapse: separate; border-spacing: 0; background: var(--white);
    border: 1px solid var(--rail); }
/* .text-end never took effect in this table: `.table th, .table td` sets text-align:left at equal
   specificity and wins on source order, so the price column has been left-aligned since it was
   written — which is also why its tabular figures never lined up. Scoped here so the utility keeps
   behaving as it does everywhere else. */
.ia-pricelist th.text-end, .ia-pricelist td.text-end { text-align: right; }
/* Sticky (design 2026-08-12): a seller scrolling 25 rows should not lose the column labels above the
   price. top:var(--header-height), not top:0 — .ia-header is sticky unconditionally above 900px
   (only the 900px query below turns it static), so a table header stuck at 0 would sit directly
   under the site header's own band and, at z-index 1 against the header's 30, render hidden behind
   it on every desktop scroll. --header-height is read by both .ia-header-inner's min-height and this
   top offset (defined once, at the top of the file, with the rest of the tokens) so the two cannot
   silently drift into two different numbers. Below 900px the header is no longer sticky (same
   query) and this is turned off too, so the offset stops mattering there.

   What the token does not fix (code review, 2026-08-12): --header-height is a value, not a
   guarantee. .ia-header-inner is a single-row flex box holding the brand, five nav links and the
   phone/language actions; only .ia-nav's own children wrap internally (flex-wrap on .ia-nav itself,
   not on .ia-header-inner, below 900px). If that content cannot fit on one line somewhere above
   900px, the header grows past --header-height and this would stick a few pixels behind it again.
   I looked for a cheap way to forbid that growth without redesigning the header — forcing
   .ia-nav to nowrap above 900px was the obvious candidate — and rejected it: a flex item that
   cannot wrap and cannot shrink past its own min-content width overflows its row instead, which
   risks the sideways-scrolling-page bug commit e16584c fixed on this exact page, and trading a
   few pixels of sticky-header overlap for that is a worse bug, not a fix. Left alone; the token
   still closes the one gap it can — two literals that could silently disagree are now one number
   that cannot. */
.ia-pricelist th { position: sticky; top: var(--header-height); z-index: 1; background: var(--paper);
    font-family: var(--font-body); font-size: .8rem; font-weight: 600; letter-spacing: .07em;
    text-transform: uppercase; color: var(--steel); text-align: left; padding: .6rem .5rem;
    border-bottom: 1px solid var(--ink); }
/* Hairline ruling, no zebra and no boxes — a datasheet, not a spreadsheet. Rail, not the old --paper:
   a border the same colour as the background it sits on is not a rule, it is a rounding error. */
.ia-pricelist td { padding: .7rem .5rem; border-bottom: 1px solid var(--rail); vertical-align: middle; }
.ia-pricelist tbody tr:last-child td { border-bottom: none; }
.ia-pricelist tbody tr:hover td { background: var(--paper); }

/* The price is the only reason anybody opens this page, and it was set at body weight in the same
   size as the part number beside it. The number is what you search for; the price is what you read. */
.ia-pricelist-price { font-size: 1.05rem; font-weight: 600; }
.ia-pricelist-price-value { display: inline-block; white-space: nowrap; }
/* A part number is one token. Wrapping "0 414 720 210" onto two lines breaks the tabular grid that
   makes a column of them scannable — which is exactly what happened on a phone. */
.ia-pricelist-number { white-space: nowrap; }
/* The action is repeated, so it stays outline-only; the accent-coloured edge and arrow make it read
   as an available next step instead of the disabled grey control it resembled before. */
.ia-pricelist .ia-btn-secondary { gap: .3rem; border-color: color-mix(in srgb, var(--ia-accent-bright) 65%, var(--steel));
    color: var(--ia-accent-bright); background: color-mix(in srgb, var(--ia-accent) 5%, transparent); }
.ia-pricelist .ia-btn-secondary::after { content: "→"; }
.ia-pricelist .ia-btn-secondary:hover { border-color: var(--ia-accent-fill); background: var(--ia-accent-fill);
    color: var(--ia-accent-on-fill); }
.ia-pricelist-mobile-sell { display: none; }

/* The table was 483px wide in a 390px viewport, so the whole page scrolled sideways — header, hero
   and footer sliding with it. This is the price list, which is where a search for a part number
   lands, and most of those searches are on a phone. The guard itself now lives in the 900px query
   below, next to the reason it has to (see that comment) — dropping the manufacturer into the
   number cell below 560px is what makes the scroll unnecessary rather than merely contained. */
.ia-pricelist-maker { display: none; }
.ia-status-card { background: var(--white); border: 1px solid var(--rail); border-radius: 10px; padding: clamp(1.25rem, 3vw, 2rem); margin-top: 1rem; }
.ia-status-card h2 { font-size: 1rem; margin: 2rem 0 .75rem; }

/* An empty result offers a way out rather than a bare sentence — the same drawing the site uses
   beside a price, faded, so the page still reads as this site's rather than as a generic 404. */
.ia-empty { text-align: center; padding: 3rem 1rem; color: var(--steel); }
.ia-empty .ia-injector { margin: 0 auto 1.5rem; opacity: .4; }
.ia-not-found { min-height: 20rem; display: grid; place-content: center; }
.ia-not-found .ia-injector { width: 72px; }
.ia-not-found-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: .75rem; }
@media (max-width: 560px) {
    .ia-not-found-actions { display: grid; }
    .ia-not-found-actions .btn,
    .ia-not-found-actions .ia-btn-secondary { width: 100%; }
}

@media (max-width: 900px) {
    /* Two things share this breakpoint, not by coincidence: the site header already stops being
       sticky here (.ia-header above), and a horizontally-scrolling wrapper is the one ancestor that
       cannot sit above a position:sticky child at any width — a non-visible overflow anywhere in the
       chain re-parents the sticky element's scrollport away from the viewport (the same reason
       .ia-pricelist itself lost its overflow:hidden above). Below 900px neither problem can occur,
       because the header rule directly above turns the sticky header off first: the guard here is
       free to clip again, and it is confined to the width the table actually needs it at — 780px
       plus this page's padding clears 900px with room to spare, so it was never doing anything up
       there anyway. */
    .ia-pricelist th { position: static; }
    .ia-pricelist-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

/* Where the offer is on the normal path. Line through the dots, filled up to the current one. */
.ia-track { list-style: none; display: flex; padding: 0; margin: 0 0 2rem; }
.ia-track li { flex: 1; position: relative; text-align: center; font-size: .8rem; color: var(--steel); }
.ia-track li::before { content: ""; position: absolute; top: 7px; left: -50%; width: 100%; height: 2px; background: var(--rail); }
.ia-track li:first-child::before { display: none; }
.ia-track li.done::before, .ia-track li.active::before { background: var(--ink); }
.ia-track-dot { display: block; width: 16px; height: 16px; margin: 0 auto .5rem; border-radius: 50%;
    background: var(--rail); position: relative; z-index: 1; }
.ia-track li.done .ia-track-dot { background: var(--ink); }
.ia-track li.active .ia-track-dot { background: var(--ink); box-shadow: 0 0 0 4px rgba(16,20,24,.15); }
.ia-track li.done, .ia-track li.active { color: var(--text); }
.ia-track li.active { font-weight: 600; }
.ia-track-label { display: block; padding: 0 .25rem; overflow-wrap: anywhere; }
/* .ia-status-row is gone (design 2026-08-12 datasheet): the three status facts it laid out by hand
   are a SpecTable now, styled by .ia-spec-row like every other fact list on the site. */
.ia-warn { color: var(--warn); }
.ia-ok { color: var(--ok); }
.ia-pager { display: flex; gap: .35rem; margin-top: 1rem; }
.ia-pager-link { padding: .25rem .6rem; border: 1px solid var(--rail); border-radius: 4px; text-decoration: none; color: var(--steel); }
.ia-pager-link.active { background: var(--ink); color: var(--text); }
/* The four steps, as cards. They were a bare <ol>: browser bullets, browser indent — the single
   most unfinished-looking thing on the site. The numeral comes from a CSS counter now, which is
   also why the .resx strings no longer carry a "1. " prefix; they used to print twice.
   decimal-leading-zero because "01" is how a bench labels a channel. */
.ia-steps-list { list-style: none; counter-reset: ia-step; padding: 0; margin: 0;
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; }
.ia-steps-list li { counter-increment: ia-step; background: var(--white); border: 1px solid var(--rail);
    border-radius: 8px; padding: 2.1rem 1.25rem 1.25rem; line-height: 1.55; position: relative; }
/* The numeral was --rail on white: the same grey as the card's own border, which left four boxes
   holding nothing but a sentence. Ink for the figure — an accent that pale on white measures under
   2:1 — and the colour arrives as a short rule above it, where being decoration is honest.
   Positioned rather than laid out in flow: the card's text is an anonymous box that cannot be
   ordered against ::before and ::after, so any flex arrangement puts the sentence first. */
.ia-steps-list li::after { content: ""; position: absolute; top: 1.25rem; left: 1.25rem;
    width: 28px; height: 3px; border-radius: 2px; background: var(--ia-accent); }
.ia-steps-list li::before { content: counter(ia-step, decimal-leading-zero); display: block;
    font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 1.5rem;
    font-weight: 500; color: var(--text); margin-bottom: .5rem; }

@media (max-width: 900px) {
    .ia-steps-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 520px) {
    .ia-steps-list { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
    .ia-pricelist { table-layout: fixed; }
    .ia-pricelist .ia-col-maker { display: none; }
    .ia-pricelist-maker { display: block; font-family: var(--font-body); font-size: .75rem; color: var(--steel); }
    .ia-pricelist th, .ia-pricelist td { padding-left: .6rem; padding-right: .6rem; }
    /* Four columns do not fit in 390px — the sell button was sliced in half by the scroll
       container. Dropping it leaves number and price, which fit with room to spare, and makes the
       part number the row's target: it leads to a page carrying the photograph, all four grade
       prices and the same call to action at full size. */
    .ia-pricelist .ia-col-sell { display: none; }
    .ia-pricelist-number { white-space: normal; overflow-wrap: anywhere; }
    .ia-pricelist-number a { padding-block: .35rem; }
    .ia-pricelist-price { width: 42%; font-size: clamp(.8rem, 3.6vw, 1rem); }
    .ia-pricelist-price-value { max-width: 100%; }
    .ia-pricelist-mobile-sell { display: flex; margin-top: .35rem; margin-left: auto;
        min-height: 38px; width: fit-content; font-family: var(--font-body); }
    .ia-pricelist-filter { display: grid; grid-template-columns: minmax(0, 1fr) auto; }
    .ia-pricelist-filter input { max-width: none; }
    .ia-pricelist-filter .ia-filter-clear { grid-column: 1 / -1; justify-self: start; }
}

@media (max-width: 820px) {
    .ia-page-opening--with-image { grid-template-columns: 1fr; gap: 1.5rem; }
    .ia-form-grid { grid-template-columns: 1fr; }
    /* Both selectors, because .ia-band--ink .ia-readout-panel (0,2,0) paints this panel 4% white so
       it reads as a lighter plate inside the hero's ink band. Down here the panel is not in the band
       any more — it is fixed over the whole document — and at 4% opacity the page scrolled visibly
       through the one number the site exists to show, with the grade description and the "Zeile
       hinzufügen" button legible straight through the price. A bar that floats over arbitrary
       content has to be opaque. */
    .ia-readout-panel,
    .ia-band--ink .ia-readout-panel {
        position: fixed; bottom: 0; left: 0; right: 0; top: auto; border-radius: 0;
        background: var(--plate); border: none; border-top: 1px solid var(--edge);
        box-shadow: 0 -10px 28px rgba(0, 0, 0, .5);
        z-index: 20; display: flex; align-items: center;
        justify-content: space-between; gap: 1rem; padding: .75rem 1rem;
        /* Phones with a home indicator put a strip of their own below the bar. */
        padding-bottom: calc(.75rem + env(safe-area-inset-bottom, 0px));
    }
    /* The bar is fixed over the page, so the form needs room to be scrolled clear of it — without
       this the last field and the Weiter button sit underneath it and cannot be reached. Measured
       against the rendered bar (72px at 390px wide), not guessed. */
    .ia-form-grid { padding-bottom: calc(6.5rem + env(safe-area-inset-bottom, 0px)); }
    .ia-form-grid--no-estimate { padding-bottom: 0; }
    .ia-readout-panel--empty,
    .ia-band--ink .ia-readout-panel--empty { display: none; }
    .ia-readout-title { margin: 0; flex: 1; min-width: 0; }
    /* Wrapping "0,00 €" onto two lines inside the bezel is the one thing a bench readout never does. */
    .ia-readout { margin-top: 0; padding: .5rem .9rem; }
    .ia-readout-value { font-size: 1.5rem; white-space: nowrap; }
    .ia-line-row { grid-template-columns: minmax(0, 1fr) 5.5rem; }
    .ia-line-row > :nth-child(3),
    .ia-line-row > .ia-line-remove { grid-column: 1 / -1; }
    .ia-form-nav .btn-readout { flex: 1; }
    .ia-review thead, .ia-status-lines thead { display: none; }
    .ia-review, .ia-review tbody, .ia-review tr, .ia-review td,
    .ia-status-lines, .ia-status-lines tbody, .ia-status-lines tr, .ia-status-lines td { display: block; width: 100%; }
    .ia-review tr, .ia-status-lines tr { padding: .6rem 0; border-bottom: 1px solid var(--rail); }
    .ia-review td, .ia-review td.text-end,
    .ia-status-lines td, .ia-status-lines td.text-end { display: grid; grid-template-columns: minmax(7rem, .8fr) minmax(0, 1.2fr);
        gap: .75rem; padding: .3rem 0; border: 0; text-align: right; overflow-wrap: anywhere; }
    .ia-review td::before, .ia-status-lines td::before { content: attr(data-label); color: var(--steel); font-family: var(--font-body);
        font-size: .78rem; text-align: left; }
}

.ia-mode-toggle { display: inline-flex; align-items: center; min-height: 44px; margin-top: .25rem; }

/* A phone, where the readout holds a range rather than one number. "32,30 € – 38,00 €" set at
   1.5rem in the mono face is 270 of the 390 pixels available, which left the label beside it about
   90 and cut it mid-word: "GESCHÄTZ… ANKAUFSP…". The number keeps its prominence; the label gets
   enough room to wrap to two lines and read. */
@media (max-width: 480px) {
    /* Four accessible 44px step targets plus the progress copy do not share a 360px panel in every
       locale. Let the copy take the next line instead of widening the whole document. */
    .ia-progress-row { flex-wrap: wrap; gap: .5rem; }
    .ia-progress-copy { min-width: 0; white-space: normal; }
    /* Keep the first decision visually attached to the part-number field. Bootstrap-era button
       padding made this link wrap even at 390px, while the quantity control reserved more width
       than a two-digit count needs. The condition still gets its own full row below. */
    .ia-line-row { grid-template-columns: minmax(0, 1fr) 4.75rem; gap: .75rem .5rem; }
    .ia-line-row > :nth-child(2) input { font-variant-numeric: tabular-nums; }
    .ia-mode-toggle { padding-inline: 0; justify-content: flex-start; text-align: left; }
    .ia-readout-title { flex: 0 0 4.75rem; font-size: .7rem; letter-spacing: .02em; line-height: 1.25; }
    .ia-readout-title-full { display: none; }
    .ia-readout-title-short { display: inline; }
    .ia-readout { flex: 1; min-width: 0; padding: .4rem .5rem; text-align: center; }
    .ia-readout-value { font-size: 1.05rem; }
}

/* Content blocks (design 2026-08-10). The stat row is the only place figures get display size;
   §9 spends its boldness on the readout, so these stay quiet. */
.ia-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 1rem; margin: 0; padding: 0; }
.ia-stat { background: var(--white); border: 1px solid var(--rail); border-radius: 8px; padding: 1.25rem; margin: 0; }
.ia-stat-value { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 1.75rem; color: var(--text); margin: 0; }
.ia-stat-label { font-size: .9rem; color: var(--steel); margin: .35rem 0 0; }

/* Strip form, for the hero column beside the estimate. Figure and label on one line separated by a
   rule — four bordered cards stacked in a narrow column would repeat the box the rest of the page
   was already made of. */
.ia-stats--stacked { grid-template-columns: 1fr; gap: 0; }
.ia-stats--stacked .ia-stat { display: flex; align-items: baseline; justify-content: space-between;
    gap: 1rem; background: transparent; border: 0; border-top: 1px solid rgba(255,255,255,.16);
    border-radius: 0; padding: .85rem 0; }
.ia-stats--stacked .ia-stat:first-child { border-top: 0; padding-top: 0; }
.ia-stats--stacked .ia-stat-value { font-size: 1.3rem; color: var(--text); }
.ia-stats--stacked .ia-stat-label { margin: 0; text-align: right; color: var(--steel); font-size: .85rem; }
/* Prose beside a photograph, or beside the injector drawing (design 2026-08-12, HowItWorks). Collapses
   to a single column when neither is present — ContentPhoto can render nothing, so an unset picture
   never leaves an empty half — the same :has() guard the part page hero uses. The drawing is never
   unset, so a .ia-split built around InjectorDrawing always keeps its two columns. */
.ia-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 2.5rem; align-items: center; }
.ia-split:not(:has(.ia-photo, .ia-injector)) { grid-template-columns: minmax(0, 68ch); }
.ia-split .ia-photo { margin: 0; }
.ia-split .ia-lead { margin-top: 0; }

@media (max-width: 820px) {
    .ia-split { grid-template-columns: 1fr; gap: 1.5rem; }
}

.ia-photo { margin: 2rem 0; }
.ia-photo img { width: 100%; height: auto; border-radius: 4px; display: block; }
.ia-photo figcaption { font-size: .85rem; color: var(--steel); margin-top: .5rem; }

/* Authored text keeps the line breaks the operator typed (same idiom as .ia-address/.ia-imprint
   above) — plain text with line breaks, not markup, so this is the only mechanism that lets a
   multi-paragraph intro read as more than one run-on block. */
.ia-content-text { white-space: pre-line; }

/* Contact: the office address and the depot address side by side. They are different addresses and
   sending parts to the wrong one is expensive, so they get equal weight rather than one buried under
   the other. <address> is italic by default in every browser — an address block is not an aside. */
.ia-contact-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2.5rem; margin: 2rem 0; }
.ia-contact-grid h2 { font-size: 1.05rem; margin: 0 0 .75rem; }
.ia-contact-grid section > h2 ~ h2 { margin-top: 2rem; }
.ia-contact { font-style: normal; line-height: 1.9; }
.ia-contact-grid .ia-address { margin: 0 0 1rem; }

@media (max-width: 820px) {
    .ia-contact-grid { grid-template-columns: 1fr; gap: 2rem; }
}

@media (max-width: 600px) {
    .ia-stats { gap: 1.5rem; }
    .ia-stat-value { font-size: 1.4rem; }
}

/* ---- The injector drawing (design 2026-08-12) ----
   The site's only imagery, drawn in code. currentColor is what lets one file serve the ink band and
   the paper page, so nothing here sets a colour. */
.ia-injector { display: block; height: auto; color: inherit; }
.ia-injector--small { width: clamp(80px, 12vw, 130px); }
.ia-injector--labelled { width: 100%; max-width: 340px; }
.ia-injector-callout text { font-family: var(--font-mono); font-size: 9px; letter-spacing: .06em; }

/* A decorative drawing is the first thing to drop when the column gets narrow — and, below a
   derived width, the only thing keeping it from spilling past the viewport's own edge (code
   review, 2026-08-12: this bled a horizontal scrollbar, the same class of bug already fixed once
   on the price list).

   .ia-main's centre track is min(1120px, 100% - 2rem); once the viewport is wide enough that the
   centre track hits its 1120px cap, each of the two 1fr gutters left over is (viewport - 1120px)/2.
   Below that cap the gutter is a flat 1rem (16px) — nowhere near enough on its own. This drawing's
   bleed (right: -3rem above) needs 3rem = 48px of gutter to stay inside the viewport, so solving
   (viewport - 1120px)/2 >= 48px gives viewport >= 1216px. Below 1216px the gutter cannot absorb the
   full 48px bleed — by as much as 32px at the low end — so the drawing is hidden until the gutter
   can actually take it, rather than hidden at an arbitrary 900px that left a wide band overflowing. */
@media (max-width: 1216px) {
    }


/* ---- Datasheet rows (design 2026-08-12) ----
   Hairline rules, not boxes: three bordered cards in a column is the "identical boxes" the visual
   system exists to stop. The label column is fixed so values line up down the page. */
.ia-spec-table { margin: 0; display: grid; gap: 0; }
.ia-spec-row { display: grid; grid-template-columns: minmax(140px, 12rem) 1fr; gap: 1rem;
    padding: .7rem 0; border-top: 1px solid var(--rail); align-items: baseline; }
.ia-spec-row:last-child { border-bottom: 1px solid var(--rail); }
.ia-spec-table dt { color: var(--steel); font-size: .85rem; letter-spacing: .02em; }
.ia-spec-table dd { margin: 0; }
.ia-band--ink .ia-spec-row { border-color: rgba(255,255,255,.16); }
.ia-band--ink .ia-spec-table dt { color: var(--steel); }

@media (max-width: 560px) {
    .ia-spec-row { grid-template-columns: 1fr; gap: .15rem; padding: .6rem 0; }
}

/* ---- Condition grades as a priced scale (design 2026-08-12) ----
   A stepped list, not four cards. The code sits in a mono chip so the column reads as a scale from
   A down to D, and the price is the row's right edge so the eye can run down it.

   Selectors below are scoped under .ia-grade-scale rather than bare .ia-grade. The part page used
   to own an unrelated .ia-grade of its own — a bar-chart card under .ia-grades — and an unscoped
   rule here would have merged into it; that bespoke markup is gone now that the part page renders
   this same component (design 2026-08-12, part-page task), but the scoping stays as the cheap
   guard against the next thing that reaches for the same class name. */
.ia-grade-scale { list-style: none; margin: 0; padding: 0; counter-reset: none; }
.ia-grade-scale .ia-grade { display: grid; grid-template-columns: 2.5rem minmax(0, 1fr) auto; gap: .35rem 1rem;
    padding: 1rem 0; border-top: 1px solid var(--rail); align-items: baseline; }
.ia-grade-scale .ia-grade:last-child { border-bottom: 1px solid var(--rail); }
.ia-grade-code { grid-row: span 2; font-size: 1.35rem; font-weight: 500; color: var(--text);
    border: 1px solid var(--rail); border-radius: 4px; text-align: center; padding: .1rem 0;
    align-self: start; }
.ia-grade-label { font-weight: 600; }
.ia-grade-desc { grid-column: 2; color: var(--steel); font-size: .92rem; }
.ia-grade-price { grid-column: 3; grid-row: 1; font-size: 1.15rem; font-variant-numeric: tabular-nums; }
.ia-band--ink .ia-grade-scale .ia-grade { border-color: rgba(255,255,255,.16); }
.ia-band--ink .ia-grade-code { color: var(--text); border-color: rgba(255,255,255,.28); }
.ia-band--ink .ia-grade-desc { color: var(--steel); }

@media (max-width: 560px) {
    .ia-grade-scale .ia-grade { grid-template-columns: 2.25rem minmax(0, 1fr); }
    .ia-grade-price { grid-column: 2; grid-row: auto; font-size: 1rem; }
}

/* ---- Quantity tiers (design 2026-08-12) ---- */
.ia-tier-scale { list-style: none; margin: 0; padding: 0; }
.ia-tier { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .3rem 1rem;
    padding: .9rem 0; border-top: 1px solid var(--rail); align-items: baseline; }
.ia-tier:last-child { border-bottom: 1px solid var(--rail); }
.ia-tier-range { font-size: 1.05rem; font-variant-numeric: tabular-nums; color: var(--text); }
.ia-tier-price { font-size: 1.1rem; font-variant-numeric: tabular-nums; }
/* The bar spans the row under the text, filled to the tier's share of the top multiplier. */
/* The fill was --ink, which read as dark-on-paper and is now the ground itself, so the bar
   showed nothing. Track and fill both have to be lighter than the housing to exist on it. */
.ia-tier-bar { grid-column: 1 / -1; height: 3px; background: var(--rail); position: relative; }
.ia-tier-bar::before { content: ""; position: absolute; inset: 0 auto 0 0;
    width: var(--ia-tier-fill); background: var(--steel); }

/* ---- Manufacturer strip (design 2026-08-12) ----
   Names, not logos — see the component. Letterspaced display type reads as a credential row. */
.ia-maker-row { list-style: none; margin: 0; padding: 0; display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; align-items: stretch; }
.ia-maker-row li { font-family: var(--font-display); font-weight: 600; font-size: 1.05rem;
    letter-spacing: .04em; color: var(--steel); display: flex; flex-direction: column; gap: .65rem;
    min-width: 0; text-align: center; align-items: center; }
/* A picture is an addition to the name, never a replacement, so the row stays readable when only
   some manufacturers have one. Fixed ratio so a tall photo cannot make one column dwarf the rest. */
.ia-maker-photo { width: 100%; max-width: 180px; height: 112px; object-fit: contain;
    border-radius: 6px; display: block; padding: .75rem; background: var(--paper); }
.ia-band--ink .ia-maker-row li { color: var(--steel); }
.ia-band--ink .ia-tier { border-color: rgba(255,255,255,.16); }
.ia-band--ink .ia-tier-bar { background: rgba(255,255,255,.16); }
.ia-band--ink .ia-tier-bar::before { background: var(--rail); }

@media (max-width: 560px) {
    .ia-tier { grid-template-columns: 5rem minmax(0, 1fr); }
    .ia-tier-price { grid-column: 2; }
}

/* ---- Legal pages (design 2026-08-12) ----
   A German visitor reads the Impressum before deciding whether a company is real, so these pages
   are worth typographic care even though they carry no components. .ia-legal itself carries the
   measure, the counter-reset and the steel body colour (site.css:194) — every <h2> counted below
   must be a descendant of that same element, not a sibling of some inner wrapper, or the counter
   restarts at 1 instead of continuing the page's numbering.

   Heading colour is set back to --ink here: .ia-legal's own steel is meant for body copy, and a
   numbered heading in the same grey as the paragraph under it would stop reading as a heading. Only
   the counter glyph itself — the part described as "in the mono face" — stays steel. */
.ia-legal h2 { font-size: 1.15rem; margin-top: 2.5rem; color: var(--text); counter-increment: ia-legal-section; }
.ia-legal h2::before { content: counter(ia-legal-section) ". "; font-family: var(--font-mono);
    color: var(--steel); font-weight: 500; }

/* A document whose clauses carry their own § — see LegalDocument — numbers itself, and the counter
   above printed a second number in front of it: "1. §1 Geltungsbereich". The § stays in the heading
   text rather than being drawn here, because it is what a reader copies, cites and searches for, and
   ::before content is none of those. */
.ia-legal--sections h2::before { content: none; }
.ia-legal--sections h2 { display: flex; gap: .5rem; }
/* Set like the counter it replaces, so the two documents that have sections and the one that is
   numbered by CSS still read as the same family. */
.ia-legal-number { font-family: var(--font-mono); color: var(--steel); font-weight: 500; }

.ia-legal strong { color: var(--text); }

/* Table of contents beside the text on a wide screen, after it on a narrow one — never above.
   Between the heading and the actual document, a list of jump links would push the legal text below
   the fold on a phone, which is the one thing a visitor opened the page to read. The content div
   comes first in markup for exactly that reason (plain block flow below 900px puts it before the
   nav); the grid below re-places both into their wide-screen positions by explicit grid-column,
   regardless of that source order.

   top: var(--header-height), the same token and the same reason .ia-pricelist th (above) uses it:
   .ia-header is sticky above 900px, and a bare top:0 would stick the ToC directly behind it. */
@media (min-width: 900px) {
    .ia-legal-wrap { display: grid; grid-template-columns: 15rem minmax(0, 1fr); gap: 3rem;
        align-items: start; }
    .ia-legal-wrap .ia-legal { grid-column: 2; grid-row: 1; }
    .ia-legal-wrap .ia-legal-toc { grid-column: 1; grid-row: 1; position: sticky;
        top: var(--header-height); font-size: .88rem; }
}
.ia-legal-toc ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }
/* #ia-legal-toc, not .ia-legal-toc (code review, 2026-08-12): .ia-main a:not(.ia-btn-secondary):
   not(.ia-pager-link):not(.ia-brand):not(.ia-card-link):not(.ia-chip) is specificity (0,6,1) — each
   :not() counts the specificity of its own argument, so that selector is one class for .ia-main plus
   one more for each of its exclusions, not one class total. A single extra class here, however
   scoped (.ia-main .ia-legal-toc a is only (0,2,1)), still loses to it, and the ToC's <a> carries
   none of the exclusion classes, so the rule matched, silently forced --ink at rest, and made
   the :hover rule below inert (it also asks for --ink, so hovering changed nothing to look at). An id
   is the fix rather than piling on more classes to outweigh it: any id already outranks any number of
   classes, so this stays correct even if a fifth exclusion is ever added to that selector, where a
   repeated-class count tuned to today's four would not. */
#ia-legal-toc a { color: var(--steel); text-decoration: none; }
#ia-legal-toc a:hover { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }

/* Admin launch cards keep the operating dashboard useful even while a new company has a long
   onboarding checklist. The summary remains visible; the remediation list is available on demand. */
.admin-readiness-card { height: 100%; }
.admin-readiness-details { margin-top: .75rem; }
.admin-readiness-details > summary { cursor: pointer; color: var(--mud-palette-primary);
    font-size: .875rem; font-weight: 600; list-style-position: outside; }
.admin-readiness-details > summary:hover { text-decoration: underline; text-underline-offset: 3px; }
.admin-readiness-details[open] > summary { margin-bottom: .25rem; }
.admin-readiness-details .mud-list { max-height: 24rem; overflow-y: auto; }
.admin-empty-state { width: 100%; min-height: 15rem; padding: 2.5rem 1rem; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .5rem; text-align: center; color: var(--mud-palette-text-secondary); }
.admin-workflow-card { max-width: 42rem; border-color: color-mix(in srgb, var(--mud-palette-primary) 28%, transparent); }
.admin-offer-reference { display: inline-flex; align-items: center; gap: .25rem; white-space: nowrap; }
.admin-offer-reference .mud-icon-root { flex: 0 0 auto; }
@media (max-width: 600px) {
    .admin-kpi-value { white-space: nowrap; font-size: 1.6rem; }
}

.ia-col-photo {
    width: 76px;
}

.ia-pricelist-photo {
    display: block;
    width: 64px;
    height: 48px;
    object-fit: contain;
    border-radius: 6px;
    background: var(--paper, #fff);
}

.ia-pricelist-photo--placeholder {
    display: grid;
    place-items: center;
    border: 1px solid var(--rail);
    background: color-mix(in srgb, var(--paper) 92%, var(--steel));
    color: var(--steel);
    font-family: var(--font-mono);
    font-size: .72rem;
    letter-spacing: .08em;
}

@media (max-width: 600px) {
    .ia-col-photo {
        width: 52px;
        padding-right: .25rem !important;
    }

    .ia-pricelist-photo {
        width: 44px;
        height: 44px;
    }
}
