/* ============================================================================
   PLUMB — design system
   ----------------------------------------------------------------------------
   PASS 1  near-black ground, one warm accent, hairline-bordered rectangles on a
           grid. Rejected: the house style of every developer tool since 2023.

   PASS 2  warm bone paper, serif display, terracotta accent. Worse, and harder
           to see from the inside: cream + serif + orange is the single most
           recognisable signature of machine-generated design. "It looks very
           AI" was an accurate reading of the GROUND, not of the accent.

   PASS 3  the mark decided the system. Two stripes — one exactly vertical in
           ink, one leaning 11 degrees in orange. No third colour. So the system
           has no third colour either.

   ONE ACCENT. This is the whole idea and it is not negotiable.
     Orange means OUT OF TRUE. That is all it ever means.
     There is no "true" colour, because a surveyor does not paint "this is fine"
     on a wall. He marks the defect and leaves the rest alone. Correct things
     are simply not marked, which is both cheaper to read and more honest.
     Every other value in this file is a neutral.

   ANGLE CARRIES MEANING. Exactly vertical is true. Anything wrong is literally
   tilted, and the tilt is the severity. The logo is the legend for the UI.

   DARK MODE IS A MIRROR, NOT AN INVERSION. The light-mode paper is the
   dark-mode ink, so both modes are drawn from one set of pigments.
   ========================================================================== */

:root{
  /* --- the stock: uncoated white paper, warm by a hair. Not #FFF, which is a
         screen colour, not a paper one. Raised surfaces go to true white. --- */
  --paper:#F9F7F2;
  --paper-2:#F2EFE8;
  --paper-3:#EBE8E1;
  --card:#FFFFFF;
  --raise:#FFFFFF;

  /* --- litho ink: warm near-black, the colour of a printed page --------- */
  --ink:#17120E;
  --ink-2:#4A4038;
  --ink-3:#726659;      /* labels/captions. Cleared against --paper-2, the
                           DARKEST ground it sits on, not --paper: 4.86:1 there
                           and 5.21:1 on the page ground. Tuning a token against
                           the lightest surface is how it fails on the others. */
  --ink-4:#8F8175;      /* GRAPHICS ONLY — 3.29:1 on --paper-2. No text.  */

  /* --- marking orange: fluorescent, the second plate ---------------------
         --flag  is the ink itself: fills, rules, fields, the spine.
         --flag-ink is the same ink at text weight, because a fluorescent
         orange can never pass 4.5:1 against white and pretending otherwise
         is how a page ends up unreadable in daylight.                   --- */
  --flag:#FA5116;
  --flag-ink:#C43C0A;
  --flag-wash:#FFEDE4;
  --flag-line:#FFC3A5;
  --flag-deep:#B33208;

  --line:#E2DDD4;
  --line-soft:#F0ECE5;
  --thread:#C9C0B4;

  --serif:'Iowan Old Style','Palatino Linotype',Palatino,Charter,Georgia,serif;
  --sans:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  --mono:ui-monospace,'SF Mono',SFMono-Regular,Menlo,Consolas,monospace;

  --lift:0 1px 1px rgba(23,18,14,.03), 0 10px 26px rgba(23,18,14,.06);
  --lift-lg:0 2px 4px rgba(23,18,14,.05), 0 30px 66px rgba(23,18,14,.10);
  --pad:clamp(20px,4.5vw,40px);
}

/* Light is the default, always — not "light unless the operating system says
   otherwise". The prefers-color-scheme switch is deliberately absent: this
   page is a printed thing and it should look printed on every machine that
   opens it. Dark remains available, but only when a person asks for it. */
:root[data-theme="dark"]{
  --paper:#100D0B; --paper-2:#171310; --paper-3:#211C18; --card:#171310; --raise:#211C18;
  --ink:#F5F2EC;   --ink-2:#B8B0A6;   --ink-3:#867C72;   --ink-4:#6A6058;
  --flag:#FF6B38;  --flag-ink:#FF8B5C; --flag-wash:#35160B; --flag-line:#5E2C16;
  --flag-deep:#FF8B5C;
  --line:#2C2620;  --line-soft:#1F1A16; --thread:#463E36;
  --lift:0 1px 1px rgba(0,0,0,.4), 0 10px 30px rgba(0,0,0,.42);
  --lift-lg:0 2px 6px rgba(0,0,0,.5), 0 34px 74px rgba(0,0,0,.55);
}

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--sans);
  font-size:16.5px;line-height:1.6;-webkit-font-smoothing:antialiased;overflow-x:clip}
a{color:inherit;text-decoration:none}
:focus-visible{outline:2px solid var(--flag);outline-offset:3px}
h1,h2,h3{margin:0;font-weight:400;letter-spacing:-.028em;text-wrap:balance}
p{margin:0 0 16px}

/* ---------------------------------------------------------------- type --- */
.d1{font-family:var(--serif);font-size:clamp(42px,7.6vw,92px);line-height:.96;letter-spacing:-.035em}
.d2{font-family:var(--serif);font-size:clamp(30px,4.8vw,54px);line-height:1.02;letter-spacing:-.03em}
.d3{font-family:var(--serif);font-size:clamp(22px,2.8vw,30px);line-height:1.12}
.lede{font-size:clamp(17px,1.9vw,20px);line-height:1.55;color:var(--ink-2)}
.tag{font-family:var(--mono);font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-3)}
.num{font-family:var(--mono);font-variant-numeric:tabular-nums;letter-spacing:-.02em}

/* ==========================================================================
   THE MARK
   Two stripes of identical width and height from a common centre line. The
   left is exactly vertical: the datum. The right leans 11 degrees: a reading
   that is not true. Nothing else. There is no third element and no third
   colour, and the mark must never be redrawn with one.

   Geometry is fixed at these coordinates in a 0 0 24 24 box and is not to be
   re-derived by eye — 11 degrees and a 5.5 unit gap are the whole design.
   ========================================================================== */
.mark{display:block}
.mark .true{fill:var(--ink)}
.mark .out{fill:var(--flag)}
/* Single-colour rendering, for a stamp, a fax, or an embed with no palette. */
.mark.mono .out{fill:var(--ink)}

/* The lockup is a LINK everywhere it appears. It was a bare <span> on the auth
   screen, the coming-soon screen, /join, /verify and /bot, so on five of the
   product's own pages the logo was the one thing a visitor reliably clicks and
   the one thing that did nothing. A logo that does not go home is a painted
   control, which is the defect this product is sold to find. */
.lockup{display:inline-flex;align-items:center;gap:.42em;font-size:31px;
  color:inherit;text-decoration:none}
a.lockup:hover .wm{color:var(--flag)}
a.lockup{transition:none}
.lockup .mark{width:.92em;height:.92em;flex:none}
.lockup .wm{font-family:var(--serif);font-size:1em;letter-spacing:-.032em;line-height:1;
  white-space:nowrap;color:var(--ink)}
.lockup .sub{display:block;font-family:var(--mono);font-size:.26em;letter-spacing:.22em;
  text-transform:uppercase;color:var(--ink-3);margin-top:.28em;white-space:nowrap}
/* On hover the out-of-true stripe swings back to vertical. The mark performs
   the product rather than illustrating it. */
.lockup .out{transition:transform .62s cubic-bezier(.22,1.15,.36,1);transform-origin:65% 50%}
.lockup:hover .out{transform:rotate(11deg)}
@media (prefers-reduced-motion:reduce){.lockup:hover .out{transform:none}}

/* ---------------------------------------------------------- the thread --- */
/* Content hangs from a line. This replaces the bordered card almost
   everywhere: a border is the exception, a hanging thread is the default. */
.hang{position:relative;padding-top:30px}
.hang::before{content:'';position:absolute;left:var(--hx,14px);top:0;width:1px;height:22px;
  background:var(--thread)}
.hang::after{content:'';position:absolute;left:calc(var(--hx,14px) - 3px);top:20px;
  width:7px;height:7px;background:var(--ink);border-radius:50%}
.hang.off::after{background:var(--flag)}

/* Paper stock with one corner cut, the way a bob's tip is cut. */
.plate{background:var(--card);padding:22px 24px;position:relative;
  clip-path:polygon(0 0,100% 0,100% calc(100% - 16px),calc(100% - 16px) 100%,0 100%);
  box-shadow:var(--lift)}

/* --------------------------------------------------------------- rules --- */
.rule{height:1px;background:var(--line);border:0;margin:0}

/* ==========================================================================
   ONE RULE ABOUT CUT CORNERS, learned the hard way

   clip-path clips the STROKE as well as the fill. So an element that has a
   border, an outline or an inset shadow AND a cut corner comes out with its
   outline stopping dead along the diagonal — the corner is simply unstroked,
   and it reads as a rendering fault rather than a shape.

   Therefore: a shape is either FILLED and clipped, or STROKED and unclipped.
   Never both. Everything below obeys that, and anything added later must too.
   ========================================================================== */

/* ------------------------------------------------------------- buttons --- */
/* Cut corner, never a rounded rectangle. The primary action is solid ink:
   orange is reserved for things that are wrong, so it is never a call to
   action. On press the cut corner flashes orange — the one place the accent
   is allowed to be decorative, because it is over in 90ms. */
/* THE LABEL ON THE SOLID BUTTON IS PINNED, and it is the orange field's bug a
   second time. --ink swaps with the theme and --flag does not, so var(--ink)
   type on an orange fill measured 5.54:1 in light and 2.54:1 in dark. At 16px
   / weight 500 the floor is 4.5:1, and every solid button in the product went
   under it at once in dark mode: "Run it on your site" and "Snap the line" on
   the marketing page, "Mark resolved" and "Accepted risk" in the finding
   drawer. The literal below is the light-mode ink, and it is the only value
   that clears both grounds — 5.54:1 on #FA5116 and 6.56:1 on the dark theme's
   #FF6B38. Anything read off a token is correct in exactly one theme.
   .btn.ghost is not orange, so it keeps the token: 15.2:1 and 15.11:1. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;
  background:var(--flag);color:#17120E;border:0;padding:14px 24px;font-size:16px;
  font-weight:500;letter-spacing:-.005em;
  font-family:inherit;cursor:pointer;position:relative;
  clip-path:polygon(0 0,100% 0,100% calc(100% - 11px),calc(100% - 11px) 100%,0 100%);
  transition:transform .16s cubic-bezier(.2,.7,.3,1),background .16s}
/* the cut corner darkens on press, like a second impression landing. Pinned to
   the same ink as the label for the same reason: on the token it LIGHTENED in
   dark mode, which is the opposite of the second impression it is drawing. */
.btn::after{content:'';position:absolute;right:0;bottom:0;width:11px;height:11px;
  background:#17120E;clip-path:polygon(100% 0,100% 100%,0 100%);
  opacity:0;transition:opacity .12s}
.btn:hover{transform:translate(-1px,-2px)}
.btn:hover::after{opacity:1}
.btn:active{transform:translate(0,0)}
/* The secondary action is a FILLED plate, not an outline. An outline plus a
   cut corner cannot both survive (see the rule above), and a two-layer fake
   outline leaves a visible seam. A quieter fill is simpler and reads better
   beside a solid orange primary. */
.btn.ghost{background:var(--paper-3);color:var(--ink)}
.btn.ghost:hover{background:var(--ink);color:var(--paper)}
.btn.ghost::after{background:var(--flag)}
.btn.sm{padding:9px 15px;font-size:14px;
  clip-path:polygon(0 0,100% 0,100% calc(100% - 8px),calc(100% - 8px) 100%,0 100%)}

/* ---------------------------------------------------------------- chips --- */
.chip{display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:9.5px;
  letter-spacing:.14em;text-transform:uppercase;padding:5px 10px;background:var(--paper-2);
  color:var(--ink-2);
  clip-path:polygon(0 0,100% 0,100% calc(100% - 6px),calc(100% - 6px) 100%,0 100%)}
.chip.off{background:var(--flag-wash);color:var(--flag-ink)}
/* A finding that was raised and then killed: still orange, but hollow. */
.chip.struck{background:transparent;color:var(--ink-3);
  box-shadow:inset 0 0 0 1px var(--flag-line);text-decoration:line-through}

.wrap{max-width:1200px;margin:0 auto;padding:0 var(--pad)}

/* ==========================================================================
   REVEAL — a wipe from the datum, not a fade-up and not a tilt

   The obvious motion thesis here was "everything arrives out of plumb and
   settles to true". It is wrong, and the reason matters: rule 2 says ANGLE
   CARRIES MEANING. If ordinary paragraphs also arrive tilted, tilt stops being
   data and becomes decoration, and the severity system quietly dies.

   So structural content is never rotated. It is SNAPPED IN: revealed by a wipe
   running outward from the datum line, the way a chalk line marks timber.
   Rotation is reserved for things that are genuinely out of true.
   ========================================================================== */
.reveal{--wipe:0%}
html.anim .reveal{clip-path:inset(0 var(--wipe,100%) 0 0);opacity:.001}
html.anim .reveal.snapped{clip-path:inset(0 0 0 0);opacity:1;
  transition:clip-path .82s cubic-bezier(.16,.84,.3,1), opacity .3s ease}
html.anim .reveal.snapped{transition-delay:calc(var(--d,0) * 90ms)}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *{animation:none!important;transition:none!important}
  html.anim .reveal{clip-path:none;opacity:1}
}

/* ==========================================================================
   SEVERITY — the mark, used as data
   The logo is the legend. Every finding carries a miniature of it: one rule
   exactly vertical, one leaning. The lean IS the severity, so a column of
   findings reads as a fan and you sort it by eye before reading a word.

   The angles are fixed and meaningful. Nothing in this system is allowed to
   tilt for decoration, because if tilt can be decorative it stops being data.
   ========================================================================== */
.sev{display:block;flex:none}
.sev .true{fill:var(--ink-4)}
.sev .out{fill:var(--flag);transform-box:fill-box;transform-origin:center;
  transform:rotate(var(--tilt,0deg));
  transition:transform .78s cubic-bezier(.22,1.15,.36,1), fill .3s, opacity .3s}
/* The spread is wide on purpose. At 10 degrees against 14 a reader cannot tell
   critical from high at a glance, and a severity system you have to squint at
   is just a decoration. These four angles are distinguishable at 20px. */
.sev[data-sev="critical"]{--tilt:19deg}
.sev[data-sev="high"]    {--tilt:12deg}
.sev[data-sev="moderate"]{--tilt:6.5deg}
.sev[data-sev="low"]     {--tilt:2.5deg}
.sev[data-sev="resolved"]{--tilt:0deg}
.sev[data-sev="resolved"] .out{fill:none;stroke:var(--ink-4);stroke-width:1.2}
/* Before the engine has judged anything, everything hangs true. */
.unjudged .sev{--tilt:0deg!important}
/* A finding that was raised and then killed by verification: the mark goes
   hollow but keeps its angle, so you can still see how bad it would have been. */
.killed .sev .out{fill:none;stroke:var(--flag-line);stroke-width:1.3}

/* ==========================================================================
   FINDING — the most important object in the product
   ========================================================================== */
.find{display:grid;grid-template-columns:20px 1fr auto;gap:16px;align-items:start;
  padding:15px 0;border-bottom:1px solid var(--line-soft);position:relative}
.find:last-child{border-bottom:0}
.find .ttl{font-size:15px;line-height:1.4;color:var(--ink);margin:0}
.find .met{display:flex;flex-wrap:wrap;gap:9px;align-items:center;margin-top:6px;
  font-family:var(--mono);font-size:10px;letter-spacing:.11em;text-transform:uppercase;
  color:var(--ink-3)}
.find .met .mod{color:var(--ink-2)}
.find .met .dot{width:2px;height:2px;background:var(--ink-4);border-radius:50%}
.find .sfc{font-family:var(--mono);font-size:10px;letter-spacing:.09em;color:var(--ink-3);
  white-space:nowrap;padding-top:2px}
/* Evidence, shown inline. A finding without its quote is an accusation. */
.find .ev{grid-column:2/-1;margin:11px 0 2px;padding:10px 14px;background:var(--paper-2);
  border-left:2px solid var(--flag);font-family:var(--mono);font-size:12px;line-height:1.65;
  color:var(--ink-2);
  clip-path:polygon(0 0,100% 0,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%)}
.find .ev b{color:var(--ink);font-weight:400;background:var(--flag-wash);padding:1px 3px}
.find .ev cite{display:block;font-style:normal;color:var(--ink-3);margin-top:7px;font-size:10.5px;
  letter-spacing:.06em}
.find.killed .ttl{color:var(--ink-3);text-decoration:line-through;
  text-decoration-color:var(--flag-line);text-decoration-thickness:1px}
.find .why{grid-column:2/-1;margin-top:7px;font-family:var(--mono);font-size:11px;
  line-height:1.6;color:var(--ink-3)}
.find .why b{color:var(--flag-ink);font-weight:400;letter-spacing:.1em;text-transform:uppercase;
  font-size:9.5px;margin-right:7px}
/* The surface label is white-space:nowrap in an `auto` track, so it takes its
   full width out of the row before the 1fr middle column gets anything. On a
   wide row that is invisible; on a phone it inverts the hierarchy. Measured at
   390px on the free report, "website · structured data" claimed 173px of the
   294 available and left the finding's own title 121px — the label was wider
   than the finding. Below this width it moves under the finding it labels,
   which is also the only place it makes sense once the title is wrapping. */
@media (max-width:640px){
  .find{grid-template-columns:20px minmax(0,1fr);gap:6px 16px}
  .find > .sfc{grid-column:2;padding-top:0}
}

/* --------------------------------------------------------------- fields --- */
.field{display:flex;flex-direction:column;gap:7px}
.field label{font-family:var(--mono);font-size:9.5px;letter-spacing:.17em;text-transform:uppercase;
  color:var(--ink-3)}
/* No border, for the same reason as the outlined button: clip-path would cut
   it. A field is a filled shape sitting on the stock, and the fill is what
   defines it. Focus is carried by a real underline, which is inside the clip
   and therefore survives. */
/* A field is a filled shape sitting on the stock. No border, because the cut
   would clip it. Focus is painted with a background-image, which lives inside
   the clip and therefore follows the corner instead of being sliced by it. */
.input{background:var(--paper-3);border:0;color:var(--ink);
  font:inherit;font-size:15px;padding:11px 13px;width:100%;
  background-image:linear-gradient(90deg,var(--ink-4) 0 2px,transparent 2px);
  background-repeat:no-repeat;
  clip-path:polygon(0 0,100% 0,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%);
  transition:background-color .15s}
.input::placeholder{color:var(--ink-3)}
.input:focus{outline:none;background-color:var(--card);
  background-image:linear-gradient(90deg,var(--flag) 0 2.5px,transparent 2.5px)}


/* ==========================================================================
   THEME SWITCH
   Light is the default and the OS is not consulted, so this control is the
   only route to dark — it has to be present and obvious rather than clever.
   The icon is the page itself: stock on one half, ink on the other.
   ========================================================================== */
/* 34x24, not the 30x19 this shipped at. WCAG 2.2 AA 2.5.8 asks for 24x24 CSS
   pixels and 19 fails it; on a phone this is a 19px-tall target sitting inside
   an open menu panel, next to full-width 46px links. The switch cannot borrow
   a bigger hit area from a pseudo-element because overflow:hidden — which is
   what keeps the sliding ::before inside the frame — clips hit testing too, so
   the drawn object itself grows. The proportion is held: 30/19 and 34/24 are
   the same shape to within a hair. */
.themer{width:34px;height:24px;padding:0;border:1.5px solid var(--ink);background:none;
  cursor:pointer;position:relative;flex:none;display:inline-block;overflow:hidden}
.themer::before{content:'';position:absolute;inset:0;
  background:linear-gradient(90deg,var(--paper) 0 50%,var(--ink) 50% 100%);
  transition:transform .3s cubic-bezier(.3,.85,.35,1)}
:root[data-theme="dark"] .themer::before{transform:translateX(-50%) scaleX(2)}
.themer:hover{border-color:var(--flag)}
.themer:focus-visible{outline:2px solid var(--flag);outline-offset:2px}

/* ==========================================================================
   THE HERO BACKGROUND
   Sits behind content, never over it, and never intercepts a pointer. It is
   drawn once on mount, so if the animation loop never runs the picture is
   still there.
   ========================================================================== */
.driftlayer{position:absolute;inset:0;width:100%;height:100%;z-index:0;
  pointer-events:none;display:block;user-select:none}
.has-field{position:relative;isolation:isolate}
.has-field > *:not(.driftlayer){position:relative;z-index:1}
