/* Jost, SIL OFL, self-hosted and subset to latin (ADR-0029). Self-hosted because a
   Google Fonts request leaks a visitor's IP to a third country, which ADR-0005 forbids
   and the performance budget dislikes; subset because the site is EN/NL/DE and the
   cyrillic block is bytes nobody reads. */
@font-face{font-family:Jost;font-style:normal;font-weight:600;font-display:swap;
  src:url(/static/fonts/jost-600.woff2) format('woff2')}
@font-face{font-family:Jost;font-style:normal;font-weight:700;font-display:swap;
  src:url(/static/fonts/jost-700.woff2) format('woff2')}
/* Entropy (ADR-0029). Both themes, and the six restraint rules are load-bearing:
   one hue, no gradient on type, noise in one place per page and never behind text,
   a deterministic seed, an accent that carries no unearned meaning, contrast checked
   in both themes. */
:root {
  --bg:#f8f8fc; --surf:#fff; --ink:#101020; --dim:#5e5e76;
  --acc:#4b46e0; --acc2:#9b2fd0; --line:#e4e4ee; --head:#fff; --code:#eeeef6;
  /* Small body text set in the accent -- the positioning line, the service edge lines --
     needs a lighter value in the dark theme to be readable at 12-15px. Only those: the
     wordmark, the buttons and the links keep the accent itself, because lightening them
     washes out the one hue the direction allows. On white, one value does both. */
  --acc-text:#4b46e0;
  /* Placeholder text. On white the ordinary dim value reads correctly; on the dark theme
     it competes with what somebody has typed, so it gets its own darker value. Two
     variables rather than one opacity: opacity would tint them both by the same amount and
     the two themes do not need the same amount. */
  --ph:#5e5e76;
  /* A filled BUTTON and the wordmark carry the same hex and do not read the same: a large
     area of a colour looks lighter than thin letterforms of it. In the dark theme the
     button is taken down a shade so the two match by eye, which is the thing that matters.
     On white the accent is already dark and one value does both. */
  --acc-fill:#4b46e0;
  --r:8px;
  --ui:'Helvetica Neue',Inter,'SF Pro Text',system-ui,sans-serif;
  --disp:Jost,Futura,'Century Gothic','Avenir Next',system-ui,sans-serif;
  --mono:'SF Mono',Menlo,'JetBrains Mono',monospace;
}
html[data-theme="dark"] {
  --bg:#0a0a12; --surf:#12121f; --ink:#eaeaf4; --dim:#85859c;
  --acc:#6f6bff; --acc2:#c86bff; --line:#1e1e30; --head:#08080f; --code:#171728;
  --acc-text:#a9a6ff;
  --acc-fill:#5551ea;
  --ph:#565669;
  color-scheme: dark;
}
html[data-theme="light"] { color-scheme: light; }

/* language: a <details> that opens a two-line list under the current code. Minimal on
   purpose -- the codes are the whole control, and the page already carries the same
   choice in the footer for anyone who scrolls past this. */
.langsel{position:relative;flex:0 0 auto}
.langsel summary{display:inline-flex;align-items:center;gap:5px;height:32px;padding:0 9px;
  border:1px solid var(--line);border-radius:var(--r);color:var(--dim);cursor:pointer;
  font:12.5px/1 var(--mono);letter-spacing:.04em;list-style:none;user-select:none}
.langsel summary::-webkit-details-marker{display:none}
.langsel summary:hover{color:var(--ink);border-color:var(--dim)}
.langsel summary:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
.langsel[open] summary{color:var(--ink);border-color:var(--dim)}
.langsel[open] summary svg{transform:rotate(180deg)}
.langmenu{position:absolute;right:0;top:calc(100% + 6px);z-index:20;display:flex;
  flex-direction:column;min-width:100%;padding:4px;background:var(--surf);
  border:1px solid var(--line);border-radius:var(--r);
  box-shadow:0 8px 24px rgba(0,0,0,.10)}
html[data-theme="dark"] .langmenu{box-shadow:0 8px 24px rgba(0,0,0,.5)}
.langmenu a{padding:6px 10px;border-radius:calc(var(--r) - 3px);color:var(--dim);
  text-decoration:none;font:12.5px/1.2 var(--mono);letter-spacing:.04em}
.langmenu a:hover{background:var(--code);color:var(--ink)}
.langmenu a[aria-current]{color:var(--ink)}

/* the toggle */
.themebtn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;
  border-radius:var(--r);border:1px solid var(--line);background:transparent;color:var(--dim);
  cursor:pointer;padding:0;flex:0 0 auto}
.themebtn:hover{color:var(--ink);border-color:var(--dim)}
.themebtn .sun{display:none}
html[data-theme="dark"] .themebtn .sun{display:block}
html[data-theme="dark"] .themebtn .moon{display:none}
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
/* The footer sits at the bottom of the VIEWPORT on a short page and after the content on
   a long one -- Entropy pins it to the bottom of its frame, and a short page that leaves a
   band of background under the footer reads as an unfinished page. A column with a growing
   main does that with no fixed heights and no overlap; 100dvh keeps it honest on mobile,
   where the browser chrome makes 100vh a promise the viewport does not keep. */
body{margin:0;background:var(--bg);color:var(--ink);font:clamp(15px,1.25vw,17px)/1.6 var(--ui);
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
  min-height:100vh;min-height:100dvh;display:flex;flex-direction:column}
main{flex:1 0 auto}
/* The measure. Entropy draws 1000px inside a 1200px frame -- a RATIO, not a pixel value,
   and holding the pixel value on a 1600px screen leaves the page marooned in the middle.
   So: the board's proportion up to a cap, and full width with padding below the board's
   own size, where every pixel is needed. */
.wrap{width:100%;max-width:1000px;margin:0 auto;padding:0 44px}
@media (min-width:1201px){.wrap{max-width:min(1240px,69vw);padding:0 clamp(44px,3.7vw,58px)}}
/* The header, the positioning band and the footer are FULL-BLEED: the board draws them as
   bands across the whole frame with 44px of side padding, and only the content column is
   held to the measure. Centring them inside the measure was a deviation. */
.hd .wrap,.strip .wrap{max-width:none}
a{color:var(--acc)}
img,svg{max-width:100%}

/* header */
.hd{background:var(--head);border-bottom:1px solid var(--line)}
.hd .wrap{display:flex;align-items:center;gap:26px;padding-top:20px;padding-bottom:20px}
/* The mark (ADR-0033) sits beside the wordmark and is sized against the TYPE, not the
   header box: it grows with the wordmark on a wide viewport and shrinks with it on a
   narrow one, so the lockup holds its proportion at every width. */
.logo{display:inline-flex;align-items:center;gap:.42em;
  font:700 clamp(19px,1.58vw,22px)/1 var(--disp);letter-spacing:-.01em;
  text-decoration:none;color:var(--ink)}
.logo .mark{width:1.92em;height:1.92em;flex:0 0 auto;display:block}
.logo b{color:var(--acc)}
/* The wordmark completes itself. A gap, then a tail typed out letter by letter --
   Kryptografie, Kryptologie, Kryptoanalyse, Kryptocurrency -- held a moment, then taken
   back faster than it arrived, and the next word begins.

   The width is held by an invisible copy of the longest tail, so the navigation beside it
   never moves while letters come and go. Typing is the one thing here that needs a script;
   without it the ellipsis and one word stand still, which is a wordmark, not a fault. */
/* No gap: the tail continues the word rather than sitting beside it, so Krypt becomes
   Kryptografie as it is typed. */
.tails{display:inline-flex;align-items:baseline;font-weight:400;
  color:var(--dim);opacity:.5;letter-spacing:-.005em}
.tails .type{position:relative;display:inline-block}
.tails .sizer{visibility:hidden}
.tails .typed{position:absolute;left:0;top:0;white-space:nowrap}
/* The caret follows the text because it IS after the text -- an inline element the letters
   push along. Measuring the width in script and positioning it would work too, and would
   be one more thing to get wrong on a font that has not loaded yet.

   It takes up NO VERTICAL SPACE, and that is the whole trick. An inline-block contributes
   its full height to the line box whatever its line-height, so a caret 0.95em tall grew the
   typed line and pushed that line's baseline down -- while the invisible sizer beside it,
   which has no caret, kept the original baseline. The tail sat about a pixel below "DeKrypt"
   at 22px, which is exactly what the owner saw.

   So the element is 0 tall and the bar is drawn out of flow, upward from the baseline it
   sits on. Both line boxes are then identical, and the two halves of the wordmark share one
   baseline at every size. */
.tails .caret{display:inline-block;position:relative;width:1px;height:0;margin-left:1.5px;
  vertical-align:baseline;animation:caret 1.05s steps(1,end) infinite}
.tails .caret::before{content:"";position:absolute;left:0;bottom:-.06em;width:1px;
  height:.95em;background:currentColor}
@keyframes caret{0%,49%{opacity:1}50%,100%{opacity:0}}
@media (prefers-reduced-motion:reduce){
  .tails .caret{animation:none;opacity:0}
}
/* A narrow header has no room for it, and the name is the part that matters. */
@media (max-width:820px){.tails{display:none}}
nav.main{margin-left:auto;display:flex;gap:clamp(12px,1.5vw,20px);align-items:center}
nav.main a{color:var(--dim);text-decoration:none;font-size:clamp(14px,1.17vw,16px)}
nav.main a:hover,nav.main a[aria-current]{color:var(--ink)}

/* The page links, behind one control when there is no room for them.

   A <details> for the same reason the language control is one: it opens, closes and takes
   the keyboard on its own. ONE set of links -- on a wide screen the summary is hidden and
   the panel is just a row, so there is no second copy of the navigation to fall out of
   step with the first.

   Every rule that turns the panel into a floating menu is keyed on `[data-ready]`, which
   the script sets once it has decided which state applies. NOTHING is keyed on the absence
   of a script: if site.js never arrives -- blocked, failed, still loading -- the panel is
   simply an open list and every page is one tap away. A menu that can only be opened by a
   script it did not get is worse than no menu at all. */
.navsel{position:relative;flex:0 1 auto;min-width:0}
.navsel summary{display:none}
.navsel summary::-webkit-details-marker{display:none}
.navmenu{display:flex;gap:clamp(12px,1.5vw,20px);align-items:center}

@media (max-width:900px){
  /* The call to action leaves the header entirely wherever the menu is a hamburger, on the
     owner's instruction -- not moved into the panel, gone. The header row is the wordmark
     and three controls; asking for a meeting is a link in the menu and a button in the
     hero, which is where a narrow page is read anyway. */
  nav.main > .btn{display:none}
  /* Three lines, at the size of the other two controls in the header. */
  .navsel summary{display:inline-flex;align-items:center;justify-content:center;
    width:36px;height:32px;border:1px solid var(--line);border-radius:var(--r);
    color:var(--dim);cursor:pointer;list-style:none}
  .navsel summary:hover{color:var(--ink);border-color:var(--dim)}
  .navsel summary:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
  .navsel[open] summary{color:var(--ink);border-color:var(--dim)}
  /* Before the script has spoken -- and forever, if it never does -- the links are a
     stacked list rather than a row, on their own line below the call to action. Nothing
     overlaps, and nothing is behind a control that cannot be operated. */
  .navmenu{flex-direction:column;align-items:flex-start;gap:6px}
  nav.main{flex-wrap:wrap}
  .navsel:not([data-ready]){flex:1 0 100%;order:2}
  /* Once the script is ready, open means a panel under the header: right-aligned to the
     control it came from, like the language menu, and wide enough that a Dutch or German
     label is not broken across two lines. */
  /* Anchored to the HEADER ROW, not to the hamburger. The control sits in the middle of
     the row -- the call to action is to its right -- so a panel hung off it would run past
     the left edge of the screen on a phone. Full width under the header instead, which is
     also where a thumb expects it. */
  .hd .wrap{position:relative}
  .navsel[data-ready]{position:static}
  .navsel[data-ready][open] .navmenu{position:absolute;left:0;right:0;
    top:calc(100% + 12px);z-index:20;align-items:stretch;gap:2px;padding:8px;
    background:var(--surf);border:1px solid var(--line);border-radius:var(--r);
    box-shadow:0 8px 24px rgba(16,16,32,.14)}
  html[data-theme="dark"] .navsel[data-ready][open] .navmenu{box-shadow:0 8px 24px rgba(0,0,0,.5)}
  .navsel[data-ready][open] .navmenu a{padding:9px 10px;
    border-radius:calc(var(--r) - 3px);font-size:15px}
  .navsel[data-ready][open] .navmenu a:hover{background:var(--code)}
  .navsel[data-ready] ~ .btn{margin-left:0}
}
/* What a narrow header gives up, in the order it gives it up.

   First the language control: it is duplicated in the footer, which is the reason it can
   go here and not there (ADR-0032 put it in both). Then the wordmark's TEXT -- the mark
   identifies the page, the name is in the tab, the title and the footer -- and what is
   left is the mark, the menu, the call to action and the theme. The call to action never
   goes: it is the one thing the header is for.

   The MEASURE goes the other way on a phone: 20px plus 5% of the viewport either side, on
   the owner's call. A line of text that runs edge to edge is harder to read than a shorter
   one with air around it, and the margin is what says the page has a shape. It is written
   as a floor plus a percentage rather than a flat percentage so that the smallest screens
   still keep a usable column: 38px a side at 360, 40 at 390, 48 at 560, where it meets the
   44px the wider layout uses without a step. */
@media (max-width:560px){
  .wrap{padding:0 calc(20px + 5vw)}
}
/* WHAT A NARROW HEADER KEEPS, on the owner's call: the wordmark and its typed tail. The
   call to action leaves the row and reappears at the top of the menu panel, which is what
   makes the room -- it was ~190px of the ~356px a 440px screen actually has.

   So a phone header reads mark, DeKrypt, tail, then menu, language, theme. The name is the
   thing a stranger needs from a header; the button is one tap further in. */
@media (max-width:640px){
  .hd .wrap{gap:8px;padding-top:14px;padding-bottom:14px}
  .logo .mark{width:1.7em;height:1.7em}
  .logo{font-size:22px;gap:.34em}
  .tails{display:inline-flex}
  nav.main .langsel{display:inline-flex}
  /* The wider margins take about 36px out of the row, so the row gives some back: the
     call to action keeps its words, which is the point of it, and pays in padding. */
  /* Four controls, a call to action that keeps its words, and 5% of the screen given back
     to the margins: below about 375px they do not fit on one line, so the theme toggle
     wraps -- to the RIGHT, under the call to action, which reads as a second row rather
     than as something that fell off the first. */
  nav.main{gap:4px;justify-content:flex-end;row-gap:8px}
  .btn{padding:9px 12px;font-size:13.5px}
}
/* ONE ROW on a phone. Everything gives a little so that nothing has to leave: the header
   takes back some of its own side padding, the wordmark drops a couple of points, and the
   three controls lose two pixels of height each. At 390 that is about 280px of content in
   335px of room, which holds down to 360. */
@media (max-width:480px){
  .fbottom{font-size:11.5px}
  .fbottom .build{font-size:10.5px}
  .hd .wrap{flex-wrap:nowrap;gap:6px;
    padding-left:calc(12px + 4vw);padding-right:calc(12px + 4vw)}
  .logo{font-size:19px;gap:.28em;flex:0 1 auto;min-width:0}
  .logo .mark{width:1.55em;height:1.55em}
  nav.main{gap:3px;flex:0 0 auto}
  .navsel summary{width:32px;height:30px}
  .themebtn{width:30px;height:30px}
  .langsel summary{height:30px;padding:0 7px;font-size:11.5px}
}
/* The tightest phones give up padding rather than another control. */
@media (max-width:390px){
  .btn{padding:9px 10px;font-size:13px}
}
nav.main a.btn{color:#fff}
nav.main a.btn:hover{color:#fff}
/* No border on the filled button. A <button> element carries one from the browser and an
   <a> does not, so the same class drew two different shapes -- the form's submit had a
   hairline the header's call to action never had. The ghost button keeps its border,
   because on that one the border IS the button. */
.btn{display:inline-block;padding:10px 18px;border:0;border-radius:var(--r);
  background:var(--acc-fill);color:#fff;font-weight:600;
  font-size:clamp(14px,1.17vw,16px);font-family:inherit;line-height:1.2;
  text-decoration:none;white-space:nowrap;cursor:pointer}
.btn.ghost{background:transparent;color:var(--ink);border:1px solid var(--line)}

/* trust strip */
/* The positioning line lives here, directly under the header: it is the first thing
   read, and it is a claim rather than a caption -- so it is set as a bold sentence and
   not as tracked-out uppercase (ADR-0030). */
.strip{background:var(--surf);border-bottom:1px solid var(--line)}
.strip .wrap{padding-top:14px;padding-bottom:14px}
/* Normal weight, on the owner's call: the line is already carried by the accent colour
   and its position under the header, and bold on top of both was shouting. */
.positioning{margin:0;color:var(--acc-text);font:400 clamp(15px,1.25vw,17px)/1.4 var(--ui);letter-spacing:.005em}
/* Written as a list, drawn as one line. EVERY item carries its own dot, including the
   first: a separator between items is punctuation, a mark before each one is a list. The
   dot sits close to the words it belongs to and the space between items is wider, so the
   grouping is done by proximity rather than by asking the reader to work it out. */
ul.positioning{display:flex;flex-wrap:wrap;align-items:baseline;
  gap:.35em 1.5em;padding:0;list-style:none}
ul.positioning li{display:flex;align-items:baseline;gap:.42em;white-space:nowrap}
ul.positioning li::before{content:"\2022";opacity:.5}

/* hero */
.hero .wrap{display:grid;grid-template-columns:1.05fr .95fr;gap:38px;align-items:center;
  padding-top:34px;padding-bottom:26px}
.kick{color:var(--acc);font:700 14.5px/1.35 var(--ui);margin:0 0 14px;max-width:46ch}

h1{font:600 clamp(42px,3.5vw,58px)/1.09 var(--disp);letter-spacing:-.02em;margin:0 0 14px}
.lead{color:var(--dim);font-size:clamp(15.5px,1.29vw,18px);max-width:62ch;margin:0 0 22px}
/* The lead is a rich text document, so it arrives as paragraphs inside the box. The box
   keeps the spacing it always had; the paragraphs inside space each other. */
.lead p{margin:0 0 12px}
.lead p:last-child{margin-bottom:0}
/* Bold in the lead reads as emphasis, not as a heading: the same weight the body uses for
   strong, in the ink colour rather than the dim one, so the words lift off the sentence
   without shouting. */
.lead strong{font-weight:600;color:var(--ink)}
.cta{display:flex;gap:12px;flex-wrap:wrap}
/* The field is alive, within Entropy's restraint rules (ADR-0029): it deals itself in
   like a keystream, then a small minority of cells keeps moving. The delays and periods
   are generated from the page's seed, so the motion is part of the fingerprint rather
   than an effect laid over it. One hue; the accent still means nothing. */
.art svg rect{transform-box:fill-box;transform-origin:50% 50%;opacity:var(--o)}
.art svg .o1{--o:0.06}
.art svg .o2{--o:0.12}
.art svg .o3{--o:0.18}
.art svg .o4{--o:0.24}
.art svg .o5{--o:0.30}
.art svg .o6{--o:0.36}
.art svg .o7{--o:0.42}
.art svg .o8{--o:0.48}
.art svg .d0{--d:0.00s}
.art svg .d1{--d:0.07s}
.art svg .d2{--d:0.14s}
.art svg .d3{--d:0.21s}
.art svg .d4{--d:0.28s}
.art svg .d5{--d:0.35s}
.art svg .d6{--d:0.42s}
.art svg .d7{--d:0.49s}
.art svg .d8{--d:0.56s}
.art svg .d9{--d:0.63s}
.art svg .d10{--d:0.70s}
.art svg .d11{--d:0.77s}
.art svg .d12{--d:0.84s}
.art svg .d13{--d:0.91s}
.art svg .d14{--d:0.98s}
.art svg .d15{--d:1.05s}
.art svg .t0{--t:4s}
.art svg .t1{--t:5s}
.art svg .t2{--t:6s}
.art svg .t3{--t:7s}
.art svg .t4{--t:8s}
.art svg .t5{--t:9s}
.art svg .t6{--t:10s}
.art svg .t7{--t:11s}
.art svg .c{animation:cell-in .55s var(--d,0s) both}
/* NO delay in this shorthand. A live cell carries no --d, and an animation shorthand with
   an undefined var() is invalid at computed-value time -- the whole declaration is thrown
   away and the cell simply never animates, which is exactly how this looked. The phase
   below sets the delay on its own, and it has to come AFTER this rule or the shorthand
   resets it to zero and every cell turns over in step. */
.art svg .l{animation:cell-live var(--t,8s) infinite}
.art svg .la{animation-name:cell-live-a}
.art svg .lb{animation-name:cell-live-b}
.art svg .p0{animation-delay:-0.00s}
.art svg .p1{animation-delay:-0.46s}
.art svg .p2{animation-delay:-0.92s}
.art svg .p3{animation-delay:-1.38s}
.art svg .p4{animation-delay:-1.84s}
.art svg .p5{animation-delay:-2.30s}
.art svg .p6{animation-delay:-2.76s}
.art svg .p7{animation-delay:-3.22s}
.art svg .p8{animation-delay:-3.68s}
.art svg .p9{animation-delay:-4.14s}
.art svg .p10{animation-delay:-4.60s}
.art svg .p11{animation-delay:-5.06s}
.art svg .p12{animation-delay:-5.52s}
.art svg .p13{animation-delay:-5.98s}
.art svg .p14{animation-delay:-6.44s}
.art svg .p15{animation-delay:-6.90s}
.art svg .p16{animation-delay:-7.36s}
.art svg .p17{animation-delay:-7.82s}
.art svg .p18{animation-delay:-8.28s}
.art svg .p19{animation-delay:-8.74s}
.art svg .p20{animation-delay:-9.20s}
.art svg .p21{animation-delay:-9.66s}
.art svg .p22{animation-delay:-10.12s}
.art svg .p23{animation-delay:-10.58s}
@keyframes cell-in{from{opacity:0;transform:scale(.55)}to{opacity:var(--o);transform:scale(1)}}
@keyframes cell-live{
  0%{opacity:var(--o);transform:scale(1)}
  38%{opacity:var(--o);transform:scale(1)}
  47%{opacity:0;transform:scale(.5)}
  56%{opacity:0;transform:scale(.5)}
  66%{opacity:var(--o);transform:scale(1)}
  100%{opacity:var(--o);transform:scale(1)}
}
/* The two accent variants change COLOUR as they turn over, which is the part that reads
   as a value changing rather than a light blinking. */
@keyframes cell-live-a{
  0%{opacity:var(--o);fill:currentColor;transform:scale(1)}
  26%{opacity:var(--o);fill:currentColor;transform:scale(1)}
  35%{opacity:0;transform:scale(.5)}
  44%{opacity:1;fill:var(--acc);transform:scale(1)}
  78%{opacity:1;fill:var(--acc)}
  90%{opacity:var(--o);fill:currentColor}
  100%{opacity:var(--o);fill:currentColor}
}
@keyframes cell-live-b{
  0%{opacity:var(--o);fill:currentColor;transform:scale(1)}
  26%{opacity:var(--o);fill:currentColor;transform:scale(1)}
  35%{opacity:0;transform:scale(.5)}
  44%{opacity:1;fill:var(--acc2);transform:scale(1)}
  78%{opacity:1;fill:var(--acc2)}
  90%{opacity:var(--o);fill:currentColor}
  100%{opacity:var(--o);fill:currentColor}
}
/* Motion is a preference, not a detail: honoured, and the field still renders. */
@media (prefers-reduced-motion:reduce){
  .art svg .c,.art svg .l{animation:none}
}
/* No frame. The field is not a picture in a box -- it is the page's own material, so it
   fades out on all four sides instead of stopping at a line. A radial mask does that in
   one declaration and degrades to a plain field where masks are unsupported. */
.art{position:relative;height:278px;overflow:hidden;color:var(--ink);
  --fade:linear-gradient(to right,transparent 0,#000 15%,#000 85%,transparent 100%),
         linear-gradient(to bottom,transparent 0,#000 17%,#000 83%,transparent 100%);
  -webkit-mask-image:var(--fade);mask-image:var(--fade);
  -webkit-mask-composite:source-in;mask-composite:intersect}

/* an interior page gets the hero's top air, without the hero */
.pagehead .wrap{padding-top:34px;padding-bottom:4px}

/* body content */
main .wrap{padding-bottom:44px}
section.blk{padding:6px 0 18px}
section.blk h2{font:600 clamp(27px,2.25vw,34px)/1.2 var(--disp);margin:26px 0 12px}
section.blk h3{font:600 clamp(19px,1.58vw,23px)/1.25 var(--disp);margin:20px 0 9px}
section.blk p{color:var(--dim);margin:0 0 12px;max-width:66ch}
section.blk ul,section.blk ol{color:var(--dim);max-width:70ch}
section.blk blockquote{margin:18px 0;padding-left:16px;border-left:2px solid var(--acc);color:var(--dim)}
section.blk hr{border:0;border-top:1px solid var(--line);margin:26px 0}
section.blk img{border-radius:var(--r);border:1px solid var(--line)}
section.blk video,section.blk audio{width:100%;border-radius:var(--r)}
.dk-file{display:inline-block;padding:8px 12px;border:1px solid var(--line);border-radius:var(--r);
  text-decoration:none;font-size:14px}

/* cards */
.cards{display:grid;grid-template-columns:repeat(4,1fr);gap:13px;margin:4px 0 6px}
@media (max-width:900px){.cards{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.cards{grid-template-columns:1fr}}
.card{padding:17px;border:1px solid var(--line);border-radius:var(--r);background:var(--surf);
  display:flex;flex-direction:column}
.card h3{margin:0 0 7px;font:600 clamp(16px,1.33vw,19px)/1.2 var(--disp)}
.card p{margin:0;color:var(--dim);font-size:clamp(12.8px,1.07vw,15px)}
/* "Read more" sits at the foot of the card and leads to the same service, in full, on the
   services page. The card is a column so the link pins to the bottom however uneven the
   blurbs are, and the rows above it keep their line lengths. */
.card .card-more{margin:0;margin-top:auto;padding-top:11px}
.card-more a{color:var(--acc-text);text-decoration:none;font-weight:500;
  font-size:clamp(12.8px,1.07vw,15px)}
.card-more a:hover{text-decoration:underline}

/* service rows: a label column beside its text, hairline-separated. Entropy's services
   screen, at the board's own measurements (245px column, 24px gutter). */
.svcs{margin-top:14px;border-top:1px solid var(--line)}
.svc{padding:14px 0;border-bottom:1px solid var(--line);display:grid;
  grid-template-columns:245px 1fr;gap:24px;align-items:start;scroll-margin-top:18px}
@media (max-width:760px){.svc{grid-template-columns:1fr;gap:6px}}
section.blk .svc-h h3{margin:0 0 3px;font:600 clamp(17px,1.42vw,20px)/1.2 var(--disp)}
/* The edge line: the short accent note under a service title, monospace and in the one
   hue, exactly as the board draws it. */
section.blk .svc-m{margin:0;font-size:12px;color:var(--acc-text);font-family:var(--mono)}
.svc p{margin:0 0 8px;color:var(--dim);font-size:clamp(13.5px,1.13vw,16px);line-height:1.55;max-width:78ch}
.svc p:last-child{margin-bottom:0}

.secta{margin:18px 0 0}

/* the meeting request form. The board's request screen: a 760px column, half-width pairs,
   the required/optional note aligned right on the label. */
.rq,.sent{max-width:760px;margin-top:10px}
.rq{display:grid;grid-template-columns:1fr 1fr;gap:0 18px}
.fg{margin-bottom:14px;grid-column:1 / -1}
.fg.half{grid-column:span 1}
@media (max-width:620px){.fg.half{grid-column:1 / -1}}
.fg label{display:block;color:var(--dim);font-size:12px;margin:0 0 5px}
.fg .req{float:right;color:var(--dim);font-size:11px}
.fg input,.fg textarea,.fg select{width:100%;padding:11px 13px;border:1px solid var(--line);
  border-radius:var(--r);background:var(--surf);color:var(--ink);font:clamp(14px,1.17vw,16px)/1.4 var(--ui)}
.fg input:focus,.fg textarea:focus,.fg select:focus{outline:2px solid var(--acc);outline-offset:1px;border-color:var(--acc)}
.fg textarea{resize:vertical}
/* A drop-down. The browser's own arrow is drawn in the system colours and looks pasted on
   in the dark theme, so it is replaced by a caret drawn in the border colour -- an inline
   SVG in the stylesheet, where the content security policy allows it and an inline style
   attribute would be dropped. Right padding leaves the caret its room. */
.fg select{appearance:none;padding-right:34px;cursor:pointer;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238b8b98' stroke-width='1.5'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 13px center}
.fg select option{background:var(--surf);color:var(--ink)}
.fg input::placeholder,.fg textarea::placeholder{color:var(--ph);opacity:1}
.fg .hint{margin:6px 0 0;color:var(--acc2);font-size:12px}
/* A field that is not right yet. Said beside the field, while the form is still being
   filled in -- not collected at the bottom after the send. */
.fielderr{margin:6px 0 0;color:var(--acc2);font-size:12px}
.fg.bad input,.fg.bad textarea,.fg.bad select,.fg.bad .cb-view{border-color:var(--acc2)}
/* The searchable field. The list is ours to draw, so it is drawn in the form's own colours
   instead of the operating system's -- the native datalist popup is white on a dark form
   and no stylesheet reaches it. */
.cb{position:relative}
.cb-list{position:absolute;z-index:40;left:0;right:0;top:calc(100% + 4px);margin:0;padding:4px;
  max-height:264px;overflow-y:auto;list-style:none;background:var(--surf);color:var(--ink);
  border:1px solid var(--line);border-radius:var(--r);
  box-shadow:0 12px 32px rgba(0,0,0,.34)}
.cb-opt{padding:7px 10px;border-radius:calc(var(--r) - 2px);font-size:14px;cursor:pointer;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cb-opt:hover,.cb-opt.on{background:var(--acc);color:#fff}
/* The native picker arrow, for the seconds before the script runs and for anyone without
   it: the input is already a text field, and the arrow promises a menu we do not draw. */
.fg input[list]::-webkit-calendar-picker-indicator{display:none}
/* The mirrored select. The real one stays in the form -- it holds the value, it carries
   `required`, and the browser's "please choose" anchors to it -- so it is made invisible
   rather than hidden: a display:none required control makes Chrome refuse to submit with
   nothing but a console line to say why. */
.cb-sel .cb-native{position:absolute;inset:0;width:100%;height:100%;opacity:0;
  pointer-events:none;appearance:none;border:0;padding:0}
.cb-view{width:100%;padding:11px 34px 11px 13px;border:1px solid var(--line);
  border-radius:var(--r);background:var(--surf);color:var(--ink);cursor:pointer;
  font:clamp(14px,1.17vw,16px)/1.4 var(--ui);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238b8b98' stroke-width='1.5'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 13px center}
.cb-view:focus{outline:2px solid var(--acc);outline-offset:1px;border-color:var(--acc)}
.cb-view.cb-placeholder{color:var(--ph)}
.cb-opt.cb-empty{color:var(--dim)}
/* The agreement. A real checkbox with a real label, so the whole sentence is a hit target
   and a screen reader reads the sentence as the control's name. */
.agree{grid-column:1 / -1;margin:2px 0 10px}
.agree + .agree{margin-top:-4px}
.agree label{display:flex;gap:9px;align-items:flex-start;cursor:pointer;
  color:var(--dim);font-size:13px;line-height:1.5}
.agree input{margin-top:.18em;accent-color:var(--acc);width:16px;height:16px;flex:0 0 auto}
.agree a{color:var(--acc-text)}

.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.formerr{grid-column:1 / -1;margin:0 0 14px;padding:11px 13px;border-radius:var(--r);
  border:1px solid var(--acc2);color:var(--acc2);font-size:13px}
.turn{grid-column:1 / -1;display:flex;align-items:center;gap:12px;margin:2px 0 14px}
.cflabel{color:var(--dim);font-size:12.5px}
.fsend{grid-column:1 / -1;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.quiet{color:var(--dim);font-size:12.5px}
.privacy{grid-column:1 / -1;margin:20px 0 0;border-left:2px solid var(--line);padding-left:14px}
.sent h2{font:600 27px/1.2 var(--disp);margin:0 0 10px}
.sent p{color:var(--dim);margin:0}

/* footer */
footer{background:var(--head);border-top:1px solid var(--line)}
footer .wrap{padding-top:24px;padding-bottom:20px;font-size:clamp(12.5px,1.04vw,14.5px);color:var(--dim)}
/* The first column is half as wide again as the others: it carries five lines with a copy
   control on each, and the longest of them is an IBAN. The three beside it carry addresses
   and links, which are shorter. */
/* The first column is wider than the other three because it carries the longest lines --
   an IBAN and a BTW number, each with a copy control beside it. 1.35, down from 1.5: the
   numbers still fit on one line and the three address columns get the 10% back. */
.fcols{display:grid;grid-template-columns:1.35fr 1fr 1fr 1fr;gap:28px;align-items:start}
@media (max-width:900px){.fcols{grid-template-columns:repeat(2,1fr);gap:24px}}
@media (max-width:820px){.fcols{grid-template-columns:1fr;gap:22px}}
/* The column headings carry the accent: the violet on white, the lighter one on black,
   which is the same pair the positioning line and the service edge lines use. */
.fcol h4{margin:0 0 8px;font:600 12px/1 var(--ui);letter-spacing:.1em;
  text-transform:uppercase;color:var(--acc-text)}
/* The details are read, not typed back -- the copy control is what they are for -- so they
   are set in the page's own face. Only the LABELS are bold: KVK and BTW are what a reader
   scans for, and the numbers beside them are what they came for. */
.fcol .line{display:flex;gap:7px;align-items:center;margin-bottom:5px;flex-wrap:wrap}
.fcol .line b{font-weight:600;color:var(--ink)}
/* The three columns sit in the measure with the rest of the content. The bottom line is a
   band across the whole page: `50% - 50vw` on each side reaches the viewport edges from
   inside a centred column, and the body clips the scrollbar's worth of overflow. */
/* The bottom line is a band across the page and its CONTENT spans it too: the copyright
   at one edge, the links and languages at the other. Only the page's own 44px of side
   padding stands between them and the viewport. */
/* One compressed line: the copyright and the running build, nothing else. */
/* One line at every width. INLINE flow rather than flex: two spans in a line of text
   cannot become two stacked blocks, whatever the width does. */
.fbottom{display:block;
  margin:20px calc(50% - 50vw) 0;padding:14px 44px 0;
  border-top:1px solid var(--line)}
.fbottom nav{margin-left:auto;display:flex;gap:16px;flex-wrap:wrap}
/* One row is three things -- the notice, the build stamp and the links -- and on a narrow
   screen they wrap into each other rather than beside each other. Stacked, they read in
   the order they matter: who this is, what is deployed, where else to go. */
@media (max-width:760px){
  .fbottom{flex-direction:column;align-items:flex-start;gap:12px;
    padding-left:clamp(20px,6vw,44px);padding-right:clamp(20px,6vw,44px)}
  .fbottom nav{margin-left:0;gap:10px 14px}
  .fbottom .langs{margin-left:0}
}
/* The footer band is full-bleed, so it carries its own copy of the measure. It has to be
   the SAME copy, or the notice sits at a different margin from the paragraph above it and
   the page reads as two pages. Flat rather than nested inside the rule above: nesting one
   @media in another is legal and is one more thing for a reader to hold in their head. */
@media (max-width:560px){
  .fbottom{padding-left:calc(20px + 5vw);padding-right:calc(20px + 5vw)}
}
footer a{color:var(--dim);text-decoration:none}
footer a:hover{color:var(--ink)}
.build{font-family:var(--mono);font-size:11px;color:var(--dim);opacity:.75;white-space:nowrap;margin-left:10px}
.langs{display:inline-flex;gap:10px;font-family:var(--mono);letter-spacing:.04em}
.langs a[aria-current]{color:var(--ink)}

/* copy control (copyable-values) */
.cpw{display:inline-flex;align-items:center;gap:6px}
.cp{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;
  border-radius:5px;border:1px solid var(--line);background:transparent;color:var(--dim);
  cursor:pointer;padding:0}
.cp:hover{color:var(--ink);border-color:var(--dim)}
.cp:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
/* The confirmed state: the glyph becomes a tick, and the button fills with the accent so
   the change is legible at 20px without hunting for it. Shape first -- the fill is
   reinforcement, and colour alone would not carry it. */
.cp .ct{display:none}
.cp.done .ci{display:none}
.cp.done .ct{display:block}
.cp.done{color:var(--head);background:var(--acc-fill);border-color:var(--acc-fill);
  animation:cp-pop .18s ease-out}
@keyframes cp-pop{from{transform:scale(.8)}to{transform:scale(1)}}
@media (prefers-reduced-motion:reduce){.cp.done{animation:none}}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* The first-visit notice. A band at the foot of the window rather than a sheet over the
   page: nothing behind it is withheld, so blocking the page to say "we store nothing"
   would be theatre. It sits above the content, never covers the footer's links for more
   than a moment, and goes on the first click. */
.consent{position:fixed;left:0;right:0;bottom:0;z-index:60;background:var(--surf);
  border-top:1px solid var(--line);box-shadow:0 -8px 24px rgba(16,16,32,.10)}
html[data-theme="dark"] .consent{box-shadow:0 -8px 24px rgba(0,0,0,.45)}
.consent[hidden]{display:none}
/* Full width, not the measure. The band already spans the window; holding its contents to
   the text column left the buttons stranded in the middle with empty page either side. */
/* NOWRAP above the phone breakpoint. The words shrink and wrap inside their own block; the
   buttons never leave the line. With wrap on, the text block took the whole row and pushed
   Refuse and Accept onto a second line at the left, which is what "not aligned" was. */
.consent-in{padding:10px clamp(20px,4vw,44px);
  display:flex;gap:16px;align-items:center;flex-wrap:nowrap}
/* The words take what they need and no more; the buttons are pushed to the far edge, so
   the band reads as one line with a decision at the end of it.
   Both sides are centred against the SAME line rather than each relying on the default,
   which is what left the sentence sitting a couple of pixels above the buttons. */
/* Plain inline text, not a nested flex row: flex items refuse to shrink below their content
   by default, so the block could not give way and the row had to wrap. */
.consent-in > div{flex:1 1 auto;min-width:0}
.consent-do{margin-left:auto;align-items:center;flex:0 0 auto}
/* Title and sentence on ONE line: two stacked lines made the band twice as tall for a
   heading nobody needed on its own. */
.consent-h{display:inline;margin:0 .4em 0 0;font:600 13px/1.6 var(--disp)}
.consent-b{display:inline;margin:0;color:var(--dim);font-size:13px;line-height:1.6}
.consent-b a{color:var(--acc-text)}
/* A control that reads as a link because it goes where the links go, but is a button
   because it changes this page rather than loading another. */
.linkish{padding:0;border:0;background:none;color:var(--dim);font:inherit;cursor:pointer;
  text-align:left}
.linkish:hover{color:var(--ink)}
.linkish[hidden]{display:none}
/* The pair never breaks: two buttons stacked in a corner read as two separate decisions. */
.consent-do{display:flex;gap:10px;flex:0 0 auto;flex-wrap:nowrap}
.consent .btn{flex:0 0 auto}
@media (max-width:560px){
  .consent-in{padding:12px calc(20px + 5vw);gap:10px;flex-wrap:wrap}
  .consent-do{width:100%;margin-left:0;flex-wrap:nowrap}
  .consent .btn{flex:1 1 0;text-align:center}
}
/* It arrives a moment after the page, so it is not part of the first impression. */
.consent:not([hidden]){animation:consent-in .22s ease-out}
@keyframes consent-in{from{transform:translateY(100%)}to{transform:none}}
@media (prefers-reduced-motion:reduce){.consent:not([hidden]){animation:none}}

@media (max-width:820px){
  .hero .wrap{grid-template-columns:1fr;gap:24px;padding-top:30px}
  h1{font-size:34px}
  .art{height:190px}
}
/* The header's gap between 641 and 820 only. This block sits AFTER the narrow rules in the
   file, so an unbounded max-width:820px here quietly overrode both of them -- the phone was
   getting 14px however small the rules above set it. Bounded, so each range owns its own. */
@media (max-width:820px) and (min-width:641px){
  nav.main{gap:14px}
}

/* iOS zooms into any form control whose text is under 16px, and it does not zoom back out
   afterwards -- the visitor is left on a page wider than the screen, scrolling sideways to
   find the next field. The clamp above gives 14px on a phone, which is exactly the trigger.
   
   So: sixteen pixels on small screens, for every control including the ones we draw
   ourselves. The alternative fix -- maximum-scale=1 on the viewport -- stops the zoom by
   stopping ALL zoom, including the pinch somebody needs to read this at all
   ([[accessibility]]). Never that. */
@media (max-width:820px) {
  .fg input,.fg textarea,.fg select,.cb-view{font-size:16px}
}
