/*
 * ispweb.css — additions on top of the compiled design stylesheet (site.css).
 *
 * site.css is the original build output and is left untouched, so the site
 * still looks exactly as it did. Everything here is either:
 *   - styling for markup the block system introduced (pricing cards, the
 *     for-sale list, the myip definition list, status severity)
 *   - accessibility and responsive fixes that the original layout lacked
 *
 * Keeping the two files apart means the design build can be regenerated from
 * resources/scss without losing any of this.
 */

/* --------------------------------------------------------------------------
   Tokens, mirrored from resources/scss/site/variables/_colors.scss
   -------------------------------------------------------------------------- */

:root {
    --ispweb-primary: #02437d;
    --ispweb-secondary: #0078be;
    --ispweb-tertiary: #a0c92d;
    --ispweb-light-blue: #f4fbff;
    --ispweb-font: #131313;
    --ispweb-muted: #5b6470;
    --ispweb-warning: #b8860b;
    --ispweb-danger: #c0392b;
    --ispweb-radius: 4px;
}

/* --------------------------------------------------------------------------
   Accessibility
   -------------------------------------------------------------------------- */

/* Visible only to screen readers. Bootstrap ships this, but site.css predates
   the utility and several blocks depend on it. */
.visually-hidden {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

/* The original markup used <div> for the hamburger, so it could not be reached
   by keyboard. It is a <button> now; this strips the default chrome. */
button.btn-mobile-menu {
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
}

/* A focus ring that is actually visible against the dark blue. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
    outline: 3px solid var(--ispweb-tertiary);
    outline-offset: 2px;
    border-radius: 2px;
}

.footer-section a:focus-visible,
.copyright-section a:focus-visible,
.bg-primary a:focus-visible {
    outline-color: #fff;
}

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

/* --------------------------------------------------------------------------
   Text-with-image section
   -------------------------------------------------------------------------- */

/*
 * Room for the two blue decorations.
 *
 * .about-section .bg-block::after sits at top:-70px with height:140%, and
 * .about-section figure::after at bottom:-4rem — both deliberately reach past
 * the article. The bg-block overhang is 0.4 × article height − 70px, so it
 * grows with the column width; hence a larger reserve at the wider breakpoints.
 *
 * The original page made this room with an empty spacer section between this
 * one and the next — markup that looked dead but was carrying the layout.
 * Removing it let the decoration run over whatever followed.
 *
 * Reserving the space on the section itself keeps the block self-contained, so
 * it can be placed anywhere in a page without bleeding into the next one.
 *
 * Below 992px the bg-block decoration is display:none and only the smaller
 * figure overhang remains.
 */
@media (min-width: 992px) {
    .about-section {
        padding-bottom: 6rem;
    }
}

@media (min-width: 1400px) {
    .about-section {
        padding-bottom: 8rem;
    }
}

/* --------------------------------------------------------------------------
   Product cards
   -------------------------------------------------------------------------- */

/* The original cards were different heights within a row whenever a title
   wrapped. Flex column with the link pushed to the bottom evens them out. */
.product-card {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    border-radius: var(--ispweb-radius);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.product-card .h3 {
    margin: 0.75rem 0 0.5rem;
}

.product-card-link {
    margin-top: auto;
    color: #fff;
    font-weight: 700;
}

.product-card:hover,
.product-card:focus-within {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(2, 67, 125, 0.22);
}

.product-index {
    padding-left: 1.1rem;
}

.product-index li {
    margin-bottom: 0.35rem;
}

/* --------------------------------------------------------------------------
   Pricing cards
   -------------------------------------------------------------------------- */

.pricing-card {
    border-radius: var(--ispweb-radius);
    /* The banded bar, as on the product and news cards. Declared here rather
       than beside them because the accent tokens are defined further down and
       a custom property resolves at use, not at parse. */
    position: relative;
    overflow: hidden;
}

.pricing-card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 4px;
    background: var(--ispweb-accent-bar);
}

.pricing-card .title {
    font-size: 1.15rem;
}

.pricing-features {
    margin: 0.9rem 0 1rem;
    padding-left: 1.1rem;
}

.pricing-features li {
    margin-bottom: 0.3rem;
}

.pricing-price {
    padding-top: 0.75rem;
    border-top: 1px solid rgba(255, 255, 255, 0.25);
}

.pricing-price .price {
    font-size: 1.35rem;
    font-weight: 700;
}

/* --------------------------------------------------------------------------
   Status
   -------------------------------------------------------------------------- */

.status-line {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.75rem;
    font-weight: 700;
}

.status-line::before {
    content: "";
    width: 0.7rem;
    height: 0.7rem;
    border-radius: 50%;
    flex: 0 0 auto;
}

/* Severity: ok, then low / medium / high. */
.status-line-ok::before { background: var(--ispweb-tertiary); }
.status-line-low::before { background: var(--ispweb-accent-yellow); }
.status-line-medium::before { background: var(--ispweb-warning); }
.status-line-high::before { background: var(--ispweb-danger); }

.status-line-low { color: var(--ispweb-warning); }
.status-line-medium { color: var(--ispweb-warning); }
.status-line-high { color: var(--ispweb-danger); }

/* --------------------------------------------------------------------------
   Domain check
   -------------------------------------------------------------------------- */

/* Replaces the inline styles the original put on the readonly WWW. input. */
.domain-check-prefix {
    display: flex;
    align-items: center;
    background: var(--ispweb-primary);
    color: #f5f5f5;
    border: 0;
    font-weight: 700;
}

.domain-check-form .input-group {
    flex-wrap: nowrap;
}

/* Below the small breakpoint the three-part input group does not fit on one
   line; stack it rather than letting the field shrink to nothing. */
@media (max-width: 575.98px) {
    .domain-check-form .input-group {
        flex-wrap: wrap;
    }

    .domain-check-form .domain-check-prefix {
        display: none;
    }

    .domain-check-form .form-control,
    .domain-check-form .btn {
        flex: 1 1 100%;
        border-radius: var(--ispweb-radius) !important;
    }

    .domain-check-form .btn {
        margin-top: 0.5rem !important;
    }
}

/* --------------------------------------------------------------------------
   MyIP
   -------------------------------------------------------------------------- */

.myip-details {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.4rem 1.25rem;
    margin: 0;
}

.myip-details dt {
    font-weight: 700;
    color: var(--ispweb-primary);
}

.myip-details dd {
    margin: 0;
    word-break: break-word;
}

/* User agents are long and arbitrary; never let one widen the page. */
.myip-agent {
    overflow-wrap: anywhere;
    font-size: 0.9rem;
    color: var(--ispweb-muted);
}

@media (max-width: 575.98px) {
    .myip-details {
        grid-template-columns: 1fr;
        gap: 0 0;
    }

    .myip-details dd {
        margin-bottom: 0.75rem;
    }
}

/* --------------------------------------------------------------------------
   For-sale domain list
   -------------------------------------------------------------------------- */

/* The original built two columns with PHP and a hard-coded row count, which
   broke whenever the list length changed. CSS columns handle any length. */
.forsale-list {
    columns: 2;
    column-gap: 2rem;
    padding: 0;
    margin: 0;
    list-style: none;
}

.forsale-list li {
    break-inside: avoid;
    margin-bottom: 0.3rem;
}

.forsale-list a {
    text-decoration: none;
    transition: color 0.12s ease;
}

.forsale-list a:hover,
.forsale-list a:focus {
    color: var(--ispweb-tertiary);
}

@media (max-width: 575.98px) {
    .forsale-list {
        columns: 1;
    }
}

/* The original pulled this image up 16rem with a negative margin, which put it
   behind the text on narrow desktops. */
.forsale-image {
    opacity: 0.3;
    pointer-events: none;
}

/* --------------------------------------------------------------------------
   Partner banner
   --------------------------------------------------------------------------
   A continuous marquee, in CSS alone. The logo list is emitted twice and the
   strip is moved by exactly half its width, so the end meets the beginning.

   The spacing is a right margin on each logo, not `gap` on the track, and that
   detail is the whole trick. With `gap`, N logos duplicated to 2N produce
   2N-1 gaps, so half the track width is half a gap short of one real cycle --
   the strip jumps back a fraction every lap, which reads as a stutter and can
   leave the last logo never fully in view. A margin makes each logo a
   self-contained unit of (logo + margin), so N units are exactly half of 2N and
   -50% lands precisely on the copy.

   No JavaScript is involved: the Splide carousel this replaced left an empty
   band whenever its bundle failed to mount.
   -------------------------------------------------------------------------- */

.partner-band {
    --partner-gap: clamp(2rem, 6vw, 5.5rem);

    /* Bleeds past the container so logos enter and leave off-screen rather
       than appearing at a visible edge. */
    margin-inline: calc(50% - 50vw);
    padding-inline: calc(50vw - 50%);
    overflow: hidden;
}

.partner-track {
    display: flex;
    align-items: center;
    margin: 0;
    padding: 0;
    list-style: none;
    justify-content: center;
    flex-wrap: wrap;
}

.partner-item {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    margin-right: var(--partner-gap);
}

.partner-item img {
    display: block;
    /* A definite height, not just a maximum: an SVG whose intrinsic size cannot
       be read has no ratio to scale from, and width:auto against a bare
       max-height collapses it to nothing. Sizing from the height gives every
       logo something to work with, whatever the format. */
    height: clamp(2rem, 4vw, 3.25rem);
    width: auto;
    max-width: clamp(7rem, 12vw, 11rem);
    object-fit: contain;
}

/* Scrolling variant ------------------------------------------------------- */

.partner-band.is-scrolling .partner-track {
    /* One long line: no wrapping, no centring, or it cannot slide. */
    flex-wrap: nowrap;
    justify-content: flex-start;
    width: max-content;
    animation: partner-marquee var(--partner-speed, 40s) linear infinite;
    /* Its own layer, so the browser does not repaint the section every frame. */
    will-change: transform;
}

@keyframes partner-marquee {
    from { transform: translate3d(0, 0, 0); }
    /* Half the strip is the duplicate, so this lands exactly on the copy. */
    to   { transform: translate3d(-50%, 0, 0); }
}

/* Standing still, the trailing margin on the last logo would push the row off
   centre; take it back. */
.partner-band:not(.is-scrolling) .partner-track {
    margin-right: calc(-1 * var(--partner-gap));
}

/* Motion is decoration here; without it the band simply stands still. */
@media (prefers-reduced-motion: reduce) {
    .partner-band.is-scrolling .partner-track {
        animation: none;
        flex-wrap: wrap;
        justify-content: center;
        width: auto;
        margin-right: calc(-1 * var(--partner-gap));
    }

    .partner-band.is-scrolling .partner-item[aria-hidden="true"] {
        display: none;
    }
}
/* --------------------------------------------------------------------------
   News
   -------------------------------------------------------------------------- */

.news-empty {
    color: var(--ispweb-muted);
}

/*
 * Fallback for a carousel that never got initialised.
 *
 * Splide adds `is-initialized` when it mounts. Until then .splide__track is
 * overflow:hidden around a flex list whose slides have no width, so the whole
 * thing collapses to nothing -- which is exactly how the partner logos
 * disappeared without a trace. This keeps an unmounted carousel usable as a
 * plain horizontally scrollable row instead of an empty gap.
 */
.splide:not(.is-initialized) .splide__track {
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
}

.splide:not(.is-initialized) .splide__list {
    display: flex;
    gap: 1.5rem;
}

.splide:not(.is-initialized) .splide__slide {
    flex: 0 0 auto;
    width: min(300px, 80vw);
    scroll-snap-align: start;
}

/* The arrows do nothing until Splide wires them up. */
.splide:not(.is-initialized) .splide__arrows {
    display: none;
}

/* Fixes the fixed 300px inline sizing the original put on every thumbnail. */
.news-thumb img {
    width: 100%;
    max-width: 300px;
    height: auto;
    aspect-ratio: 1 / 1;
    object-fit: cover;
}

.news-section .date {
    color: var(--ispweb-muted);
    font-size: 0.9rem;
}

/* --------------------------------------------------------------------------
   Long-form content pages
   -------------------------------------------------------------------------- */

/* The policy and product pages are a wall of text; these give them a readable
   measure and consistent rhythm without touching the rest of the design. */
/* The body sits in its own .block-body wrapper -- that is what carries the
   optional newspaper columns -- so the rhythm rule has to reach inside it as
   well as apply to the article itself. */
.longform > * + *,
.longform .block-body > * + * {
    margin-top: 0.9rem;
}

.longform h6 {
    margin-top: 1.6rem;
    color: var(--ispweb-primary);
    font-weight: 700;
}

.longform ul,
.longform ol {
    padding-left: 1.25rem;
}

.longform li + li {
    margin-top: 0.4rem;
}

.longform.col-12 {
    max-width: 72ch;
}

/* The policy pages are a numbered list of sections: the list item's own text is
   the section title and everything nested inside it is that section. Left at
   the default the titles read as smaller than the bold leads underneath them,
   which put the visual hierarchy upside down. */
.longform ol > li {
    margin-top: 1.75rem;
    color: var(--ispweb-primary);
    font-weight: 700;
    font-size: 1.0625rem;
}

.longform ol > li:first-child {
    margin-top: 1rem;
}

/* Back to body text for the section's own content. */
.longform ol > li > p,
.longform ol > li > ul,
.longform ol > li > ol {
    color: var(--ispweb-font);
    font-weight: 400;
    font-size: 1rem;
}

.longform ol > li > ul {
    margin-top: 0.6rem;
}

/* Long domain names and URLs must not push the layout sideways. */
.longform a {
    overflow-wrap: anywhere;
}

/* The list of downloadable terms. Seventeen PDF file names in a single column
   is a wall; they are short and independent, so let them flow into as many
   columns as the width allows. */
.longform .download-list {
    margin-top: 0.75rem;
    padding-left: 0;
    list-style: none;
    /* Wide enough for the longest file name to stay on one line. Narrower
       columns fit more per row but split names mid-word, and a broken
       "…programmatuur.p / df" is harder to read than a shorter list. */
    columns: 30rem auto;
    column-gap: 2.5rem;
}

.longform .download-list li {
    margin: 0 0 0.35rem;
    break-inside: avoid;
}

.longform .download-list a {
    display: inline-block;
    padding-left: 1.4rem;
    position: relative;
}

/* A document mark in place of the old literal ■ character. */
.longform .download-list a::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.15em;
    width: 0.75em;
    height: 0.95em;
    border: 1px solid currentColor;
    border-radius: 1px;
}

/* --------------------------------------------------------------------------
   Global responsive guards
   -------------------------------------------------------------------------- */

/*
 * Pulls the illustration on a content page up beside the heading, the way the
 * homepage hero sits.
 *
 * The original declared this in an inline <style> block on every product page.
 * Removing inline styles took the declaration with it, so the class stayed in
 * the markup while meaning nothing -- and the image dropped to the bottom of
 * the text instead of sitting alongside it.
 *
 * Desktop only: below 1200px there is one column and pulling up would drag the
 * image over the text.
 */
@media (min-width: 1200px) {
    .mt-xl-n16 {
        margin-top: -16rem !important;
    }
}

@media (max-width: 1199.98px) {
    .mt-xl-n16 {
        margin-top: 0 !important;
    }
}

img {
    max-width: 100%;
    height: auto;
}

/* Nothing may cause a horizontal scrollbar. */
html,
body {
    overflow-x: hidden;
}

/* Give the sticky header something to scroll to when following an anchor. */
:target {
    scroll-margin-top: 6rem;
}

/* --------------------------------------------------------------------------
   Page layout: content with an illustration beside it
   --------------------------------------------------------------------------
   Used by product pages: the blocks flow down the page and a single tall image
   sits beside them.

   The illustration is floated rather than placed in a grid column, so a block
   sits beside it while there is room and takes the whole width once it has
   ended. A column would have been as tall as the row, keeping every block at
   half width for the whole page and leaving a large empty area beside the ones
   that start below the artwork.

   For that to work each block has to be its own block formatting context:
   ordinary block boxes slide *under* a float and only their text wraps around
   it, which would put a card grid behind the illustration. A flow-root box is
   placed beside the float instead, narrowed to the space left over, and moves
   below it when too little remains.

   The section reuses the homepage hero's classes -- .intro-section and
   .image-right -- so the artwork's size and position come from site.css and
   match the homepage by construction rather than by a second set of numbers
   kept in step by hand. What is left below is only what differs.
   -------------------------------------------------------------------------- */

@media (min-width: 992px) {
    .page-aside-figure {
        float: right;
        width: 50%;
        text-align: right;
    }

    .page-aside-figure.is-left {
        float: left;
        text-align: left;
    }

    /* Everything except plain prose. A flow-root box is placed beside the float
       and stays that narrow for its whole height; prose is left as an ordinary
       block so its lines wrap around the illustration and widen out underneath
       it, which is how the policy pages read. */
    .page-aside .block-part:not(.is-prose) {
        display: flow-root;
    }

    /* A list's markers sit outside its content box, which for a wrapping block
       means they can end up underneath the illustration. Moving them inside
       keeps every number and bullet visible beside it. */
    .page-aside .block-part.is-prose ul,
    .page-aside .block-part.is-prose ol {
        list-style-position: inside;
        padding-left: 0;
    }

    .page-aside .block-part.is-prose li > ul,
    .page-aside .block-part.is-prose li > ol {
        padding-left: 1.25rem;
    }

    /* "Volle breedte": always start below the illustration. */
    .page-aside .block-part.is-full {
        clear: both;
    }
}

/* On a phone there are no two columns. The float is declared first because a
   float has to be, but the illustration belongs under the opening text there,
   the way the hero's does -- so the container becomes a flex column and the
   figure is ordered between the first block and the rest. Ordering it simply
   last would drop the artwork below the whole page. */
@media (max-width: 991.98px) {
    .page-aside > .container {
        display: flex;
        flex-direction: column;
    }

    /* The first selector has to outweigh the catch-all below it, or the two tie
       on specificity and the later rule sends every block to order 3 -- which
       put the illustration above the heading. */
    .page-aside .page-aside-figure + .block-part {
        order: 1;
    }

    .page-aside .page-aside-figure {
        order: 2;
    }

    .page-aside .block-part {
        order: 3;
    }
}

/* Where the heading starts.

   The homepage has no number for this: its heading lands wherever centring puts
   it, which is half the illustration's height minus half the text block's. The
   illustration scales with the viewport, so the position slides continuously --
   measured on the homepage: 221px at 1024 wide, 271 at 1200, 168 at 1320, 219
   at 1440, 264 at 1600, 355 at 1920, 372 at 2200 (there the artwork hits its
   natural 1101px and stops growing).

   A product page's content is several screens long, so it cannot be centred.
   These reproduce that curve instead, one segment per band of site.css's own
   .col-text offset. Verified against the homepage at each width above; worst
   case is 12px out, most within 10px. It sits on the first block, the one that
   starts level with the illustration. */
@media (min-width: 992px) and (max-width: 1300px) {
    .page-aside .page-aside-figure + .block-part {
        padding-top: calc(33.2vw - 110px);
    }
}

@media (min-width: 1301px) and (max-width: 1420px) {
    .page-aside .page-aside-figure + .block-part {
        padding-top: calc(27.5vw - 186px);
    }
}

@media (min-width: 1421px) {
    /* The clamp matches the artwork: it stops growing at its natural height,
       and the lower bound keeps the heading clear of the fixed header. */
    .page-aside .page-aside-figure + .block-part {
        padding-top: clamp(200px, calc(27.6vw - 169px), 372px);
    }
}

/* A page with no illustration has nothing to line up with, but still has to
   clear the fixed header. */
.page-aside.has-no-figure > .container > .block-part:first-child {
    padding-top: 7rem;
}

@media (min-width: 992px) {
    .page-aside.has-no-figure > .container > .block-part:first-child {
        padding-top: 11rem;
    }
}

/* No position:sticky on the illustration.
   It looked like a free improvement -- the artwork keeping pace with a long
   column -- but a sticky element with `top: 6rem` is already pushed down by
   those 6rem at scroll position 0, because its natural place is above the
   threshold. Measured: the illustration started 96px lower than the homepage's,
   which is precisely that 6rem. It starts flush with the top of the section,
   like the hero's. */

/* .col-text gets a large top offset from site.css when it sits in an
   .intro-section -- correct once, for the column at the top of the section.
   A block nested inside that column keeps the class for its typography but must
   not repeat the offset, or the heading is pushed down by it twice. */
.page-aside .block-part .col-text,
.page-aside .block-part.col-text {
    padding-top: 0;
}

/* Text in newspaper columns. Only from lg: on a phone there is no width to
   divide, and a two-column list there would be unreadable. */
@media (min-width: 992px) {
    .block-columns-2 {
        columns: 2;
    }

    .block-columns-3 {
        columns: 3;
    }

    .block-columns {
        column-gap: 3rem;
    }

    /* A list must not be cut in half across the break... */
    .block-columns > * {
        break-inside: avoid;
    }

    /* ...and a heading must not be left behind at the foot of one column while
       the list it introduces starts in the next. break-inside cannot prevent
       that: the heading and the list are separate elements, so the break falls
       between them. break-after moves the heading along with its list. */
    .block-columns h1,
    .block-columns h2,
    .block-columns h3,
    .block-columns h4,
    .block-columns h5,
    .block-columns h6 {
        break-after: avoid;
    }

    .block-columns > :first-child {
        margin-top: 0;
    }
}

/* An image inside a column block sits under its text at the column's width;
   the bleeding .image-right treatment is only for the illustration beside the
   whole page. */
.page-aside .block-figure img {
    max-width: 100%;
    height: auto;
}

/* Blocks inside the column: no band, no container, just stacked parts. */
.page-aside .block-part + .block-part {
    margin-top: 1.5rem;
}

/* The first block sets the page heading; it needs no extra lead-in. */
.page-aside .block-part:first-child {
    margin-top: 0;
}
/* --------------------------------------------------------------------------
   Block spacing
   --------------------------------------------------------------------------
   Set per block in the portal, so an editor can close a gap between two blocks
   without anyone touching a stylesheet.
   -------------------------------------------------------------------------- */

/*
 * These sit on the block's container, which is the element the design's own
 * `my-6` / `pt-6` utilities are on. They therefore have to be at least as
 * loud as those utilities -- Bootstrap's carry !important, so a plain
 * declaration here would simply lose and the setting would appear to do
 * nothing.
 *
 * `normal` deliberately has no rule at all: it means "leave the design's own
 * rhythm alone", and it is the default on every block, so anything declared
 * for it would change every page on the site.
 */
.space-top-none    { margin-top: 0 !important; padding-top: 0 !important; }
.space-top-small   { margin-top: 1.5rem !important; padding-top: 0 !important; }
.space-top-large   { margin-top: 7rem !important; }

.space-bottom-none  { margin-bottom: 0 !important; padding-bottom: 0 !important; }
.space-bottom-small { margin-bottom: 1.5rem !important; padding-bottom: 0 !important; }
.space-bottom-large { margin-bottom: 7rem !important; }

/*
 * The block's own first row carries `mt-6` as well, to sit clear of the header
 * when it is the opening band of a page. With the container's spacing zeroed
 * that margin collapses straight out of the section and reappears as the gap
 * the setting was supposed to remove -- so it goes too.
 */
.space-top-none > .row:first-child,
.space-top-small > .row:first-child {
    margin-top: 0 !important;
}

.space-bottom-none > .row:last-child,
.space-bottom-small > .row:last-child {
    margin-bottom: 0 !important;
}

/* Inside a column the same choices control the stack rhythm. */
.block-part.space-top-none    { margin-top: 0 !important; }
.block-part.space-top-small   { margin-top: .5rem; }
.block-part.space-top-large   { margin-top: 3rem; }

.block-part.space-bottom-none  { margin-bottom: 0 !important; }
.block-part.space-bottom-small { margin-bottom: .5rem; }
.block-part.space-bottom-large { margin-bottom: 3rem; }

/* ==========================================================================
   Accents
   ==========================================================================
   The house style is one blue with a green button, which over a long page
   reads as a wall of the same colour. The logo has always carried a red, a
   yellow and a green next to that blue; security.ispweb.nl puts them to work
   as a banded accent line, and this is the same set, used the same way.

   The green here is the site's own #A0C92D rather than the security site's
   #A1CE16 -- they are a shade apart, and matching the button colour already on
   the page beats matching another site's stylesheet.
   -------------------------------------------------------------------------- */

:root {
    --ispweb-accent-red: #e5051f;
    --ispweb-accent-yellow: #eed500;
    --ispweb-accent-green: #a0c92d;
    --ispweb-accent-blue: #0078be;

    /* Four equal bands, no blending: a gradient with coincident stops. A soft
       fade between them turns to mud at four pixels tall. */
    --ispweb-accent-bar: linear-gradient(
        90deg,
        var(--ispweb-accent-red) 0 25%,
        var(--ispweb-accent-yellow) 25% 50%,
        var(--ispweb-accent-green) 50% 75%,
        var(--ispweb-accent-blue) 75% 100%
    );
}

/* The banded line itself, available to any block that wants to close a section
   off, and used below on the footer and under the header. */
.accent-bar {
    display: block;
    height: 5px;
    background: var(--ispweb-accent-bar);
}

/*
 * Two footers, one sticky-footer rule.
 *
 * site.css has `body footer { margin-top: auto !important }`, written for a
 * layout with a single <footer>: in a flex column an auto top margin eats the
 * free space and pushes the footer to the bottom of a short page.
 *
 * This site emits two footers -- the contact block and the copyright bar -- and
 * an auto margin on *each* means the free space is divided between them. That
 * is where the two identical bands of white came from: one above the contact
 * block and one between it and the copyright bar, growing on exactly the pages
 * with the least content.
 *
 * Only the first footer should push. The second sits directly under it, so the
 * pair travels to the bottom as one block.
 */
.site-wrapper > footer + footer {
    margin-top: 0 !important;
}

/* The footer opens with it, so every page ends on the full palette. */
.footer-section {
    position: relative;
}

.footer-section::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 5px;
    background: var(--ispweb-accent-bar);
}

/*
 * And the header closes with a thinner one -- but only once it has shrunk.
 *
 * In its large state the header is the logo on white with nothing under it, and
 * a full-width band of colour there competes with the logo's own coloured
 * blocks and reads as a second, heavier rule. Once the page is scrolled the
 * header becomes a compact white bar over the content, and there the line is
 * doing a job: it separates the bar from whatever is passing underneath it.
 *
 * `body.scroll` is set by ispweb.js from the scroll position, and is the same
 * class site.css already uses to shrink the header. The line fades rather than
 * appears, so it arrives with the shrink instead of blinking in.
 */
.header-section::after {
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    height: 3px;
    background: var(--ispweb-accent-bar);
    opacity: 0;
    transition: opacity 0.2s ease;
}

body.scroll .header-section::after {
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .header-section::after {
        transition: none;
    }
}

/* --------------------------------------------------------------------------
   Section headings
   -------------------------------------------------------------------------- */

/* A short banded rule under the heading of each homepage band. Scoped to those
   sections on purpose: give every <h2> one and it stops meaning "new section"
   and starts meaning nothing. */
.products-section h2,
.news-section h2,
.status-section h2,
.domain-section h2,
.partners-section h2 {
    position: relative;
    padding-bottom: 0.6rem;
}

.products-section h2::after,
.news-section h2::after,
.status-section h2::after,
.domain-section h2::after,
.partners-section h2::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 72px;
    height: 4px;
    border-radius: 2px;
    background: var(--ispweb-accent-bar);
}

/* --------------------------------------------------------------------------
   Product cards
   -------------------------------------------------------------------------- */

/* Every card carries the whole banded bar, the same one as the footer.
   Giving each card the next single colour instead made the grid look like a
   sequence -- as though the cards were ranked, or as though the colour meant
   something per product. It does not: the four together are the house style,
   and repeating the full set says "ISPWeb" on every card rather than "this is
   the red one". */
.product-card {
    position: relative;
    overflow: hidden;
}

.product-card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 4px;
    background: var(--ispweb-accent-bar);
}

/* --------------------------------------------------------------------------
   Incident accordion
   --------------------------------------------------------------------------
   One incident open at a time, newest first, resolved ones behind their own
   fold. Built on <details name="…">, so the browser closes the siblings and
   there is no JavaScript involved — it works with scripting off, and it is
   keyboard- and screen-reader-accessible without any wiring.

   Tabs were considered and rejected: incident titles are sentences rather than
   labels, the number of them changes by the hour, and a tab strip is the first
   thing to break on a phone.
   -------------------------------------------------------------------------- */

.incident-list {
    display: grid;
    gap: 0.6rem;
    margin: 1.25rem 0 0;
    padding: 0;
    list-style: none;
}

.incident {
    position: relative;
    overflow: hidden;
    background: #fff;
    border: 1px solid rgba(2, 67, 125, 0.12);
    border-radius: var(--ispweb-radius);
    box-shadow: 0 2px 10px rgba(2, 67, 125, 0.06);
}

/* The severity stripe. A left edge rather than a full border, so the panel
   still reads as one object while the colour stays unmissable. */
.incident::before {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 5px;
    background: var(--ispweb-accent-yellow);
    z-index: 1;
}

.incident-high::before   { background: var(--ispweb-accent-red); }
.incident-medium::before { background: var(--ispweb-warning); }
.incident-low::before    { background: var(--ispweb-accent-yellow); }
.incident-resolved::before { background: var(--ispweb-accent-green); }

/* -- the row you click ----------------------------------------------------- */

.incident-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
    padding: 0.85rem 2.5rem 0.85rem 1.35rem;
    cursor: pointer;
    /* Both are needed to drop the default disclosure triangle: the standard
       property and the WebKit pseudo-element that predates it. */
    list-style: none;
}

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

.incident-summary:hover {
    background: var(--ispweb-light-blue);
}

.incident-summary:focus-visible {
    outline: 3px solid var(--ispweb-tertiary);
    outline-offset: -3px;
}

/* The chevron, drawn rather than an image so it can turn with the panel. */
.incident-summary::after {
    content: "";
    position: absolute;
    top: 1.35rem;
    right: 1.15rem;
    width: 0.5rem;
    height: 0.5rem;
    border-right: 2px solid var(--ispweb-secondary);
    border-bottom: 2px solid var(--ispweb-secondary);
    transform: rotate(45deg);
    transform-origin: center;
    transition: transform 0.15s ease;
}

.incident[open] > .incident-summary::after {
    transform: rotate(-135deg);
}

.incident-title {
    margin: 0;
    flex: 1 1 12rem;
    min-width: 0;
    font-size: 1rem;
    line-height: 1.35;
    color: var(--ispweb-primary);
}

/* Pushed to the right of the title, and the first thing to wrap away when the
   title needs the room. */
.incident-when {
    flex: none;
    font-size: 0.82rem;
    color: var(--ispweb-muted);
    font-variant-numeric: tabular-nums;
}

.incident-badge {
    flex: none;
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #fff;
    background: var(--ispweb-muted);
    white-space: nowrap;
}

/* Yellow needs dark text: white on #eed500 is about 1.7:1. */
.incident-low .incident-badge {
    color: #3a3000;
    background: var(--ispweb-accent-yellow);
}

.incident-medium .incident-badge {
    background: var(--ispweb-warning);
}

.incident-high .incident-badge {
    background: var(--ispweb-accent-red);
}

.incident-resolved .incident-badge {
    color: #1d2a00;
    background: var(--ispweb-accent-green);
}

/* -- what unfolds ---------------------------------------------------------- */

.incident-detail {
    padding: 0 1.35rem 1.1rem;
    border-top: 1px solid rgba(2, 67, 125, 0.08);
    margin-top: -1px;
    padding-top: 0.9rem;
}

/* The full severity wording, which the collapsed row shortens to one word. */
/*
 * Urgency, impact and phase, side by side.
 *
 * Three short facts rather than one word: they are separate questions and a
 * visitor reads them as a sentence -- "hoog · merkbare hinder · wordt opgelost".
 * The separator is a border on the left of each following item, so it never
 * strands on its own when the row wraps on a phone.
 */
.incident-severity {
    margin: 0 0 0.5rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 0.6rem;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--ispweb-muted);
}

.incident-fact + .incident-fact {
    padding-left: 0.6rem;
    border-left: 1px solid rgba(148, 163, 184, 0.45);
}

.incident-fact-level {
    color: var(--ispweb-font);
}

.incident-fact-phase {
    font-weight: 600;
}

.incident-services {
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.incident-service {
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    font-size: 0.8rem;
    color: var(--ispweb-primary);
    background: var(--ispweb-light-blue);
    border: 1px solid rgba(2, 67, 125, 0.14);
}

.incident-times {
    margin: 0.55rem 0 0;
    font-size: 0.85rem;
    color: var(--ispweb-muted);
}

.incident-body {
    margin-top: 0.6rem;
    font-size: 0.94rem;
}

.incident-body > :last-child {
    margin-bottom: 0;
}

/* -- resolved, behind its own fold ----------------------------------------- */

.incident-archive {
    margin-top: 0.9rem;
}

.incident-archive > summary {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem 0.2rem;
    cursor: pointer;
    list-style: none;
    font-size: 0.88rem;
    font-weight: 700;
    color: var(--ispweb-muted);
}

.incident-archive > summary::-webkit-details-marker {
    display: none;
}

.incident-archive > summary:hover {
    color: var(--ispweb-secondary);
}

.incident-archive > summary::before {
    content: "";
    width: 0.45rem;
    height: 0.45rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
    transition: transform 0.15s ease;
}

.incident-archive[open] > summary::before {
    transform: rotate(-135deg);
}

.incident-archive-count {
    padding: 0.05rem 0.45rem;
    border-radius: 999px;
    background: rgba(2, 67, 125, 0.1);
    font-size: 0.78rem;
}

/* History, not news: present but quieter than what is still running. */
.incident-archive .incident {
    box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
    .incident-summary::after,
    .incident-archive > summary::before {
        transition: none;
    }
}

/* --------------------------------------------------------------------------
   News cards
   -------------------------------------------------------------------------- */

/* The cards themselves are styled further down, under "News carousel" -- the
   markup is a scroll-snap track rather than the Splide list this block used to
   emit. What stays here is the empty state, which is not part of the track. */

.news-empty {
    padding: 1.25rem 1.4rem;
    border-left: 4px solid var(--ispweb-accent-yellow);
    background: var(--ispweb-light-blue);
    border-radius: 0 var(--ispweb-radius) var(--ispweb-radius) 0;
    color: var(--ispweb-muted);
}

/* --------------------------------------------------------------------------
   Reduced motion
   -------------------------------------------------------------------------- */

/* The card lift is decoration. Someone who has asked their system to stop
   moving things has asked for this too. */
@media (prefers-reduced-motion: reduce) {
    .product-card {
        transition: none;
    }

    .product-card:hover,
    .product-card:focus-within {
        transform: none;
    }
}

/* --------------------------------------------------------------------------
   News carousel
   --------------------------------------------------------------------------
   A scroll-snap row rather than a mounted slider: it works with no JavaScript
   at all, which matters because the design bundle that used to mount the
   Splide carousel here cannot run under this site's CSP. See the comment at
   the top of src/Render/Blocks/news.php.
   -------------------------------------------------------------------------- */

.news-carousel {
    position: relative;
}

.news-track {
    list-style: none;
    margin: 0;
    padding: 0 0 1rem;
    display: flex;
    gap: 1.5rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    /* Firefox; WebKit gets the ::-webkit-scrollbar rules below. */
    scrollbar-width: thin;
    scrollbar-color: rgba(2, 67, 125, 0.35) transparent;
}

/*
 * Card width.
 *
 * Flexbox rather than grid-auto-columns: a percentage there is measured
 * against the grid's own content box, which on a track that is deliberately
 * wider than its container is not the number wanted -- five cards ended up
 * sharing the visible width instead of three sticking out past it. A flex
 * basis resolves against the container, which is exactly the intent.
 *
 * flex-shrink: 0 is what keeps them from being squeezed back in to fit; the
 * overflow is the point.
 */
.news-item {
    flex: 0 0 min(280px, 78%);
}

/* Two across on a tablet, three on a desktop. On a phone the card stops just
   short of the full width, and that peek of the next one is what tells a
   visitor the row scrolls at all. */
@media (min-width: 768px) {
    .news-item { flex-basis: calc((100% - 1.5rem) / 2); }
}

@media (min-width: 992px) {
    .news-item { flex-basis: calc((100% - 3rem) / 3); }
}

.news-track:focus-visible {
    outline: 3px solid var(--ispweb-tertiary);
    outline-offset: 4px;
}

.news-track::-webkit-scrollbar {
    height: 6px;
}

.news-track::-webkit-scrollbar-thumb {
    background: rgba(2, 67, 125, 0.35);
    border-radius: 3px;
}

/*
 * No banded bar on a news card, unlike the product and pricing cards.
 *
 * Those sit in a grid with nothing above them. A news card sits directly under
 * the section heading, which carries the same banded rule -- so the two landed
 * a few pixels apart and read as one double line that had come out wrong. With
 * a single item published it is worse still: the card is as wide as the column,
 * so the two lines are the same length as well.
 *
 * The heading keeps its rule, because that is what marks the section. The cards
 * below it do not need to say it a second time.
 */
.news-item {
    scroll-snap-align: start;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.news-thumb {
    margin: 0 0 1rem;
    overflow: hidden;
    border-radius: var(--ispweb-radius);
}

.news-thumb img {
    width: 100%;
    height: auto;
    aspect-ratio: 3 / 2;
    object-fit: cover;
}

.news-item-body {
    display: flex;
    flex-direction: column;
    flex: 1;
}

.news-item .date {
    color: var(--ispweb-muted);
    font-size: 0.85rem;
    margin-bottom: 0.25rem;
}

.news-item-title {
    margin-bottom: 0.5rem;
}

/* Pushes every "Lees meer" onto the same baseline, whatever the summary does. */
.news-item-link {
    margin-top: auto;
    font-weight: 700;
}

/* --------------------------------------------------------------------------
   Carousel arrows
   -------------------------------------------------------------------------- */

/* Revealed by ispweb.js, and only when the track actually overflows: two
   arrows that cannot move anything are worse than no arrows. */
.news-controls {
    display: flex;
    gap: 0.5rem;
    justify-content: flex-start;
}

@media (min-width: 992px) {
    .news-controls { justify-content: flex-end; }
}

.news-arrow {
    width: 42px;
    height: 42px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 2px solid var(--ispweb-secondary);
    border-radius: 50%;
    background: transparent;
    color: var(--ispweb-secondary);
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease;
}

.news-arrow svg {
    width: 16px;
    height: 16px;
}

.news-arrow svg path {
    fill: currentColor;
}

.news-arrow:hover,
.news-arrow:focus-visible {
    background: var(--ispweb-secondary);
    color: #fff;
}

/* At either end the arrow is dimmed and unclickable rather than removed, so
   the pair does not shift sideways as you scroll. */
.news-arrow[disabled] {
    opacity: 0.35;
    cursor: default;
}

.news-arrow[disabled]:hover {
    background: transparent;
    color: var(--ispweb-secondary);
}

@media (prefers-reduced-motion: reduce) {
    .news-track {
        scroll-behavior: auto;
    }
}

/* --------------------------------------------------------------------------
   Long-form documents
   --------------------------------------------------------------------------
   The voorwaarden, privacy-, cookie- en disclaimerpagina's. These are pages
   people read to find one specific answer, so the styling is about
   scannability: a clear opening, headings that stand out from the body, and a
   comfortable measure.
   -------------------------------------------------------------------------- */

/* The opening paragraph: the summary in one or two sentences. */
.longform .block-body .lead {
    font-size: 1.12rem;
    line-height: 1.65;
    padding: 1.1rem 1.3rem;
    margin-bottom: 2rem;
    background: var(--ispweb-light-blue);
    border-left: 4px solid var(--ispweb-secondary);
    border-radius: 0 var(--ispweb-radius) var(--ispweb-radius) 0;
}

/* Section headings inside a document. A rule above rather than below, so the
   heading reads as belonging to the text that follows it. */
.longform .block-body h2 {
    margin-top: 2.4rem;
    margin-bottom: 0.75rem;
    padding-top: 1.1rem;
    border-top: 2px solid rgba(2, 67, 125, 0.12);
    font-size: 1.45rem;
}

/* The first heading needs no separator: nothing precedes it but the lead. */
.longform .block-body > h2:first-child {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

.longform .block-body h3 {
    margin-top: 1.6rem;
    margin-bottom: 0.5rem;
    font-size: 1.12rem;
    color: var(--ispweb-primary);
}

.longform .block-body p,
.longform .block-body li {
    /* Long lines are hard to track back from; this caps the measure without
       narrowing a column that is already narrow. */
    max-width: 68ch;
}

.longform .block-body ul,
.longform .block-body ol {
    margin-bottom: 1.1rem;
    padding-left: 1.25rem;
}

.longform .block-body li {
    margin-bottom: 0.45rem;
}

/* Definition lists carry the company details in the privacy statement. */
.longform .block-body dl {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.3rem 1.25rem;
    margin: 1.1rem 0 1.4rem;
    padding: 1rem 1.25rem;
    background: var(--ispweb-light-blue);
    border-radius: var(--ispweb-radius);
    max-width: 68ch;
}

.longform .block-body dt {
    font-weight: 700;
    color: var(--ispweb-primary);
}

.longform .block-body dd {
    margin: 0;
}

@media (max-width: 575.98px) {
    .longform .block-body dl {
        grid-template-columns: 1fr;
        gap: 0 0;
    }

    .longform .block-body dd {
        margin-bottom: 0.6rem;
    }
}

/* The "laatst bijgewerkt" line, which closes every document. */
.longform .block-body .doc-updated {
    margin-top: 2.5rem;
    padding-top: 1rem;
    border-top: 2px solid rgba(2, 67, 125, 0.12);
    font-size: 0.9rem;
    color: var(--ispweb-muted);
}

/* --------------------------------------------------------------------------
   Check another domain
   --------------------------------------------------------------------------
   The search bar repeated under the verdict. Someone who has just looked up one
   name usually has a second one in mind, and the only route back to the bar was
   the homepage.
   -------------------------------------------------------------------------- */

.domain-recheck {
    margin-top: 2.5rem;
    padding-top: 1.75rem;
    border-top: 1px solid rgba(2, 67, 125, 0.14);
    max-width: 34rem;
}

.domain-recheck .input-group {
    flex-wrap: nowrap;
}

.domain-recheck h2 {
    color: var(--ispweb-primary);
}

/* Matches the stacking the homepage bar does at the same width. */
@media (max-width: 575.98px) {
    .domain-recheck .input-group {
        flex-wrap: wrap;
    }

    .domain-recheck .domain-check-prefix {
        display: none;
    }

    .domain-recheck .form-control,
    .domain-recheck .btn {
        flex: 1 1 100%;
        border-radius: var(--ispweb-radius) !important;
    }

    .domain-recheck .btn {
        margin-top: 0.5rem;
    }
}

/* --------------------------------------------------------------------------
   Long documents: contents beside the text
   --------------------------------------------------------------------------
   The voorwaarden, privacy-, cookie- en disclaimerpagina's.

   These were laid out as one column beside an illustration. With the amount of
   text they carry that produced a very tall page filled only down its left-hand
   side, and no way to reach a section other than scrolling past everything
   above it. The contents list fills that column, follows the reader down, and
   makes every section one click away.
   -------------------------------------------------------------------------- */

.document {
    display: grid;
    gap: 2.5rem;
    /* Centred rather than stretched: the text keeps a readable measure, and the
       leftover width is split evenly instead of collecting on the right. */
    justify-content: center;
}

@media (min-width: 992px) {
    .document {
        grid-template-columns: minmax(230px, 280px) minmax(0, 74ch);
        gap: 3.5rem;
        align-items: start;
    }
}

/* -- the list ------------------------------------------------------------- */

.document-toc {
    position: relative;
    padding: 1.4rem 1.5rem 1.5rem;
    background: var(--ispweb-light-blue);
    border-radius: var(--ispweb-radius);
    overflow: hidden;
}

/* The house palette, on the panel that opens the page. */
.document-toc::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 4px;
    background: var(--ispweb-accent-bar);
}

@media (min-width: 992px) {
    .document-toc {
        position: sticky;
        /* Clear of the fixed header once it has shrunk. */
        top: 96px;
        max-height: calc(100vh - 128px);
        overflow-y: auto;
    }
}

.document-toc-title {
    margin: 0.35rem 0 0.9rem;
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ispweb-muted);
    /* Overrides the document h2 styling further down: this is a label, not a
       section of the document. */
    padding-top: 0;
    border-top: 0;
}

.document-toc ol {
    counter-reset: toc;
    margin: 0;
    padding: 0;
    list-style: none;
}

.document-toc li {
    margin: 0;
    counter-increment: toc;
}

.document-toc a {
    display: block;
    padding: 0.4rem 0 0.4rem 2rem;
    position: relative;
    font-size: 0.94rem;
    line-height: 1.4;
    color: var(--ispweb-primary);
    text-decoration: none;
    border-radius: 3px;
}

/* Numbered without an <ol> marker, so the number can be styled and aligned. */
.document-toc a::before {
    content: counter(toc);
    position: absolute;
    left: 0;
    top: 0.4rem;
    width: 1.35rem;
    text-align: right;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--ispweb-secondary);
}

.document-toc a:hover,
.document-toc a:focus-visible {
    color: var(--ispweb-secondary);
    text-decoration: underline;
}

/* -- the document itself --------------------------------------------------- */

.document-body {
    min-width: 0;
}

/*
 * `.longform.col-12` caps the article at 72ch so a full-width block of prose
 * keeps a readable line length. With a contents list that cap applies to the
 * whole two-column grid instead of to the text, squeezing both columns into
 * half the page -- which is the emptiness this layout exists to fix. Here the
 * measure is set by the grid's own text column, so the article may have the
 * full width.
 */
.longform.col-12.has-toc,
.longform.col-12.is-wide {
    max-width: none;
}

/* With the cap gone the article fills the container, so the measure has to be
   restored on the elements that are actually read. Cards, grids and lists are
   deliberately left out: filling the width is the point of them. */
.longform.is-wide .block-body > p,
.longform.is-wide .block-body > h2,
.longform.is-wide .block-body > h3 {
    max-width: 68ch;
}

/*
 * Section headings.
 *
 * A short banded rule above each one instead of a flat grey line: it is the
 * same palette as the footer and the cards, and on a page this long it gives
 * the reader something to count sections by.
 */
.longform.has-toc .document-body h2,
.longform.is-wide .block-body h2 {
    position: relative;
    margin-top: 2.75rem;
    margin-bottom: 0.85rem;
    padding-top: 1.25rem;
    border-top: 0;
    font-size: 1.4rem;
    color: var(--ispweb-primary);
    /* Clears the fixed header when jumped to from the contents. */
    scroll-margin-top: 110px;
}

.longform.has-toc .document-body h2::before,
.longform.is-wide .block-body h2::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    width: 88px;
    height: 4px;
    border-radius: 2px;
    background: var(--ispweb-accent-bar);
}

.longform.has-toc .document-body > h2:first-child,
.longform.is-wide .block-body > h2:first-child {
    margin-top: 0;
}

.longform.has-toc .document-body h3,
.longform.is-wide .block-body h3 {
    scroll-margin-top: 110px;
}

/* The measure is already set by the grid column, so the per-element cap that
   the other documents use would only narrow it a second time. */
.longform.has-toc .document-body p,
.longform.has-toc .document-body li {
    max-width: none;
}

/* -- the download list ----------------------------------------------------- */

/* The voorwaarden page is mostly a list of PDF modules. As plain links they
   were an undifferentiated wall; as cards they are scannable, and the column
   count follows the width instead of being fixed. */
.longform.has-toc .download-list {
    columns: auto 1;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: 0.6rem;
    margin: 1.1rem 0 0;
    padding: 0;
    list-style: none;
}

.longform.has-toc .download-list li {
    margin: 0;
}

.longform.has-toc .download-list a {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    height: 100%;
    /* Overrides the inline-block + left padding the plain list uses to make
       room for its absolutely positioned mark. */
    padding: 0.7rem 0.9rem;
    position: relative;
    background: #fff;
    border: 1px solid rgba(2, 67, 125, 0.16);
    border-left: 4px solid var(--ispweb-secondary);
    border-radius: var(--ispweb-radius);
    font-size: 0.88rem;
    line-height: 1.35;
    color: var(--ispweb-primary);
    text-decoration: none;
    overflow-wrap: anywhere;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}

.longform.has-toc .download-list a::before {
    content: "PDF";
    flex: none;
    /* The plain list further up styles this pseudo-element as a small outlined
       document mark: absolutely positioned, sized in em, with a border. Those
       declarations still apply here, so they are undone explicitly -- without
       this the badge is a red sliver a few pixels across with the word
       overflowing invisibly out of it. */
    position: static;
    width: auto;
    height: auto;
    border: 0;
    padding: 0.15rem 0.4rem;
    border-radius: 2px;
    background: var(--ispweb-accent-red);
    color: #fff;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    line-height: 1.3;
    /* The link sets overflow-wrap: anywhere so a long module name can break;
       without this the badge inherits it and "PDF" is broken onto three lines,
       leaving a red sliver a few pixels wide. */
    white-space: nowrap;
    overflow-wrap: normal;
}

.longform.has-toc .download-list a:hover,
.longform.has-toc .download-list a:focus-visible {
    background: var(--ispweb-light-blue);
    border-left-color: var(--ispweb-accent-green);
}

/* -- callout --------------------------------------------------------------- */

/* A paragraph marked .note in the copy: something the reader should not skim
   past. Yellow, matching the warning band elsewhere on the site. */
.longform .block-body .note {
    padding: 1rem 1.25rem;
    margin: 1.5rem 0;
    background: #fffdf0;
    border-left: 4px solid var(--ispweb-accent-yellow);
    border-radius: 0 var(--ispweb-radius) var(--ispweb-radius) 0;
}

.longform .block-body .note > :last-child {
    margin-bottom: 0;
}

@media (prefers-reduced-motion: reduce) {
    .longform.has-toc .download-list a {
        transition: none;
    }
}

/* --------------------------------------------------------------------------
   Social icons
   --------------------------------------------------------------------------
   Every icon carries all four house accents as a ring, rather than one accent
   each. Same reasoning as the cards: cycling one colour per icon made the row
   read as a sequence, and it left each network arbitrarily "the yellow one".

   It also settles a mismatch in the icon set itself. Three of these marks are
   solid discs with the glyph knocked out; WhatsApp and Instagram are outlines.
   Coloured flat, side by side, an outline among discs reads as an icon that
   failed to load. Inside a ring on a white field they all read the same: a blue
   mark in a circle.
   -------------------------------------------------------------------------- */

.copyright-section .socialmedia li a .svg-icon {
    /* The ring. Four quadrants, hard stops -- a blended conic gradient turns to
       mud at three pixels wide. Starting at 45deg so a colour boundary is not
       sitting on the vertical axis, where it reads as a seam. */
    background: conic-gradient(
        from calc(45deg + var(--ring-rotate, 0deg)),
        var(--ispweb-accent-red) 0 25%,
        var(--ispweb-accent-yellow) 25% 50%,
        var(--ispweb-accent-green) 50% 75%,
        var(--ispweb-accent-blue) 75% 100%
    );
    border-radius: 50%;
    /*
     * The ring is drawn *inside* the box the icon already occupied.
     *
     * With content-box the badge grew by twice the padding, and four of them no
     * longer fitted the column the copyright bar gives them -- Instagram
     * dropped onto a second line. border-box plus an explicit size keeps the
     * footprint exactly what it was; the ring eats into the mark instead of
     * into the row.
     */
    box-sizing: border-box;
    width: 1em;
    height: 1em;
    padding: 3px;
}

/*
 * Each ring turned a quarter further than the one before it.
 *
 * All four buttons carry the whole palette -- that part does not change, and it
 * is what keeps them reading as one set rather than as four ranked things. What
 * changes is where the set starts, so the colour at twelve o'clock runs blue,
 * green, yellow, red across the row. Side by side that is the difference between
 * four identical stamps and a row that was arranged.
 *
 * Rotating rather than recolouring is also what keeps this honest at any length:
 * :nth-child(4n+…) means a fifth button starts the sequence again instead of
 * running out of colours.
 */
.copyright-section .socialmedia li:nth-child(4n + 2) a .svg-icon { --ring-rotate: 90deg; }
.copyright-section .socialmedia li:nth-child(4n + 3) a .svg-icon { --ring-rotate: 180deg; }
.copyright-section .socialmedia li:nth-child(4n + 4) a .svg-icon { --ring-rotate: 270deg; }

/* The white field the mark sits on, and what makes the gradient show as a ring
   instead of a filled circle. */
.copyright-section .socialmedia li a .svg-icon svg {
    display: block;
    width: 100%;
    height: 100%;
    background: #fff;
    border-radius: 50%;
}

/*
 * The mark itself, in the brand blue.
 *
 * Driven through `color` with the fill forced, because the icons disagree about
 * how they paint themselves: three carry a `cls-1` class filled from a <style>
 * block inside the SVG, and WhatsApp and Instagram carry an inline
 * `style="fill: currentColor"`. An inline style beats an ordinary rule, so
 * without !important one declaration would not cover both kinds.
 */
.copyright-section .socialmedia li a {
    color: var(--ispweb-primary);
}

.copyright-section .socialmedia li a .svg-icon path,
.copyright-section .socialmedia li a .svg-icon rect,
.copyright-section .socialmedia li a .svg-icon circle,
.copyright-section .socialmedia li a .svg-icon polygon {
    fill: currentColor !important;
}

/* On hover the ring stays and the mark deepens, so the state change does not
   depend on telling two accent colours apart. site.css already scales the icon
   up by 1.1 on hover. */
.copyright-section .socialmedia li a:hover,
.copyright-section .socialmedia li a:focus-visible {
    color: var(--ispweb-secondary);
}

/* ==========================================================================
   Product pages
   ==========================================================================
   The six pages under /producten. They were a wall of prose with an
   illustration beside it, written in a register that had drifted (Title Case
   Everywhere on one page, "u" on the next, "jouw" on a third) and structured
   with <h6> and <br><br> because the original editor had no headings.

   They are documents now, like the policy pages, but shorter and with the
   product's own mark at the top -- the same line-art icon the homepage cards
   use, which is the one distinctive image each product actually has.
   -------------------------------------------------------------------------- */

/* -- the opening panel ----------------------------------------------------- */

.longform .block-body .product-intro {
    display: grid;
    gap: 1.5rem;
    align-items: center;
    position: relative;
    overflow: hidden;
    margin: 0 0 2.25rem;
    padding: 1.75rem 1.9rem;
    background: var(--ispweb-light-blue);
    border-radius: var(--ispweb-radius);
}

@media (min-width: 576px) {
    .longform .block-body .product-intro {
        grid-template-columns: auto minmax(0, 1fr);
        gap: 1.9rem;
    }
}

.longform .block-body .product-intro::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 4px;
    background: var(--ispweb-accent-bar);
}

/* The product mark. The icons carry their own #52ade0 fill, so they arrive in
   the house blue without being recoloured here. */
.longform .block-body .product-mark {
    margin: 0;
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 92px;
    height: 92px;
    padding: 1.1rem;
    background: #fff;
    border-radius: var(--ispweb-radius);
    box-shadow: 0 2px 10px rgba(2, 67, 125, 0.08);
}

.longform .block-body .product-mark img {
    width: auto;
    height: 100%;
    max-width: 100%;
}

/* Inside the panel the lead loses the bar and the tint it has elsewhere -- the
   panel is already both. */
.longform .block-body .product-intro .lead {
    margin: 0;
    padding: 0;
    background: none;
    border: 0;
}

/* -- feature cards --------------------------------------------------------- */

.longform .block-body .feature-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15.5rem, 1fr));
    gap: 0.85rem;
    margin: 1.25rem 0 0;
    padding: 0;
    list-style: none;
}

.longform .block-body .feature-grid li {
    position: relative;
    margin: 0;
    max-width: none;
    padding: 1rem 1.1rem 1.05rem;
    background: #fff;
    border: 1px solid rgba(2, 67, 125, 0.14);
    border-radius: var(--ispweb-radius);
    font-size: 0.94rem;
    line-height: 1.5;
    overflow: hidden;
}

.longform .block-body .feature-grid li::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: var(--ispweb-accent-bar);
}

.longform .block-body .feature-grid strong {
    display: block;
    margin-bottom: 0.25rem;
    color: var(--ispweb-primary);
    font-size: 1rem;
}

/* -- specification list ---------------------------------------------------- */

/* Two columns of label and value: prices, extensions, what is included. The
   policy pages use a plain <dl> for company details; this is the same element
   with rules between the rows, because these lists are longer and read as a
   table. */
.longform .block-body .spec-list {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0;
    margin: 1.25rem 0 0;
    padding: 0;
    background: none;
    max-width: none;
}

@media (min-width: 576px) {
    .longform .block-body .spec-list {
        grid-template-columns: minmax(10rem, auto) minmax(0, 1fr);
    }
}

.longform .block-body .spec-list dt {
    padding: 0.7rem 1rem 0.2rem 0;
    color: var(--ispweb-primary);
    font-weight: 700;
    border-top: 1px solid rgba(2, 67, 125, 0.12);
}

.longform .block-body .spec-list dd {
    margin: 0;
    padding: 0 0 0.7rem;
}

@media (min-width: 576px) {
    .longform .block-body .spec-list dt {
        padding: 0.7rem 1rem 0.7rem 0;
    }

    .longform .block-body .spec-list dd {
        padding: 0.7rem 0;
        border-top: 1px solid rgba(2, 67, 125, 0.12);
    }
}

/* -- closing panel --------------------------------------------------------- */

/* The "what now" at the foot of each product page. Deliberately the brand blue
   rather than another tinted box: it is the one thing on the page asking the
   reader to do something. */
.longform .block-body .cta-panel {
    position: relative;
    overflow: hidden;
    margin: 2.5rem 0 0;
    padding: 1.6rem 1.8rem;
    background: var(--ispweb-primary);
    border-radius: var(--ispweb-radius);
    color: #fff;
}

.longform .block-body .cta-panel::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 4px;
    background: var(--ispweb-accent-bar);
}

.longform .block-body .cta-panel p {
    max-width: none;
}

.longform .block-body .cta-panel > :last-child {
    margin-bottom: 0;
}

.longform .block-body .cta-panel a {
    color: #fff;
    font-weight: 700;
    text-decoration: underline;
}

.longform .block-body .cta-panel a:hover {
    color: var(--ispweb-tertiary);
}

/* ==========================================================================
   Outage bar
   ==========================================================================
   Shown above the header on every page while an incident is marked `high`.
   Rendered inside <header>, which site.css fixes to the top of the viewport,
   so the bar travels with it.
   -------------------------------------------------------------------------- */

.alert-bar {
    background: var(--ispweb-accent-red);
    color: #fff;
    /*
     * A fixed height, and a single line of text.
     *
     * The page below clears the fixed header with paddings of its own rather
     * than a global offset, so the bar's height has to be a number this
     * stylesheet knows — otherwise the compensation on <main> is guesswork and
     * a long incident title pushes the logo over the hero.
     */
    height: 44px;
    display: flex;
    align-items: center;
    font-size: 0.92rem;
}

.alert-bar-inner {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 0;
}

.alert-bar-tag {
    flex: none;
    padding: 0.1rem 0.5rem;
    border-radius: 2px;
    background: rgba(0, 0, 0, 0.22);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* The title is the part that gives way: it is the only piece whose length we
   do not control, and the link must stay reachable. */
.alert-bar-text {
    margin: 0;
    /* Grows to fill, which is what pushes the clock and the link to the right.
       Doing that with margin-left:auto on the link instead would break the
       moment the clock is absent -- an incident with an unreadable start time
       would leave the link stranded in the middle. */
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 700;
}

/* How long it has been running. Tabular figures so the seconds ticking over do
   not make the text jitter sideways once a second. */
.alert-bar-since {
    flex: none;
    padding: 0.1rem 0.5rem;
    border-radius: 2px;
    background: rgba(0, 0, 0, 0.18);
    font-size: 0.82rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.alert-bar-link {
    flex: none;
    margin-left: 0.75rem;
    color: #fff;
    font-weight: 700;
    text-decoration: underline;
    white-space: nowrap;
}

.alert-bar-link:hover,
.alert-bar-link:focus-visible {
    color: #fff;
    text-decoration: none;
}

.alert-bar a:focus-visible {
    outline-color: #fff;
}

/* Give back exactly what the bar took. Without this the header is 44px taller
   and overlaps the first section, which reserves its space for a header of the
   original height. */
body.has-alert main {
    padding-top: 44px;
}

/* On a phone the link label goes and the arrow carries it, so the title keeps
   the width it needs. */
@media (max-width: 575.98px) {
    .alert-bar {
        font-size: 0.85rem;
    }

    .alert-bar-tag,
    .alert-bar-since {
        display: none;
    }
}

/* Between phone and desktop there is room for the clock but not for both it
   and the tag. */
@media (min-width: 576px) and (max-width: 767.98px) {
    .alert-bar-tag {
        display: none;
    }
}

/* --------------------------------------------------------------------------
   Telephone hours in the footer
   --------------------------------------------------------------------------
   Directly under the number they belong to. Headed "telefonisch bereikbaar"
   and not "openingstijden": there is no counter to visit, and a heading that
   suggests one is the beginning of someone driving to Schijndel.
   -------------------------------------------------------------------------- */

/*
 * The heading carries the number it belongs to.
 *
 * "Tel: …" stood on a line of its own above an unrelated heading, which read as
 * two facts instead of one. The number and when it is answered are the same
 * answer to the same question.
 */
.footer-hours-title {
    position: relative;
    margin: 0 0 0.55rem;
    padding-bottom: 0.5rem;
    font-weight: 700;
    letter-spacing: 1px;
}

/*
 * The same banded line as everywhere else, at footnote scale.
 *
 * It was a grey hairline, which did the job and said nothing. The four accents
 * are what tie the cards, the news items, the incident stripes and the band
 * above the footer together, and there is no reason this one line should be the
 * exception.
 *
 * Full width of the column, exactly where the hairline ran: it lines up with the
 * right edge of the times below it, and a short stub under a heading that spans
 * the whole column just looks unfinished.
 */
.footer-hours-title::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    background: var(--ispweb-accent-bar);
}

.footer-hours-title a {
    white-space: nowrap;
}

/*
 * The days and times as one small table.
 *
 * A grid on the <dl> rather than on each row: that way every time starts at the
 * same x *and* ends at the same x, whatever the longest day label is, and the
 * column is exactly as wide as it needs to be instead of a guessed 7.5rem.
 */
.footer-hours {
    margin: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) max-content;
    gap: 0.1rem 1.25rem;
    line-height: 1.5;
}

/* The heading above is the bold one. These are its detail, so they step back:
   shouting all three made the block read as a wall. */
.footer-hours-day {
    font-weight: 400;
}

.footer-hours-time {
    margin: 0;
    /* Tabular figures so the digits line up between rows as well, and a flush
       right edge so the block ends on a straight line. */
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}

/* "Gesloten" is the absence of an answer; it should not carry the same weight
   as the hours that are worth acting on. */
.footer-hours-closed {
    opacity: 0.7;
}

/* --------------------------------------------------------------------------
   Contact block: tighter
   --------------------------------------------------------------------------
   The block grew by four lines when the hours were added and became the
   tallest thing on the page. Nothing is removed -- the air is.
   -------------------------------------------------------------------------- */

.footer-section .container {
    padding-top: 3rem !important;
    padding-bottom: 3rem !important;
}

/* The headset drawing was 168px and set the height of the whole band. */
.footer-section svg {
    height: 120px;
}

/*
 * A hierarchy instead of nine equally loud lines.
 *
 * site.css makes the whole column bold with 2px of letter-spacing. Labels keep
 * that; values do not. The letter-spacing in particular is what made it read as
 * a wall -- it is fine on three words and exhausting on a postcode and an IBAN.
 */
.footer-section .contact-details {
    letter-spacing: 0;
    line-height: 1.55;
}

.footer-section .contact-details .footer-label {
    font-weight: 700;
    letter-spacing: 1px;
}

.footer-section .contact-details .footer-value {
    font-weight: 400;
}

/* The paperwork: present, findable, and not competing with the phone number. */
.footer-section .contact-details .footer-registration {
    font-size: 0.92rem;
    opacity: 0.8;
}

.footer-section .contact-details ul {
    margin-bottom: 0;
}

/* The spacing between the groups of details was a full line; half is enough to
   read them as separate without the block growing another 40px. */
.footer-section .contact-details .pt-1 {
    padding-top: 0.5rem !important;
}

@media (max-width: 991.98px) {
    .footer-section svg {
        height: 96px;
    }
}

/* --------------------------------------------------------------------------
   Copyright bar: lower
   --------------------------------------------------------------------------
   Its height came from the social icons, which site.css sets at font-size: 3em.
   Four coloured discs at 48px in a bar of one line of text made it taller than
   the content it carries.
   -------------------------------------------------------------------------- */

/*
 * The bar was cut back when it held four decorative icons. It now holds the
 * only way to reach us from the foot of the page, so the buttons are the
 * content rather than trim -- big enough to be an obvious tap target on a
 * phone, and the bar grows to fit them rather than squeezing them.
 */
.copyright-section .container {
    padding-top: 1.15rem !important;
    padding-bottom: 1.15rem !important;
}

.copyright-section .socialmedia a .svg-icon {
    font-size: 2.9em;
    margin-right: 0.7rem;
}

.copyright-section .footer-menu li {
    padding-right: 1.1rem;
}

.copyright-section {
    font-size: 0.92rem;
}

/* On a phone the bar stacks anyway; the extra breathing room there is the
   difference between two rows and three. */
@media (max-width: 1199.98px) {
    .copyright-section .container {
        padding-top: 1.1rem !important;
        padding-bottom: 1.1rem !important;
    }
}

/* --------------------------------------------------------------------------
   Contact buttons
   --------------------------------------------------------------------------
   Telephone, e-mail, WhatsApp and the profile pages, in one row and one style.
   They used to be a row of social icons, a phone number elsewhere in the
   footer, an "E-mail: klik hier" link elsewhere again, and a third-party widget
   floating over the corner in a style belonging to nobody.

   The ring styling they inherit from .socialmedia is deliberate: every way of
   reaching us now looks the same, whether it dials, mails or opens a profile.
   -------------------------------------------------------------------------- */

.copyright-section .socialmedia {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0;
    padding: 0;
}

/* The line icons sit a touch tighter than the knocked-out discs do, so they
   get a little padding to match. Nothing else distinguishes the types: the
   whole point is that every way of reaching us looks the same. */
.contact-button-phone .svg-icon svg,
.contact-button-email .svg-icon svg,
.contact-button-whatsapp .svg-icon svg {
    padding: 2px;
}

/* --------------------------------------------------------------------------
   Contact form
   --------------------------------------------------------------------------
   Our own form, in our own stylesheet, in the same blue as everything else.
   What it replaces was a Freshworks widget: a script, a stylesheet and a
   webfont from someone else's server, rendering a green pill in a corner in a
   style belonging to nobody.

   A <dialog>, so the browser does the modal work -- focus trapping, Escape,
   inertness of the page behind it -- rather than a div and three hundred lines
   of JavaScript. Rendered by the server with `open` when scripting is off, in
   which case it appears as an ordinary panel and still submits.
   -------------------------------------------------------------------------- */

.contact-modal {
    /* Wide enough for the send button and Turnstile side by side -- the widget
       will not render below 300px, so the room has to come from here. The
       max-width is not redundant: a flex row that cannot shrink below its
       content will push a fixed-position box past the edge of the screen, and
       this is what stops it. */
    width: min(40rem, calc(100vw - 1.5rem));
    max-width: calc(100vw - 1.5rem);
    max-height: calc(100vh - 3rem);
    padding: 0;
    border: 0;
    border-radius: var(--ispweb-radius);
    box-shadow: 0 1.5rem 3rem rgba(2, 67, 125, 0.25);
    color: var(--ispweb-font);
    background: #fff;
}

/* The accent bar across the top: the same four colours as the cards, the news
   items and the band above the footer. */
.contact-modal::before {
    content: "";
    display: block;
    height: 4px;
    background: var(--ispweb-accent-bar);
}

.contact-modal::backdrop {
    background: rgba(2, 67, 125, 0.55);
}

.contact-modal-inner {
    padding: 1.5rem;
    /* A long form on a short screen scrolls inside the dialog rather than
       stretching it past the bottom of the viewport. */
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
}

.contact-modal h2 {
    margin: 0 0 0.75rem;
    font-size: 1.5rem;
    color: var(--ispweb-primary);
}

.contact-modal-close {
    float: right;
    margin: -0.5rem -0.5rem 0 0;
    padding: 0 0.5rem;
    font-size: 1.75rem;
    line-height: 1;
    color: var(--ispweb-muted);
    background: none;
    border: 0;
    cursor: pointer;
}

.contact-modal-close:hover {
    color: var(--ispweb-primary);
}

.contact-form-intro {
    margin-bottom: 1.25rem;
    color: var(--ispweb-muted);
}

.contact-form-row {
    margin-bottom: 1rem;
}

.contact-form-row label {
    display: block;
    margin-bottom: 0.25rem;
    font-weight: 600;
    font-size: 0.95rem;
}

.contact-form-row input,
.contact-form-row select,
.contact-form-row textarea {
    width: 100%;
    padding: 0.55rem 0.75rem;
    font: inherit;
    color: inherit;
    background: #fff;
    border: 1px solid #c8d2dc;
    border-radius: var(--ispweb-radius);
}

.contact-form-row input:focus,
.contact-form-row select:focus,
.contact-form-row textarea:focus {
    outline: 2px solid var(--ispweb-secondary);
    outline-offset: 1px;
    border-color: var(--ispweb-secondary);
}

.contact-form-row [aria-invalid="true"] {
    border-color: var(--ispweb-danger);
}

.contact-form-row textarea {
    resize: vertical;
}

.contact-form-error {
    margin: 0.3rem 0 0;
    font-size: 0.9rem;
    color: var(--ispweb-danger);
}

/* A message about the form as a whole -- an expired token, a failed captcha --
   above the fields rather than beside one of them. */
.contact-modal > .contact-modal-inner > .contact-form-error {
    padding: 0.75rem 1rem;
    border-left: 4px solid var(--ispweb-danger);
    background: #fdf2f1;
}

.contact-form-sent {
    padding: 0.9rem 1rem;
    border-left: 4px solid var(--ispweb-tertiary);
    background: #f5faea;
}

/*
 * Send, captcha, cancel -- on one line, all the same height.
 *
 * The captcha used to sit on its own row above the button: a 65px panel in the
 * flow, twice the height of anything near it, which made the bottom of the form
 * look like two unrelated things stacked. Beside the button it reads as part of
 * sending, which is what it is.
 */
/*
 * Send and cancel on the left, the captcha hard against the right edge.
 *
 * Turnstile's widget is an iframe of a height Cloudflare decides (65px), and
 * nothing on our side can change what is inside it. So the button is given that
 * same height rather than the other way round: matching the one thing that
 * cannot move is what makes the row a row instead of a staircase.
 */
.contact-form-actions {
    --contact-captcha-height: 65px;
    display: flex;
    align-items: center;
    /* No wrapping: the captcha dropping to its own line is exactly what this
       layout exists to prevent. The phone gets its own rule at the bottom. */
    flex-wrap: nowrap;
    gap: 0.75rem;
    margin-top: 1.25rem;
}

.contact-form-actions .btn-primary {
    display: inline-flex;
    align-items: center;
    /* Allowed to give up width before the captcha does: a narrower "Versturen"
       is still a button, a narrower Turnstile is a broken one. */
    flex: 0 1 auto;
    min-height: var(--contact-captcha-height);
    white-space: nowrap;
}

.contact-form-actions .cf-turnstile {
    /* Hard against the right edge, and never squeezed below the 300px the
       widget itself refuses to go under. */
    margin-left: auto;
    flex: 0 0 300px;
    height: var(--contact-captcha-height);
}

.contact-form-actions .cf-turnstile iframe {
    max-width: 100%;
}

/* On a phone there is no room beside the button. Then it does wrap, full width,
   and stops being pushed anywhere. */
@media (max-width: 575.98px) {
    .contact-form-actions {
        flex-wrap: wrap;
    }

    .contact-form-actions .cf-turnstile {
        flex: 1 1 100%;
        margin-left: 0;
    }
}

/*
 * The honeypot.
 *
 * Off-screen rather than display:none or type="hidden": a bot that reads the
 * markup skips the obvious ones, and one that fills in every visible field
 * falls into this. aria-hidden and tabindex keep it away from anyone using a
 * keyboard or a screen reader, who would otherwise be asked for a "website"
 * that must stay empty.
 */
.contact-form-trap {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* --------------------------------------------------------------------------
   News archive (/nieuws)
   --------------------------------------------------------------------------
   A grid, not the homepage's scroll-snap row. That row shows a handful of items
   in a band beside other sections; here the list is the page, and it has to be
   able to grow to fifty without becoming a horizontal scroll of fifty.

   The card itself reuses .news-item, so an item looks the same wherever it is.
   -------------------------------------------------------------------------- */

.news-archive-lead {
    max-width: 60ch;
    margin-bottom: 2.5rem;
    color: var(--ispweb-muted);
}

.news-archive {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
    gap: 1.75rem;
    margin: 0;
    padding: 0;
}

/* The carousel sizes its items for a track; in a grid the cell decides. */
.news-archive-item {
    width: auto;
    min-width: 0;
    scroll-snap-align: none;
}

.news-archive-item .news-item-title {
    margin-bottom: 0.5rem;
}

/* --------------------------------------------------------------------------
   Seasons
   --------------------------------------------------------------------------
   A season repaints the four accent tokens from a <style> block in the head
   (src/Theme.php), so every banded line on the site follows without any rule
   here. What is left is the one line it may say.

   Nothing else is themed on purpose: a visitor who came to read about SSL
   should not have to scroll past clip-art, and a palette is noticeable without
   ever being in the way.
   -------------------------------------------------------------------------- */

.season-greeting {
    margin: 0.6rem 0 0;
    font-weight: 700;
    letter-spacing: 1px;
    font-size: 0.95rem;
}

/* The preview bar, for a signed ?season= link from the portal. Fixed to the
   bottom because the header is fixed to the top and the outage bar already
   owns that space, complete with a body class compensating for its height. */
.season-preview-bar {
    position: fixed;
    left: 50%;
    bottom: 1rem;
    z-index: 1080;
    transform: translateX(-50%);
    max-width: calc(100vw - 2rem);
    margin: 0;
    padding: 0.6rem 1.1rem;
    border-radius: 999px;
    background: rgba(19, 19, 19, 0.92);
    color: #fff;
    font-size: 0.88rem;
    box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.25);
}

/* --------------------------------------------------------------------------
   Seasonal effect layer
   --------------------------------------------------------------------------
   The particles built by seasons.js. Two animations run at once on each one:
   a fall from above the viewport to below it, and a sideways sway -- together
   they read as drifting rather than as dropping in a straight line.

   Both are transforms, so they run on the compositor and the page behind them
   is never re-laid-out. A JavaScript loop nudging thirty elements every frame
   is what this avoids.
   -------------------------------------------------------------------------- */

.season-layer {
    position: fixed;
    inset: 0;
    z-index: 1;
    overflow: hidden;
    /* Never eats a click, never blocks the page it decorates. */
    pointer-events: none;
}

.season-particle {
    position: absolute;
    top: -8vh;
    line-height: 1;
    user-select: none;
    will-change: transform;
    animation-name: season-fall, season-sway;
    animation-timing-function: linear, ease-in-out;
    animation-iteration-count: infinite, infinite;
}

.season-particle-spin {
    animation-name: season-fall-spin, season-sway;
}

/* Paused wholesale when the tab is hidden: nothing is visible there. */
.season-layer-paused .season-particle {
    animation-play-state: paused;
}

@keyframes season-fall {
    from { transform: translateY(0); }
    to   { transform: translateY(116vh); }
}

@keyframes season-fall-spin {
    from { transform: translateY(0) rotate(0deg); }
    to   { transform: translateY(116vh) rotate(540deg); }
}

/* The sway is on the element's own axis via margin, so it composes with the
   transform above instead of fighting it for the same property. */
@keyframes season-sway {
    0%, 100% { margin-left: -1.6rem; }
    50%      { margin-left: 1.6rem; }
}

/*
 * Someone who asks for no motion gets none.
 *
 * Not fewer particles and not slower ones: the request is usually made because
 * movement on a page causes actual nausea. seasons.js checks this too and never
 * builds the layer; this is the belt to that pair of braces.
 */
@media (prefers-reduced-motion: reduce) {
    .season-layer {
        display: none;
    }
}

/* --------------------------------------------------------------------------
   myip.ispweb.nl
   --------------------------------------------------------------------------
   The panel that answers the question in the domain name. myip is another site
   in this installation -- same code, same database, its own two hostnames -- so
   this is an ordinary block and its styling is an ordinary part of this file.

   The address is set in a large weight because it is the one thing anybody came
   for; everything below it is detail.
   -------------------------------------------------------------------------- */

.myip-heading {
    margin: 0 0 1.25rem;
    font-size: clamp(1.5rem, 3vw, 2rem);
    color: var(--ispweb-primary);
}

/*
 * Where the panel starts is decided by the alignment, not by a margin.
 *
 * The row is bottom-aligned from `lg` up (see the template), so the column sits
 * as low as the illustration is tall and the last line of text finishes level
 * with the drawing. Nothing here has to guess a distance, and it stays right
 * when the text is edited.
 *
 * The only nudge is at the bottom: the artwork has a little transparent space
 * under it, so a strictly equal bottom edge reads as the text hanging slightly
 * low.
 */
@media (min-width: 992px) {
    .myip-section .col-text {
        padding-bottom: 2rem;
    }
}

/*
 * The illustration is left at full size on purpose.
 *
 * `.intro-section .image-right` in site.css gives it a width that deliberately
 * runs past the container to the edge of the viewport, and that bleed is the
 * whole look -- it is what the old myip page had and what the homepage hero
 * still does. Capping its height was tried and undone: it brought the artwork
 * back inside the column, where it reads as a picture that did not load
 * properly rather than as the background it is meant to be.
 *
 * The panel is centred against it, which is also what the old page did.
 */

.myip-panel {
    max-width: 46rem;
    padding: 2rem 2.25rem;
    background: var(--ispweb-light-blue);
    border-radius: var(--ispweb-radius);
    /* The house banded line across the top, exactly as on the product cards. */
    background-image: var(--ispweb-accent-bar);
    background-repeat: no-repeat;
    background-size: 100% 4px;
    background-position: top left;
}

.myip-label {
    margin: 0 0 0.15rem;
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--ispweb-muted);
}

.myip-address {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem 1rem;
    margin: 0 0 1.25rem;
    font-size: clamp(1.75rem, 5vw, 2.75rem);
    font-weight: 700;
    line-height: 1.1;
    color: var(--ispweb-primary);
    /* An address is read a character at a time and often typed over: tabular
       figures stop the digits from shifting around as they are scanned. */
    font-variant-numeric: tabular-nums;
    word-break: break-all;
}

.myip-copy {
    flex: 0 0 auto;
    padding: 0.35rem 0.9rem;
    font-size: 0.9rem;
    font-weight: 600;
    color: #fff;
    background: var(--ispweb-secondary);
    border: 0;
    border-radius: var(--ispweb-radius);
    cursor: pointer;
}

.myip-copy:hover,
.myip-copy:focus-visible {
    background: var(--ispweb-primary);
}

/* Hidden without scripting: a button that cannot copy is worse than no button. */
.no-js .myip-copy {
    display: none;
}

.myip-details {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 0.35rem 1.25rem;
    margin: 0 0 1.25rem;
    font-size: 0.95rem;
}

.myip-details dt {
    font-weight: 700;
    color: var(--ispweb-muted);
}

.myip-details dd {
    margin: 0;
    min-width: 0;
    word-break: break-word;
}

.myip-hint {
    margin: 0;
    font-size: 0.9rem;
    color: var(--ispweb-muted);
}

.myip-hint code {
    padding: 0.1rem 0.35rem;
    background: #fff;
    border-radius: 3px;
    color: var(--ispweb-primary);
}

@media (max-width: 575.98px) {
    .myip-panel {
        padding: 1.5rem 1.25rem;
    }

    .myip-details {
        grid-template-columns: minmax(0, 1fr);
        gap: 0 0;
    }

    .myip-details dd {
        margin-bottom: 0.6rem;
    }
}

/* --------------------------------------------------------------------------
   Contact tab
   --------------------------------------------------------------------------
   Half the pages on this site say "druk op de knop Contact". Until now that
   button was a small icon in the footer bar -- at the bottom of a page several
   screens tall, which is a strange place for the thing every other paragraph
   points at. This is the same button, kept in view.

   Left edge rather than right: the right is where a browser puts its scrollbar
   and where every support widget on the internet sits, and this is not one of
   those. Green, because that is the colour this site already uses for the one
   action it wants you to take ("Onze producten →").

   It opens the same dialog through the same link, so it works with scripting
   off as well.
   -------------------------------------------------------------------------- */

.contact-tab {
    position: fixed;
    left: 0;
    top: 50%;
    z-index: 1035;
    transform: translateY(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    padding: 1.1rem 0.55rem;
    background: var(--ispweb-tertiary);
    color: #fff;
    text-decoration: none;
    /* Rounded on the outer side only, so it reads as something attached to the
       edge of the window rather than as a floating pill. */
    border-radius: 0 var(--ispweb-radius) var(--ispweb-radius) 0;
    box-shadow: 0 0.25rem 1rem rgba(2, 67, 125, 0.28);
    transition: padding-left 0.15s ease, background-color 0.15s ease;
}

.contact-tab:hover,
.contact-tab:focus-visible {
    color: #fff;
    background: #8fb828;
    /* Leans out of the edge a little on hover: enough to answer "is this a
       button?" without moving anything else on the page. */
    padding-left: 0.95rem;
}

.contact-tab-label {
    /* Bottom-to-top, so the word is read from the bottom up like the spine of a
       book on a shelf -- the direction a left-hand tab is read in. */
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    font-size: 0.82rem;
    font-weight: 700;
    letter-spacing: 2px;
    text-transform: uppercase;
}

.contact-tab-icon {
    display: block;
    width: 1.15rem;
    height: 1.15rem;
    flex: 0 0 auto;
}

.contact-tab-icon svg {
    display: block;
    width: 100%;
    height: 100%;
}

.contact-tab-icon svg path,
.contact-tab-icon svg rect,
.contact-tab-icon svg circle,
.contact-tab-icon svg polygon {
    fill: currentColor !important;
}

/* Out of the way while the mobile menu is over the page: that menu covers the
   whole viewport, and a tab floating on top of it belongs to nothing. */
.active-mobile-menu .contact-tab {
    display: none;
}

/*
 * On a phone it moves to the corner and loses the word.
 *
 * A vertical tab against the left edge is fine at 1200px, where it sits in the
 * margin beside the text. At 375px there is no margin: the container starts
 * twelve pixels in, so the tab would lie across the first characters of every
 * line in the middle of the screen. Round, in the bottom corner, is where a
 * small screen expects a persistent action anyway -- and it is out of the
 * reading column entirely.
 */
@media (max-width: 575.98px) {
    .contact-tab {
        top: auto;
        bottom: 1rem;
        left: 1rem;
        transform: none;
        width: 3rem;
        height: 3rem;
        padding: 0;
        justify-content: center;
        border-radius: 50%;
    }

    .contact-tab:hover,
    .contact-tab:focus-visible {
        padding-left: 0;
    }

    .contact-tab-label {
        /* The icon carries it; the word is kept for screen readers. */
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .contact-tab-icon {
        width: 1.35rem;
        height: 1.35rem;
    }
}

/*
 * Print, and anyone who asked for less movement.
 *
 * A fixed tab has no meaning on paper, and the hover shift is the only thing on
 * the page that moves without being asked.
 */
@media print {
    .contact-tab {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .contact-tab {
        transition: none;
    }

    .contact-tab:hover,
    .contact-tab:focus-visible {
        padding-left: 0.55rem;
    }
}

/*
 * The explanation beside the illustration, under the panel.
 *
 * Held to a readable measure rather than the column width: the column is half a
 * wide screen, and a paragraph running the whole of that is hard to read back
 * to the start of the next line.
 */
.myip-body {
    max-width: 46rem;
    margin-top: 2.5rem;
}

.myip-body p:last-child {
    margin-bottom: 0;
}

/* IPv4 and IPv6 under each other, each with its own label and copy button. */
.myip-family + .myip-family {
    margin-top: 1.25rem;
}

/* Waiting, and not-found: both quieter than an address, because neither is the
   answer somebody came for. */
.myip-pending,
.myip-family-absent .myip-address {
    color: var(--ispweb-muted);
    font-size: clamp(1.1rem, 2.5vw, 1.5rem);
}
