:root{
  --red:#D2232A; --blue:#1E90FF; --green:#3FA535; --yellow:#FFD400;
  --ink:#111111; --gray:#52504B; --paper:#FAFAF7;
  /* UI-alt: the logo's colours on a light ground, none of them the field.
     Red is the one action; sand is the warm ground (hero sky, the price band);
     green means yes/included; Odoo's own purple marks anything Odoo, sparingly.
     Blue stays inside the pixel motifs. --sand is the hero sky exactly: the
     hero frames were recoloured to it by assets-src/recolor_hero.py.
     The shape system is pixels: no radius anywhere, hard offset shadows in
     whole pixels, 8px steps. Nothing blurred, nothing rounded. */
  --purple:#714B67; --sand:#EEDFB4; --lilac:#F2EBF0; --mint:#EAF4E4;
  /* the logo's blue, light: the price band and the form's hover - the hero
     sky's own colour, calm to read, and it leaves green to mean "selected"
     inside the form. (A cream, then a yellow, were both too loud here.) */
  --ice:#DDEEFF; --ice-2:#EEF6FF;
  /* the hero sky's darkest blue: the end band and the footer, so the page
     closes under the sky it opens with. White on it is 6:1. */
  --deep:#0B5EC2; --deep-ink:#DCEBFC;
  --line:#E6E2D8;
  --hard:4px 4px 0 var(--ink);
  /* three rows of 7px tiles on the 8px grid - the sky's own tile, gaps and
     all - thinning out upwards; a mask, so the colour bands crumble into
     pixels at their edges in whatever colour the band is */
  --px-edge:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='192' height='24'%3E%3Cpath d='M1 17h7v7h-7zM9 17h7v7h-7zM25 17h7v7h-7zM33 17h7v7h-7zM41 17h7v7h-7zM49 17h7v7h-7zM57 17h7v7h-7zM65 17h7v7h-7zM73 17h7v7h-7zM81 17h7v7h-7zM89 17h7v7h-7zM97 17h7v7h-7zM105 17h7v7h-7zM113 17h7v7h-7zM121 17h7v7h-7zM145 17h7v7h-7zM153 17h7v7h-7zM161 17h7v7h-7zM169 17h7v7h-7zM177 17h7v7h-7zM185 17h7v7h-7zM1 9h7v7h-7zM9 9h7v7h-7zM25 9h7v7h-7zM33 9h7v7h-7zM41 9h7v7h-7zM57 9h7v7h-7zM73 9h7v7h-7zM89 9h7v7h-7zM97 9h7v7h-7zM153 9h7v7h-7zM161 9h7v7h-7zM169 9h7v7h-7zM177 9h7v7h-7zM25 1h7v7h-7zM57 1h7v7h-7zM73 1h7v7h-7zM161 1h7v7h-7zM169 1h7v7h-7z'/%3E%3C/svg%3E");
}
*{margin:0;padding:0;box-sizing:border-box}

@font-face{font-family:'Space Grotesk';src:url('../fonts/space-grotesk-v22-latin-500.woff2') format('woff2');font-weight:500;font-display:swap}
@font-face{font-family:'Space Grotesk';src:url('../fonts/space-grotesk-v22-latin-700.woff2') format('woff2');font-weight:700;font-display:swap}
@font-face{font-family:'JetBrains Mono';src:url('../fonts/jetbrains-mono-v24-latin-regular.woff2') format('woff2');font-weight:400;font-display:swap}
@font-face{font-family:'JetBrains Mono';src:url('../fonts/jetbrains-mono-v24-latin-500.woff2') format('woff2');font-weight:500;font-display:swap}
@font-face{font-family:'JetBrains Mono';src:url('../fonts/jetbrains-mono-v24-latin-700.woff2') format('woff2');font-weight:700;font-display:swap}

html{scroll-behavior:smooth}
html,body{overflow-x:clip}
body{font-family:'Space Grotesk',sans-serif;background:var(--paper);color:var(--ink)}
/* The two brand faces are latin-only, so Chinese would fall back to whatever the
   OS picks - and for the mono stack that is usually an ugly full-width face.
   Name the CJK fallbacks explicitly; latin still renders in the brand fonts. */
html[lang^="zh"] body{font-family:'Space Grotesk','PingFang SC','Hiragino Sans GB',
  'Microsoft YaHei','Noto Sans SC',sans-serif}
html[lang^="zh"] .mono,html[lang^="zh"] .btn,html[lang^="zh"] .sub,html[lang^="zh"] .kicker,
html[lang^="zh"] .fcopy,html[lang^="zh"] .flang,html[lang^="zh"] label,
html[lang^="zh"] .nav-odoo,html[lang^="zh"] .bb-btn{
  font-family:'JetBrains Mono','PingFang SC','Hiragino Sans GB','Microsoft YaHei',
  'Noto Sans SC',monospace}
a{color:inherit;text-decoration:none}
.mono{font-family:'JetBrains Mono',monospace}
.wrap{max-width:1400px;margin:0 auto;padding-inline:clamp(20px,4vw,56px)}
section[id]{scroll-margin-top:80px}
:focus-visible{outline:2px solid var(--ink);outline-offset:2px}

.btn{display:inline-block;font-family:'JetBrains Mono',monospace;font-weight:700;
  font-size:15px;padding:18px 30px;border:0;cursor:pointer;
  transition:transform .12s,box-shadow .12s}
.btn-red{background:var(--red);color:#fff}
.btn-red:hover{transform:translate(-2px,-2px);box-shadow:4px 4px 0 var(--ink)}
/* "onred" is historical: these two were the white buttons on the red hero.
   The hero is light now, so they take the light-ground look - red primary,
   ink outline - under the old names, which saves rewriting the markup of
   every page and the generated translations with it. */
.btn-onred{background:var(--red);color:#fff}
.btn-onred:hover{transform:translate(-2px,-2px);box-shadow:4px 4px 0 var(--ink)}
/* The outline variant. Lived inline in privacy.html and again in 404.html,
   hand-rolled twice and already drifting; it is a button of this system like
   the other two, so it lives here. */
.btn-line{background:transparent;color:var(--ink);box-shadow:inset 0 0 0 2px var(--ink)}
.btn-line:hover{transform:translate(-2px,-2px);box-shadow:inset 0 0 0 2px var(--ink),4px 4px 0 var(--ink)}
.btn-line:active{transform:none;box-shadow:inset 0 0 0 2px var(--ink)}
.btn:active{transform:none;box-shadow:none}
/* The hero's secondary - the same ink outline as .btn-line now that the hero
   is light, with a white fill so it holds its own over the sand. It sits after
   .btn:active deliberately - that rule kills box-shadow at the same
   specificity (0,2,0) and wins on order, which is why .btn-line:active above
   it has never actually done anything. */
.btn-onred-line{background:#fff;color:var(--ink);box-shadow:inset 0 0 0 2px var(--ink)}
.btn-onred-line:hover{transform:translate(-2px,-2px);
  box-shadow:inset 0 0 0 2px var(--ink),4px 4px 0 var(--ink)}
.btn-onred-line:active{transform:none;box-shadow:inset 0 0 0 2px var(--ink)}

/* ---------- nav ---------- */
nav{position:fixed;inset:0 0 auto 0;z-index:50;display:flex;align-items:center;
  justify-content:space-between;height:68px;padding-inline:clamp(20px,4vw,56px);
  transition:background .25s ease,box-shadow .25s ease}
/* Over the light hero the nav is ink from the first frame, so the white
   variants of the logo and the Odoo mark are never used any more. */
.nav-odoo img.ow{display:none}
/* A plain paper bar with an ink rule, over the hero and after it: the sky
   behind is busy enough, and the build pushes red buildings up under the
   right-hand links, so they need a ground of their own. */
nav:not(.legal-nav):not(.book-nav){background:var(--paper);box-shadow:0 2px 0 var(--ink)}
.wordmark{font-family:'JetBrains Mono',monospace;font-weight:700;font-size:18px;
  letter-spacing:.08em;color:var(--ink)}
nav .btn{font-size:13px;font-weight:500;padding:11px 18px}

/* ---------- language, in the nav ---------- */
/* Wrapped with the CTA so <nav> still has three flex children and the Odoo chip
   stays centred. A <details> keeps this working with no JavaScript. */
.nav-right{display:flex;align-items:center;gap:clamp(10px,2vw,20px)}
.nportal{font-family:'JetBrains Mono',monospace;font-size:12.5px;color:var(--ink);
  text-decoration:none;white-space:nowrap;opacity:.85}
.nportal:hover{opacity:1;text-decoration:underline;text-underline-offset:3px}
.nlang{position:relative;font-family:'JetBrains Mono',monospace;font-size:12.5px}
.nlang>summary{list-style:none;cursor:pointer;color:var(--ink);padding:6px 2px;
  letter-spacing:.06em;white-space:nowrap}
.nlang>summary::-webkit-details-marker{display:none}
.nlang>summary::after{content:"";display:inline-block;margin-left:6px;
  border:4px solid transparent;border-top-color:currentColor;transform:translateY(2px)}
.nlang[open]>summary::after{transform:translateY(-2px) rotate(180deg)}
.nlang>div{position:absolute;right:0;top:calc(100% + 8px);background:#fff;
  border:2px solid var(--ink);box-shadow:var(--hard);min-width:170px;padding:6px 0;z-index:60}
.nlang>div a{display:block;padding:8px 14px;color:var(--ink);text-decoration:none;
  white-space:nowrap}
.nlang>div a:hover{background:var(--ink);color:var(--paper)}

/* ---------- hero ---------- */
/* The build used to take two extra screens of scroll and people sat in the
   hero not realising there was a page under it. One screen on a desktop,
   under half of one on a phone. */
.hero-stage{height:200vh}
.hero{height:100dvh;background:#fff;color:var(--ink);display:flex;
  flex-direction:column;justify-content:center;
  padding:84px clamp(20px,4vw,56px) 48px;overflow:hidden;position:sticky;top:0}
/* the sky canvas at the back, the art (sky cut out) over it, copy on top */
.hero-art{position:absolute;inset:0;display:block;z-index:1}
.hero-art img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center}
.hero-frames picture{position:absolute;inset:0;display:block}
.hero-frames .f-d,.hero-frames .f-m{opacity:0}
.f-m{display:none}
@media (max-width:767px){
  .hero-stage{height:145vh}
  .f-d{display:none}
  .f-m{display:block}
}
@media (prefers-reduced-motion: reduce){
  .hero-stage{height:auto}
  .hero{position:relative;min-height:100dvh;height:auto}
}
.build-bar{position:absolute;left:0;right:0;bottom:0;z-index:3}
.bb-btn{position:absolute;right:clamp(20px,4vw,56px);bottom:26px;display:flex;
  align-items:center;gap:10px;font-family:'JetBrains Mono',monospace;font-size:11px;
  font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--ink);
  background:#fff;border:0;padding:13px 18px;cursor:pointer;
  box-shadow:inset 0 0 0 2px var(--ink),var(--hard);transition:opacity .4s}
.bb-btn:hover{box-shadow:inset 0 0 0 2px var(--ink),6px 6px 0 var(--ink)}
/* The Spanish label is 46 characters, which is 441px wide at desktop metrics.
   Pinned to the right edge only, it would hang 71px off the left of a 390px
   phone. Anchoring both edges lets it wrap to two lines instead, which is
   fine for an instruction - it is a hint, not a CTA. */
@media (max-width:640px){
  .bb-btn{left:20px;right:20px;justify-content:center;text-align:center;
    font-size:10px;letter-spacing:.08em;padding:11px 14px;line-height:1.5}
}
.bb-arrow{width:0;height:0;border-left:6px solid transparent;
  border-right:6px solid transparent;border-top:8px solid var(--ink)}
@media (prefers-reduced-motion: no-preference){
  .bb-btn{animation:bbbob 2.2s ease-in-out infinite}
  @keyframes bbbob{0%,100%{translate:0 0}50%{translate:0 -7px}}
}
.bb-track{display:block;position:relative;height:12px;background:rgba(17,17,17,.08)}
.bb-track::after{content:"";position:absolute;right:0;top:0;bottom:0;width:14px;background:var(--ink)}
.bb-fill{display:block;height:100%;width:0;background:repeating-linear-gradient(90deg,
  #D2232A 0 14px,#000 14px 28px,#3FA535 28px 42px,#D2232A 42px 56px,#FFD400 56px 70px,
  #1E90FF 70px 84px,#000 84px 98px,#3FA535 98px 112px,#D2232A 112px 126px,#FFD400 126px 140px,
  #1E90FF 140px 154px)}
@media (prefers-reduced-motion: reduce){.build-bar{display:none}}
.hero-sky{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:0}
/* the pointer's force field, drawn over the art and under the copy */
.hero-fx{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:1}
.hero-content{position:relative;z-index:2;max-width:1400px;margin-inline:auto;width:100%}
/* On a phone the copy sits over the sky, as on a desktop: the sky canvas
   pales out around the headline, the line under it and the buttons, so the
   type reads without blocks behind it. */
@media (max-width:767px){
  .hero{justify-content:flex-start;padding-top:116px}
}
.hero h1{font-size:clamp(34px,4.6vw,64px);font-weight:700;line-height:1;
  letter-spacing:-.02em}
/* 15ch, not the 22 it was: at 22 the headline ran on into the crane, which
   was fine white-on-black and is unreadable ink-on-black. */
.hero h1 span{display:block;max-width:15ch}
.hero .sub{font-family:'JetBrains Mono',monospace;font-size:clamp(13px,1.2vw,16px);
  line-height:1.7;margin-top:22px;color:#3d3b37}
.hero .sub span{display:block;max-width:52ch}
.hero .cta-row{margin-top:30px;display:flex;flex-wrap:wrap;gap:12px}
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto}
}

/* ---------- pixel strip ---------- */
.strip{display:flex;height:8px}
.strip .bar{flex:1;background:var(--ink)}
.strip i{width:14px;flex:none}

/* ---------- problem ---------- */
.problem{padding-block:clamp(72px,10vw,128px)}
.problem p.big{font-size:clamp(26px,3.2vw,46px);font-weight:700;letter-spacing:-.02em;
  line-height:1.2;max-width:26ch}
.problem p.big+p.big{margin-top:.4em}
/* The scribble runs out of this section and down into the AI band. z-index 1
   puts it over the band's colour; .ai .wrap at 2 keeps the AI copy and demo
   over the line, as they were when the band had no colour. */
.problem{position:relative;z-index:1}
@media (max-width:1023px){.problem .wrap{padding-left:clamp(84px,24vw,120px)}}
.problem .wrap{position:relative;z-index:1}
.scribble{position:absolute;top:0;left:0;z-index:0;pointer-events:none}
.mchip{position:absolute;width:40px;height:40px;background:#fff;border:1.5px solid #c9c6bd;
  display:grid;place-items:center;z-index:0;opacity:0;transform:scale(.5);
  transition:opacity .45s cubic-bezier(.16,1,.3,1),transform .45s cubic-bezier(.16,1,.3,1)}
.mchip.on{opacity:1;transform:scale(1)}
.mchip img{width:20px;height:20px;opacity:.55;display:block}
.mchip b{font-family:'JetBrains Mono',monospace;font-size:10px;color:#6f6c66}
.mchip span{position:absolute;top:100%;left:50%;transform:translateX(-50%);margin-top:4px;
  font-family:'JetBrains Mono',monospace;font-size:9px;color:#8a877f;white-space:nowrap}
@media (prefers-reduced-motion: reduce){.mchip{transition:none}}
.mtools{display:flex;flex-wrap:wrap;gap:8px;margin-top:34px;max-width:245px}
.mtools i{width:34px;height:34px;background:#fff;border:1.5px solid #c9c6bd;
  display:grid;place-items:center;font-style:normal}
.mtools img{width:18px;height:18px;opacity:.55;display:block}
.mtools b{font-family:'JetBrains Mono',monospace;font-size:9.5px;color:#6f6c66}
.mtools .more{border-style:dashed}
.mtools .more b{font-size:13px;letter-spacing:.5px;color:#8a877f}
@media (max-width:374px){.mtools{gap:7px;max-width:228px}
  .mtools i{width:32px;height:32px}}
.mtools.anim i{opacity:0;transform:scale(.5);
  transition:opacity .45s cubic-bezier(.16,1,.3,1),transform .45s cubic-bezier(.16,1,.3,1)}
.mtools.anim i.on{opacity:1;transform:scale(1)}
@media (min-width:1160px){.mtools{display:none}}
@media (min-width:1160px){.problem{min-height:660px;display:flex;align-items:center}
  .problem .wrap{width:100%}}
/* The resolution line, as one white brick: the scattered pieces above,
   clicked together. Studs run evenly along its whole top edge, each an ink
   outline that breaks the brick's top line the way a brick is drawn in pixel
   art, with a sliver of shade down its right side - not four loose green
   rectangles floating over it, which is what this used to be.
   background-repeat:space keeps every stud whole whatever the width. */
.problem .kicker-block{
  position:relative;margin-top:52px;max-width:54ch;
  border:2px solid var(--ink);background:#fff;
  padding:26px 26px 24px;box-shadow:var(--hard);
}
.problem .kicker-block::before{
  content:"";position:absolute;left:10px;right:10px;top:-12px;height:12px;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='12'%3E%3Crect x='10' y='1' width='20' height='12' fill='%23fff'/%3E%3Crect x='25' y='2' width='4' height='10' fill='%23E6E2D8'/%3E%3Cpath d='M11 12V1h18v11' fill='none' stroke='%23111' stroke-width='2' stroke-linejoin='miter'/%3E%3C/svg%3E") space no-repeat 0 0/40px 12px;
}
.problem .kicker{font-family:'JetBrains Mono',monospace;color:var(--ink);
  font-size:clamp(13px,1.1vw,15px);line-height:1.65;margin:0}
.mtools+.kicker-block{margin-top:44px}

/* The studs settle in as the section reveals - the snap the sentence
   describes. One motion, 320ms, and nothing else on the page moves. */
@media (prefers-reduced-motion: no-preference){
  .problem .kicker-block::before{
    transform:translateY(8px);opacity:0;
    transition:transform .32s steps(4) .25s,opacity .32s steps(4) .25s;
  }
  .problem.in .kicker-block::before{transform:none;opacity:1}
}
@media (max-width:640px){
  .problem .kicker-block{padding:22px 20px 20px;margin-top:40px}
}

/* ---------- what we build ---------- */
.build{padding-block:clamp(72px,10vw,128px);border-top:1px solid var(--line)}
.build h2{font-size:clamp(30px,3.6vw,52px);font-weight:700;letter-spacing:-.02em;max-width:20ch}
.build .lead{color:var(--gray);max-width:56ch;line-height:1.6;margin-top:16px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-top:56px}
/* Four separate blocks, not one ink grid: the section says "assembled from four
   blocks". On hover each one lifts off a shadow in its own colour - red, green,
   yellow, purple, the swatch side by side - and it moves in two whole-pixel
   steps rather than a glide. */
.cell{background:#fff;border:2px solid var(--ink);--c:var(--red);
  transition:transform .14s steps(2),box-shadow .14s steps(2)}
.cell:nth-child(2){--c:var(--green)}
.cell:nth-child(3){--c:var(--yellow)}
.cell:nth-child(4){--c:var(--purple)}
.cell:hover{box-shadow:6px 6px 0 var(--c)}
@media (prefers-reduced-motion: no-preference){
  .cell:hover{transform:translate(-4px,-4px);box-shadow:10px 10px 0 var(--c)}
}
.cell-art{display:block;width:100%;height:auto;border-bottom:2px solid var(--ink)}
.cell-body{padding:26px 28px 36px}
.px{width:16px;height:16px;display:block;margin-bottom:22px}
.cell h3{font-size:22px;font-weight:700;letter-spacing:-.01em}
.cell p{font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.65;
  color:var(--gray);margin-top:10px;max-width:44ch}
@media (max-width:767px){.grid{grid-template-columns:1fr}}

/* ---------- how we work ---------- */
.how{padding-block:clamp(72px,10vw,128px);border-top:1px solid var(--line)}
.how h2{font-size:clamp(30px,3.6vw,52px);font-weight:700;letter-spacing:-.02em}
.how .rows{margin-top:48px}
.how .row{display:grid;grid-template-columns:minmax(220px,1fr) 2fr;gap:24px;
  padding-block:28px;border-top:1px solid var(--line)}
.how .row:last-of-type{border-bottom:1px solid var(--line)}
.how .row h3{font-size:21px;font-weight:700}
.how .row p{font-family:'JetBrains Mono',monospace;font-size:13.5px;line-height:1.7;
  color:var(--gray);max-width:60ch}
@media (max-width:767px){.how .row{grid-template-columns:1fr;gap:8px}}
@media (max-width:1023px){.scribble path{stroke:var(--red)}}
@media (max-width:767px){nav .nav-right .btn{display:none}}

/* ---------- process ---------- */
.process{padding-block:clamp(72px,10vw,128px);border-top:1px solid var(--line)}
.process h2{font-size:clamp(30px,3.6vw,52px);font-weight:700;letter-spacing:-.02em}
.steps{list-style:none;display:grid;grid-template-columns:repeat(4,1fr);gap:32px;
  margin-top:64px;position:relative}
/* the line between the steps is a row of pixels, not a rule */
.steps::before{content:"";position:absolute;left:8px;right:calc((100% - 96px)/4 - 8px);
  top:6px;height:4px;background:repeating-linear-gradient(90deg,var(--ink) 0 4px,transparent 4px 12px)}
.steps li{position:relative}
.steps .px{position:relative;z-index:1;margin-bottom:20px;outline:6px solid var(--paper)}
@media (prefers-reduced-motion: no-preference){
  .steps li{transition:transform .18s cubic-bezier(.16,1,.3,1)}
  .steps .px{transition:transform .18s cubic-bezier(.16,1,.3,1)}
  .steps li:hover{transform:translateY(-5px)}
  .steps li:hover .px{transform:scale(1.35)}
}
.steps h3{font-size:19px;font-weight:700}
.steps p{font-family:'JetBrains Mono',monospace;font-size:12.5px;line-height:1.65;
  color:var(--gray);margin-top:8px}
@media (max-width:767px){
  .steps{grid-template-columns:1fr;gap:40px}
  .steps::before{left:6px;right:auto;top:4px;bottom:0;width:4px;height:auto;
    background:repeating-linear-gradient(180deg,var(--ink) 0 4px,transparent 4px 12px)}
  .steps li{padding-left:44px}
  .steps li:last-child::after{content:"";position:absolute;top:24px;bottom:0;left:6px;width:4px;
  background:var(--paper)}
  .steps .px{position:absolute;left:0;top:4px}
  .steps li:hover{transform:none}
}

/* ---------- proof ---------- */
.proof{padding-block:clamp(72px,10vw,128px);border-top:1px solid var(--line)}
/* The partner badge is Odoo's, so it wears Odoo's purple, as a tint */
.badge{display:inline-flex;align-items:center;gap:14px;border:2px solid var(--purple);
  background:var(--lilac);color:#3d2836;
  padding:16px 22px;font-family:'JetBrains Mono',monospace;font-weight:700;font-size:15px}
.badge img{display:block}
.facts{list-style:none;margin-top:40px}
.facts li{font-family:'JetBrains Mono',monospace;font-size:14px;color:var(--ink);
  padding-block:16px;border-top:1px solid var(--line);max-width:640px}
.facts li:last-child{border-bottom:1px solid var(--line)}

/* ---------- faq ---------- */
.faq{padding-block:clamp(72px,10vw,128px);border-top:1px solid var(--line)}
.faq h2{font-size:clamp(30px,3.6vw,52px);font-weight:700;letter-spacing:-.02em}
.faq .items{margin-top:40px;max-width:820px}
.faq details{border-top:1px solid var(--line)}
.faq details:last-child{border-bottom:1px solid var(--line)}
.faq summary{cursor:pointer;list-style:none;display:flex;justify-content:space-between;
  align-items:center;gap:16px;padding-block:20px;font-weight:700;font-size:17px}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{content:"+";font-family:'JetBrains Mono',monospace;font-weight:700;
  font-size:20px;color:var(--ink)}
.faq details[open] summary::after{content:"-"}
.faq details p{font-family:'JetBrains Mono',monospace;font-size:13.5px;line-height:1.7;
  color:var(--gray);padding-bottom:22px;max-width:64ch}

/* ---------- end band + footer ---------- */
.end{background:var(--deep);color:#fff;padding-block:clamp(72px,9vw,110px);position:relative}
.end h2{font-size:clamp(30px,4vw,56px);font-weight:700;letter-spacing:-.02em;max-width:18ch}
.end .cta-row{margin-top:36px}
.end .btn-red:hover{box-shadow:4px 4px 0 rgba(255,255,255,.25)}
footer{background:var(--deep);position:relative}
/* the band crumbles into the page above in pixels, like the AI and price
   bands; over the end band the footer's edge is blue on blue, so it only
   shows on pages where the footer comes straight after the content */
.end::before,footer::before{content:"";position:absolute;left:0;right:0;bottom:100%;height:24px;
  background:var(--deep);pointer-events:none;
  -webkit-mask:var(--px-edge) repeat-x left bottom/192px 24px;mask:var(--px-edge) repeat-x left bottom/192px 24px}
/* Where a reader actually looks for an address. Both are our partners'
   premises, not Unravel offices - which is also why neither has a Google
   Business Profile: Google wants your own staffed presence, and a listing at
   a partner's address gets suspended. */
.faddr{display:flex;justify-content:center;gap:56px;flex-wrap:wrap;align-items:flex-start;
  padding:34px 20px 0;font-family:'JetBrains Mono',monospace;font-size:12px;
  line-height:1.7;color:var(--deep-ink)}
/* 46ch, not 38: the Jakarta address is half as long again as Gijón's and
   took three lines at 38. At 46 both are two even lines. */
.faddr address{font-style:normal;max-width:46ch;display:flex;flex-direction:column;gap:10px;
  text-wrap:balance}   /* two even lines instead of one long and one stub */
/* Each address carries its city's landmark as a pixel skyline, generated by
   assets-src/footer_skylines.py in the hero sky's tile language: Gijón is the
   Cimadevilla houses, San Pedro's tower and the Santa Catalina headland over
   the sea; Jakarta is a tower cluster around Monas and its gold flame. 3-unit
   tiles on a 4-unit grid, drawn at 1:1, so every tile is three whole pixels
   with a one-pixel gap. */
.fcity{display:block;width:223px;height:auto}
@media (max-width:760px){
  .faddr{flex-direction:column;gap:16px;text-align:center;padding-top:28px}
  .faddr address{max-width:none;align-items:center}
}
.fcopy{text-align:center;font-family:'JetBrains Mono',monospace;font-size:12.5px;
  color:var(--deep-ink);padding:22px 20px 30px}
.cf-turnstile,.ts-slot{margin-top:16px}
/* "My Portal" was splitting across lines, leaving "Mi" alone at the end of
   one and "portal" starting the next. A link label is one thing. */
.fcopy a,.flang a{white-space:nowrap}
.fcopy a{color:var(--deep-ink);text-decoration:underline;text-underline-offset:3px}
.fcopy a:hover{color:#fff}
.flang{text-align:center;font-family:'JetBrains Mono',monospace;font-size:12px;
  color:var(--deep-ink);margin:-18px 0 0;padding:0 20px 28px}
.flang b{color:#fff;font-weight:400}
.flang a{color:var(--deep-ink);text-decoration:underline;text-underline-offset:3px}
.flang a:hover{color:#fff}

/* ---------- reveals ---------- */
.reveal{opacity:0;transform:translateY(18px);
  transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
.reveal.in{opacity:1;transform:none}
@media (prefers-reduced-motion: reduce){
  .reveal{opacity:1;transform:none;transition:none}
}

/* ---------- ai / mcp ---------- */
/* ---------- colour bands ----------
   Two sections get a ground of their own, the way the reference gives each
   section one colour family: the AI demo on Odoo's purple (it is a demo of
   Odoo), the price on the logo's yellow. Their top and bottom edges break up into the
   site's pixels instead of ending on a rule - the same 8px square as the logo,
   cut out of the band colour by --px-edge. */
.ai,.cost{position:relative;--band:var(--lilac)}
.cost{--band:var(--ice)}
.ai::before,.ai::after,.cost::before,.cost::after{content:"";position:absolute;left:0;right:0;
  height:24px;background:var(--band);pointer-events:none;
  -webkit-mask:var(--px-edge) repeat-x 0 0/192px 24px;mask:var(--px-edge) repeat-x 0 0/192px 24px}
.ai::before,.cost::before{bottom:100%}
.ai::after,.cost::after{top:100%;transform:scaleY(-1)}
.cost::before,.cost::after{-webkit-mask-position:96px 0;mask-position:96px 0}
.ai+section,.cost+section{border-top:0}
.ai{padding-block:clamp(72px,10vw,128px);background:var(--band)}
.ai .wrap{position:relative;z-index:2}
.ai h2{font-size:clamp(30px,3.6vw,52px);font-weight:700;letter-spacing:-.02em;max-width:20ch}
.ai .lead{color:var(--gray);max-width:56ch;line-height:1.6;margin-top:16px}
.mcp{position:relative;display:grid;align-items:center;margin-top:56px;
  grid-template-columns:minmax(300px,440px) 1fr 72px 1fr auto}
.mcp-chat{border:2px solid var(--ink);background:#fff;box-shadow:var(--hard)}
.mcp-head{display:flex;align-items:center;gap:10px;padding:12px 16px;
  border-bottom:1px solid var(--ink);font-family:'JetBrains Mono',monospace;
  font-size:12px;color:var(--gray)}
.mcp-head img{display:block}
.mcp-thread{padding:18px 16px;min-height:118px}
.mcp-thread .q{font-weight:700;font-size:15.5px}
.mcp-thread .a{font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.65;
  color:var(--gray);margin-top:10px;min-height:2.6em}
.mcp-chips{display:flex;flex-wrap:wrap;gap:8px;padding:0 16px 16px}
.mcp-chips button{font-family:'JetBrains Mono',monospace;font-size:12px;cursor:pointer;
  background:#fff;border:1px solid var(--ink);padding:8px 12px;color:var(--ink);
  transition:background .12s,color .12s}
.mcp-chips button:hover{background:var(--ink);color:#fff}
.mcp-chips button.pressed{background:var(--ink);color:#fff}
.mcp-chips button:disabled{opacity:.45;cursor:default;background:#fff;color:var(--ink)}
.mcp-wire{height:2px;background:var(--ink)}
.mcp-hub{width:72px;height:72px;background:var(--ink);color:#fff;display:grid;
  place-items:center;font-family:'JetBrains Mono',monospace;font-weight:700;font-size:15px;
  position:relative;transition:transform .15s}
.mcp-hub::before,.mcp-hub::after{content:"";position:absolute;top:-8px;width:16px;height:8px;
  background:var(--ink)}
.mcp-hub::before{left:13px}
.mcp-hub::after{right:13px}
.mcp-hub.hit{transform:scale(1.12)}
.mcp-hub small{display:block;font-size:8px;font-weight:400;letter-spacing:.1em;
  text-transform:uppercase;color:rgba(255,255,255,.75);margin-top:2px}
.mcp-modules{display:grid;grid-template-columns:1fr 1fr;gap:2px;background:var(--ink)}
.mod{background:#fff;padding:16px 18px;display:flex;align-items:center;gap:10px;
  font-family:'JetBrains Mono',monospace;font-size:13px;transition:background .15s,color .15s;
  border:0;color:inherit;cursor:pointer;text-align:left}
/* these are Odoo's modules, so lighting one up is Odoo purple */
.mod:hover{background:var(--lilac)}
.mod.on{background:var(--purple);color:#fff}
.mod .px{width:12px;height:12px;margin:0}
.packet{position:absolute;width:12px;height:12px;margin:-6px 0 0 -6px;background:var(--yellow);
  box-shadow:0 0 0 1.5px var(--ink);
  opacity:0;z-index:2;transition:left .4s linear,top .4s linear,opacity .15s}
.packet.go{opacity:1}
@media (max-width:1023px){
  .mcp{grid-template-columns:1fr;justify-items:center;gap:0}
  .mcp-chat{width:100%}
  .mcp-wire{width:2px;height:34px}
  .mcp-modules{width:100%;margin-top:0}
  .mcp-hub{margin:0}
}
@media (prefers-reduced-motion: reduce){
  .packet{display:none}
  .mcp-hub.hit{transform:none}
}

/* ---------- nav brand + odoo ---------- */
.brand{display:flex;align-items:center;gap:12px}
/* The pixel logo on its own, straight on whatever is behind it - no tile.
   The plate only ever existed to lift it off the red. crispEdges keeps the
   cells as hard squares at any zoom instead of antialiasing their seams. */
.brand .plate{display:block}
.brand .lw{display:none}
.nlogo{height:24px;width:auto;display:block;shape-rendering:crispEdges}
.nav-odoo{display:flex;align-items:center;gap:9px;font-family:'JetBrains Mono',monospace;
  font-size:12.5px;letter-spacing:.05em;color:var(--gray);text-decoration:none}
/* Odoo's own partner page. On hover it lifts into a sticker in Odoo's purple,
   in whole-pixel steps like every other button here, with a small arrow to
   say it opens Odoo's site. The padding is always there so nothing moves. */
a.nav-odoo{padding:7px 12px;border:2px solid transparent;
  transition:transform .12s steps(2),box-shadow .12s steps(2),background .12s,border-color .12s,color .12s}
a.nav-odoo::after{content:"\2197";font-size:12px;margin-left:-2px;opacity:0;transition:opacity .12s}
a.nav-odoo:hover,a.nav-odoo:focus-visible{border-color:var(--purple);background:var(--lilac);
  color:var(--purple);transform:translate(-2px,-2px);box-shadow:3px 3px 0 var(--purple);outline:none}
a.nav-odoo:hover::after,a.nav-odoo:focus-visible::after{opacity:1}
a.nav-odoo:active{transform:none;box-shadow:none}
.nav-odoo img{display:block}
@media (max-width:679px){.nav-odoo{display:none}}

/* ---------- mcp odoo container ---------- */
.mcp-odoo{border:2px solid var(--ink);background:#fff;box-shadow:var(--hard)}
.mcp-odoo-head{display:flex;align-items:center;justify-content:center;gap:10px;padding:11px 16px;
  border-bottom:2px solid var(--ink);font-family:'JetBrains Mono',monospace;
  font-size:12px;color:var(--gray)}
.mcp-odoo-head img{display:block}
.mcp-more{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.02em;
  color:var(--gray);text-align:center;padding:10px 12px;border-top:1px solid var(--line)}

/* ---------- risk-free + end note ---------- */
/* the guarantee is a "yes", so it sits on green's tint */
.riskfree{margin-top:44px;display:flex;align-items:center;gap:14px;
  border:2px solid var(--ink);background:var(--mint);
  padding:18px 22px;max-width:660px;
  font-family:'JetBrains Mono',monospace;font-size:13.5px;line-height:1.55}
.riskfree .px{flex:none;margin:0}
.end-note{font-family:'JetBrains Mono',monospace;font-size:12.5px;color:var(--deep-ink);margin-top:22px}

/* ---------- currency picker ---------- */
.curr{display:flex;flex-wrap:wrap;align-items:center;gap:10px;padding-bottom:22px}
.curr-select,.curr-detect{font-family:'JetBrains Mono',monospace;font-size:12.5px;
  border:1px solid var(--ink);background:#fff;color:var(--ink);padding:9px 12px}
.curr-detect{cursor:pointer;background:var(--ink);color:#fff;border-color:var(--ink);
  transition:background .12s}
.curr-detect:hover{background:#3d3b37}
.curr-out{flex-basis:100%;font-family:'JetBrains Mono',monospace;font-size:13px;
  line-height:1.6;color:var(--ink);min-height:1.4em}
.curr-out:empty{display:none}

/* ---------- small-screen nav ---------- */
@media (max-width:479px){
  .wordmark{font-size:12.5px;letter-spacing:.04em}
  .nlogo{height:22px}
  nav .btn{font-size:11.5px;padding:10px 12px}
}

/* ---------- end band contact form ---------- */
.end-grid{display:grid;grid-template-columns:1.1fr 1fr;gap:56px;align-items:start}
@media (max-width:900px){.end-grid{grid-template-columns:1fr}}
.end-form .ef-title{font-weight:700;font-size:19px;letter-spacing:-.01em;color:#fff}
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.field{margin-top:14px}
.field label{display:block;font-family:'JetBrains Mono',monospace;font-size:12px;
  font-weight:700;margin-bottom:6px}
.end-form .field label{color:#fff}
.field input,.field textarea,.field select{width:100%;font-family:'JetBrains Mono',monospace;
  font-size:13.5px;padding:11px 12px;border:1px solid var(--ink);background:var(--paper);
  color:var(--ink)}
.end-form .field input,.end-form .field textarea{border-color:var(--ink)}
.end-form .field input:focus-visible,.end-form .field textarea:focus-visible{outline-color:#fff}
.field textarea{min-height:96px;resize:vertical}
.field .err{outline:2px solid var(--red)}
.actions{margin-top:20px;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.status{font-family:'JetBrains Mono',monospace;font-size:12.5px;color:#8a877f}
.status.bad{color:#ff8a8a}
.end .status{color:var(--deep-ink)}
.end .status.bad{color:var(--yellow)}
.status a{text-decoration:underline}
.done-box{border:1px solid #3a3a3a;padding:22px;font-family:'JetBrains Mono',monospace;
  font-size:14px;line-height:1.8;color:#d8d5cd}
.done-box b{display:block;font-family:'Space Grotesk',sans-serif;font-size:20px;
  color:#fff;letter-spacing:-.01em;margin-bottom:8px}
.done-box.ok{border:2px solid var(--green);background:#1b3012;color:#d8e8cd}
.done-box.ok b{color:#9fdb8a}

/* The answer to "what does it cost" used to sit at 86% of the page, inside the
   FAQ. Nine of the first fourteen paid clicks came from people searching the
   price; they were being asked to scroll past everything to find out that the
   answer is "we quote you". Same words, moved to where the question is asked. */
/* Now a section among sections rather than a strip under the hero, so it takes
   the site's own rhythm: 1px top rule and the shared padding-block, like .ai and
   .build either side of it. Its own 2px bottom border used to sit directly
   against .build's 1px top one, which read as one uneven thick line.
   The 760px column stays - the calculator inside it would be absurd at 1400. */
.cost{background:var(--band);padding-block:clamp(72px,10vw,128px)}
.cost .wrap{max-width:760px;margin:0 auto}
.cost h2{font-size:clamp(30px,3.6vw,52px);letter-spacing:-.02em;margin:0 0 18px}
.cost p{margin:0;font-size:16px;line-height:1.75;color:#4a4740;max-width:62ch}

/* Quote request. Built by site.js into "#cost .wrap" in every language; see the
   comment on that block for why it replaced the price calculator. The same
   pixel vocabulary as the rest: a 2px ink window with a hard shadow, square
   options, a selected answer marked with a green pixel, and the progress shown
   as four pixels that fill in the logo's colours.
   Every <p> here is scoped under .qf: ".cost p" (0,1,1) sets margin:0,
   font-size:16px and line-height:1.75 on anything inside #cost and beats a
   bare class (0,1,0) - the calculator was bitten by exactly that, twice. */
.qf{margin-top:30px;border:2px solid var(--ink);background:#fff;box-shadow:var(--hard)}
.qf-head{display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:14px 18px;border-bottom:2px solid var(--ink)}
.qf .qf-title{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:19px;
  line-height:1.2;color:var(--ink);letter-spacing:-.01em}
.qf-meta{display:flex;align-items:center;gap:12px;font-family:'JetBrains Mono',monospace;
  font-size:11.5px;color:var(--gray);white-space:nowrap}
.qf-prog{display:flex;gap:4px}
.qf-prog i{width:12px;height:12px;border:2px solid var(--ink);background:#fff}
.qf-prog i.on:nth-child(1){background:var(--red)}
.qf-prog i.on:nth-child(2){background:var(--green)}
.qf-prog i.on:nth-child(3){background:var(--yellow)}
.qf-prog i.on:nth-child(4){background:var(--purple)}
.qf-body{padding:20px 18px 22px}
.qf .qf-q{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:18px;
  line-height:1.3;color:var(--ink);outline:none}
.qf .qf-hint{font-family:'JetBrains Mono',monospace;font-size:12px;line-height:1.6;
  color:var(--gray);margin:6px 0 14px;max-width:62ch}
.qf .qf-sub{font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:700;
  line-height:1.5;color:var(--ink);margin:18px 0 10px}
.qf .qf-sub:first-of-type{margin-top:14px}
.qf-opts{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.qf-opts.qf-3{grid-template-columns:repeat(3,1fr)}
.qf-opts.qf-chips{display:flex;flex-wrap:wrap}
.qf-opt{position:relative;display:flex;flex-direction:column;gap:4px;text-align:left;
  font-family:'JetBrains Mono',monospace;cursor:pointer;background:#fff;
  border:1px solid var(--ink);color:var(--ink);padding:11px 26px 11px 12px;
  transition:background .12s}
.qf-opt:hover{background:var(--ice-2)}
.qf-opt[aria-pressed="true"]{background:var(--mint);box-shadow:inset 0 0 0 1px var(--ink)}
.qf-opt[aria-pressed="true"]::after{content:"";position:absolute;top:6px;right:6px;
  width:8px;height:8px;background:var(--green)}
.qf-wide{grid-column:1/-1}
.qf-opt-head{display:flex;align-items:flex-start;gap:8px}
.qf-ico{width:18px;height:18px;flex-shrink:0;margin-top:1px}
.qf-opt-label{font-size:13px;font-weight:700;line-height:1.4}
.qf-opt-desc{font-size:11px;line-height:1.5;opacity:.72}
.qf-chips .qf-opt{padding:9px 24px 9px 12px}
.qf-fields{display:grid;grid-template-columns:1fr 1fr;gap:0 14px;margin-top:4px}
.qf-ts{margin-top:16px;min-height:65px}
.qf-desc{margin-top:22px}
.qf-desc textarea{min-height:110px}
.qf .qf-desc .qf-hint{margin:0 0 8px}
.qf .qf-small{font-family:'JetBrains Mono',monospace;font-size:11.5px;line-height:1.6;
  color:var(--gray);margin-top:12px;max-width:62ch}
.qf .qf-err{font-family:'JetBrains Mono',monospace;font-size:12.5px;line-height:1.5;
  color:#B3141B;margin-top:14px}
.qf-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:14px 18px;border-top:2px solid var(--ink);background:var(--paper)}
.qf-foot .btn{font-size:13px;padding:13px 22px}
.qf-foot .btn:disabled{opacity:.6;cursor:default;transform:none;box-shadow:none}
.qf-done{padding:6px 0 4px}
.qf-done b{display:block;font-size:24px;letter-spacing:-.01em;margin-bottom:8px}
.qf .qf-done p{font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.7;color:var(--ink)}
.qf-call{display:inline-block;margin-top:16px;font-family:'JetBrains Mono',monospace;
  font-size:13px;font-weight:700;text-decoration:underline;text-underline-offset:3px}
@media (max-width:640px){
  .qf-opts,.qf-opts.qf-3,.qf-fields{grid-template-columns:1fr}
  .qf-head{flex-wrap:wrap}
  .qf-foot .btn{flex:1;text-align:center}
}

/* ---------- Odoo 20 keynote -------------------------------------------
   A click-to-load facade, not a YouTube iframe. An iframe contacts
   youtube.com and sets its cookies the moment the page renders, which would
   quietly make a liar of the consent bar we show EU visitors - they would be
   tracked before answering it - and it drags about a megabyte of player onto
   a page most people never watch a video on. So the poster is served from our
   own origin, nothing leaves the browser until the button is pressed, and at
   that point pressing play IS the consent. The embed then uses
   youtube-nocookie.com. See privacy.html, which says exactly this. */
.odoo20{background:var(--paper);padding-block:clamp(48px,7vw,96px)}
.odoo20 .wrap{max-width:900px;margin:0 auto}
.odoo20 h2{font-size:clamp(30px,3.6vw,52px);letter-spacing:-.02em;margin:0 0 14px}
.odoo20 .lead{font-size:16px;line-height:1.75;color:var(--gray);max-width:62ch}
.vid{position:relative;aspect-ratio:16/9;margin-top:26px;border:2px solid var(--ink);
  background:var(--ink);overflow:hidden;box-shadow:var(--hard)}
.vid button{position:absolute;inset:0;width:100%;height:100%;padding:0;border:0;
  background:none;cursor:pointer;display:block}
.vid img{width:100%;height:100%;object-fit:cover;display:block}
.vid iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
/* The same square-sticker language as the buttons, rather than YouTube's own
   rounded badge: this is our page, and the chrome should look like it. Odoo's
   purple, because it is Odoo's keynote. */
.vid-play{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:74px;height:74px;display:grid;place-items:center;background:var(--purple);
  transition:transform .14s,box-shadow .14s}
.vid-play svg{width:28px;height:28px;fill:#fff;margin-left:3px}
.vid button:hover .vid-play{transform:translate(-50%,-50%) translate(-2px,-2px);
  box-shadow:4px 4px 0 var(--ink)}
.vid button:active .vid-play{transform:translate(-50%,-50%);box-shadow:none}
.vid button:focus-visible{outline:3px solid var(--purple);outline-offset:3px}
/* Readable to a screen reader, invisible on screen: the triangle already says
   "play" to everyone who can see it. */
.vid-label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);
  white-space:nowrap}
@media (max-width:640px){.vid-play{width:58px;height:58px}.vid-play svg{width:22px;height:22px}}

/* ---------- cookie notice ----------------------------------------------
   Built by js/analytics.js and only ever shown to a visitor the edge places
   inside the EU, so most people never see these rules at all. A bar, not a
   modal: it does not block the page, which is the whole point of showing it
   only where people are used to it.

   Both buttons are the same size, in the same row, one click each. Making
   "refuse" harder than "accept" is the thing regulators actually fine, and it
   would be a lie about how little we lose when someone refuses. */
.cc{position:fixed;inset:auto 0 0 0;z-index:60;background:#fff;
  border-top:2px solid var(--ink);padding:18px clamp(20px,4vw,56px);
  display:flex;flex-wrap:wrap;align-items:center;gap:14px 24px}
.cc p{font-size:14.5px;line-height:1.5;color:var(--ink);flex:1 1 340px;max-width:74ch}
/* underlined always, not just on hover: in a consent notice the way to the
   detail has to be findable, and an unstyled link reads as plain text */
.cc a{color:var(--ink);text-decoration:underline;text-underline-offset:2px}
.cc-acts{display:flex;gap:10px;flex-shrink:0}
.cc button{font-family:'JetBrains Mono',monospace;font-weight:700;font-size:13px;
  letter-spacing:.04em;padding:14px 22px;border:0;cursor:pointer;
  transition:transform .12s,box-shadow .12s}
/* ink, not red: red is kept for the one thing the page is asking for */
.cc-yes{background:var(--ink);color:#fff}
.cc-no{background:var(--paper);color:var(--ink);box-shadow:inset 0 0 0 2px var(--ink)}
.cc-yes:hover{transform:translate(-2px,-2px);box-shadow:4px 4px 0 var(--ink)}
.cc-no:hover{transform:translate(-2px,-2px);box-shadow:inset 0 0 0 2px var(--ink),4px 4px 0 var(--ink)}
.cc button:active{transform:none;box-shadow:none}
.cc-no:active{box-shadow:inset 0 0 0 2px var(--ink)}
@media (max-width:680px){
  .cc{padding:16px 20px;gap:12px}
  .cc-acts{width:100%}
  .cc button{flex:1}
}

/* ---------- utility pages (privacy, 404) --------------------------------
   Both pages are the same shape: a thin nav back to the site, one column of
   prose, the shared footer. They each carried their own copy of these rules
   until 2026-10-03; one definition is one place to change them. Page-specific
   bits still live inline on each page, which is the right amount of local. */
.legal-nav{display:flex;align-items:center;height:64px;border-bottom:1px solid var(--line);
  background:var(--paper)}   /* fixed over the text it scrolls past */
.legal-nav .wrap{width:100%}
.legal-nav a{font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--ink);
  text-decoration:none;letter-spacing:.08em}
.legal-nav a:hover{text-decoration:underline}
/* Short pages (the 404 especially) left the footer floating mid-viewport with
   dead space under it, which reads as a layout bug rather than as space. The
   column pins it to the bottom; on a long page like privacy it changes nothing.
   100dvh, not 100vh, so the mobile address bar does not push it off. */
.util-page{display:flex;flex-direction:column;min-height:100dvh}
.util-page main{flex:1 0 auto}
.util-page footer{margin-top:auto}
.legal{padding-block:clamp(48px,7vw,96px)}
.legal h1{font-size:clamp(30px,3.6vw,52px);font-weight:700;letter-spacing:-.02em}
.legal p{color:var(--gray);line-height:1.65;max-width:62ch;margin-top:10px}

/* ---------- service pages ------------------------------------------------
   /odoo-implementation and its three siblings, generated by
   assets-src/service_pages.py. Long-form articles for search, in the same
   pixel language as the home page: 2px ink boxes, hard shadows, square
   bullets in the logo's colours, one red action repeated down the page. */
.svc-page main{padding-top:68px}
.svc-hero{padding-block:clamp(40px,6vw,88px) clamp(40px,5vw,72px);border-bottom:2px solid var(--ink);
  background:var(--paper)}
.svc-hero-grid{display:grid;grid-template-columns:1.1fr 1fr;gap:clamp(28px,4vw,64px);align-items:center}
.svc-crumbs{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--gray);
  display:flex;gap:8px;flex-wrap:wrap;margin-bottom:22px}
.svc-crumbs a{text-decoration:underline;text-underline-offset:3px}
.svc-hero h1{font-size:clamp(34px,4.4vw,60px);font-weight:700;line-height:1.02;letter-spacing:-.02em;
  max-width:16ch;text-wrap:balance}
.svc-lead{font-family:'JetBrains Mono',monospace;font-size:clamp(13.5px,1.15vw,15.5px);line-height:1.7;
  color:#3d3b37;margin-top:22px;max-width:56ch}
.svc-hero .cta-row{margin-top:30px;display:flex;flex-wrap:wrap;gap:12px}
.svc-note{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--gray);margin-top:14px}
.svc-art{display:block;width:100%;height:auto;border:2px solid var(--ink);box-shadow:var(--hard);
  background:#F7F8F3}
@media (max-width:860px){.svc-hero-grid{grid-template-columns:1fr}.svc-art{max-width:520px}}

.svc-body{padding-block:clamp(48px,7vw,96px)}
.svc-col{max-width:820px}
.svc-body h2{font-size:clamp(26px,2.8vw,38px);font-weight:700;letter-spacing:-.02em;line-height:1.1;
  margin-top:clamp(48px,6vw,72px);max-width:24ch}
.svc-body h2:first-child{margin-top:0}
.svc-body p{font-size:17px;line-height:1.7;color:#2f2d2a;margin-top:16px;max-width:66ch}
.svc-body h2+p{margin-top:20px}
/* square pixel bullets, cycling through the logo's colours */
.svc-list{list-style:none;margin-top:22px;display:grid;gap:12px;max-width:66ch}
.svc-list li{position:relative;padding-left:28px;font-family:'JetBrains Mono',monospace;
  font-size:14.5px;line-height:1.6;color:var(--ink)}
.svc-list li::before{content:"";position:absolute;left:0;top:.42em;width:12px;height:12px;
  background:var(--red);box-shadow:inset 0 0 0 2px rgba(17,17,17,.18)}
.svc-list li:nth-child(4n+2)::before{background:var(--green)}
.svc-list li:nth-child(4n+3)::before{background:var(--yellow)}
.svc-list li:nth-child(4n+4)::before{background:var(--purple)}
/* the steps as a timeline, read top to bottom: a square node per step in the
   logo's colours, joined by a rail of pixels like the home page's step line */
.svc-timeline{list-style:none;margin-top:36px;max-width:66ch}
.svc-timeline li{position:relative;padding:0 0 40px 60px}
.svc-timeline li:last-child{padding-bottom:0}
.svc-timeline li::before{content:"";position:absolute;left:10px;top:36px;bottom:4px;width:4px;
  background:repeating-linear-gradient(180deg,var(--ink) 0 4px,transparent 4px 12px)}
.svc-timeline li:last-child::before{display:none}
.tl-node{position:absolute;left:0;top:2px;width:24px;height:24px;background:var(--c);
  border:2px solid var(--ink);box-shadow:3px 3px 0 var(--ink)}
.tl-when{display:block;font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:var(--gray);line-height:1.2;margin-bottom:6px}
.svc-timeline h3{font-size:22px;font-weight:700;line-height:1.2;letter-spacing:-.01em}
.svc-body .svc-timeline p{font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.7;
  color:var(--gray);margin-top:8px}
/* the point a paragraph must not get lost in: a mint brick with a green edge */
.svc-callout{margin-top:28px;background:var(--mint);border:2px solid var(--ink);
  box-shadow:var(--hard);padding:22px 26px 24px;border-left:10px solid var(--green);max-width:66ch}
.svc-callout h3{font-size:21px;font-weight:700;line-height:1.25}
.svc-body .svc-callout p{margin-top:10px;font-size:16px;color:var(--ink)}
/* 80/20: a hundred tiles, one per cent, on a white brick */
.svc-split{margin-top:36px;display:grid;grid-template-columns:auto 1fr;gap:28px 40px;align-items:center}
.split-grid{display:grid;grid-template-columns:repeat(10,1fr);gap:4px;width:300px;aspect-ratio:1;
  padding:10px;background:#fff;border:2px solid var(--ink);box-shadow:var(--hard)}
.split-grid i{background:var(--purple);box-shadow:inset -3px -3px 0 rgba(17,17,17,.22)}
.split-grid i.cus{background:var(--yellow)}
.split-key{display:grid;gap:26px}
.svc-body .split-item{margin:0;display:grid;grid-template-columns:auto 1fr;column-gap:16px;row-gap:4px;
  align-items:baseline;max-width:none}
.split-item b{grid-row:span 2;font-size:44px;font-weight:700;letter-spacing:-.03em;line-height:1;
  display:flex;align-items:center;gap:12px}
.split-item b::before{content:"";width:18px;height:18px;flex:none;background:var(--purple);
  box-shadow:inset -3px -3px 0 rgba(17,17,17,.22)}
.split-item.cus b::before{background:var(--yellow)}
.split-name{font-size:19px;font-weight:700;color:var(--ink)}
.split-desc{font-family:'JetBrains Mono',monospace;font-size:13.5px;line-height:1.6;color:var(--gray)}
/* the tiles lay themselves one by one when the chart scrolls in */
@media (prefers-reduced-motion: no-preference){
  .svc-split.reveal{opacity:1;transform:none}
  .svc-split.reveal .split-grid i{opacity:0}
  .svc-split.reveal.in .split-grid i{animation:tile-in 1ms calc(var(--i) * 9ms + 150ms) forwards}
  .svc-split.reveal.in .split-grid i.cus{animation-delay:calc(var(--i) * 9ms + 450ms)}
}
@keyframes tile-in{to{opacity:1}}
@media (max-width:640px){
  .svc-split{grid-template-columns:1fr}
  .split-grid{width:100%;max-width:340px}
  .split-item b{font-size:36px}
}
/* the mid-article action: one white brick with the red button */
.svc-cta{margin-top:clamp(40px,5vw,56px);border:2px solid var(--ink);background:#fff;box-shadow:var(--hard);
  padding:24px 26px;display:flex;align-items:center;justify-content:space-between;gap:18px 28px;flex-wrap:wrap}
.svc-body .svc-cta p{margin:0;font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:20px;
  line-height:1.3;color:var(--ink);max-width:34ch}
.svc-body .svc-link{margin-top:18px;font-family:'JetBrains Mono',monospace;font-size:14px}
.svc-link a{font-weight:700;text-decoration:underline;text-underline-offset:4px}
.svc-link a::after{content:" \2192"}
.svc-faq{border-top:2px solid var(--ink)}
.svc-more{padding-block:clamp(56px,7vw,96px);border-top:2px solid var(--ink)}
.svc-more h2{font-size:clamp(26px,2.8vw,38px);font-weight:700;letter-spacing:-.02em}
.svc-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;margin-top:32px}
.svc-card{display:flex;flex-direction:column;background:#fff;border:2px solid var(--ink);
  --c:var(--red);transition:transform .14s steps(2),box-shadow .14s steps(2)}
.svc-card:nth-child(2){--c:var(--green)}
.svc-card:nth-child(3){--c:var(--purple)}
.svc-card:hover{transform:translate(-4px,-4px);box-shadow:8px 8px 0 var(--c)}
.svc-card img{display:block;width:100%;height:auto;border-bottom:2px solid var(--ink)}
.svc-card-body{padding:18px 20px 22px;display:flex;flex-direction:column;gap:8px}
.svc-card-body b{font-size:19px}
.svc-card-body span{font-family:'JetBrains Mono',monospace;font-size:12.5px;line-height:1.6;color:var(--gray)}
@media (max-width:900px){.svc-cards{grid-template-columns:1fr}}
.svc-page .end .cta-row{margin-top:30px}

/* the home page's four cards open their article: the title is the link and
   its ::after stretches over the whole card */
.cell{position:relative}
.cell h3 a::after{content:"";position:absolute;inset:0}
.cell h3 a:focus-visible{outline:none}
.cell:has(h3 a:focus-visible){outline:2px solid var(--ink);outline-offset:3px}
.cell h3 a::before{content:"\2192";float:right;margin-left:12px;font-family:'JetBrains Mono',monospace;
  font-size:18px;transition:transform .14s steps(2)}
.cell:hover h3 a::before{transform:translateX(4px)}
.svc-cta .btn{white-space:nowrap}
@media (max-width:520px){.svc-cta .btn{width:100%;text-align:center}}
