/* The family's fonts, tokens and base rules: paper and ink, a serif to read, pixel mono for labels.
   A site's stylesheet is this file, then its brand's skin (skins/<brand>.css), then its own rules (kit.py css()). */
/* Instrument Sans (reading, UI, plots and figures; variable, 400-700), Literata (titles: h1 and h2), Departure Mono
   (labels). Self-hosted, with their licences in fonts/. */
@font-face { font-family: "Instrument Sans"; src: url(/fonts/InstrumentSans-latin.woff2) format("woff2"); font-style: normal; font-weight: 400 700; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Instrument Sans"; src: url(/fonts/InstrumentSans-latin-ext.woff2) format("woff2"); font-style: normal; font-weight: 400 700; font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Instrument Sans"; src: url(/fonts/InstrumentSans-italic-latin.woff2) format("woff2"); font-style: italic; font-weight: 400 700; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Instrument Sans"; src: url(/fonts/InstrumentSans-italic-latin-ext.woff2) format("woff2"); font-style: italic; font-weight: 400 700; font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: Literata; src: url(/fonts/literata-400-normal.woff2) format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: Literata; src: url(/fonts/literata-400-italic.woff2) format("woff2"); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: Literata; src: url(/fonts/literata-500-normal.woff2) format("woff2"); font-weight: 500; font-display: swap; }
@font-face { font-family: Literata; src: url(/fonts/literata-600-normal.woff2) format("woff2"); font-weight: 600 700; font-display: swap; }
@font-face { font-family: "Departure Mono"; src: url(/fonts/DepartureMono-Regular.woff2) format("woff2"); font-display: swap; }
@property --i { syntax: "<integer>"; inherits: true; initial-value: 0; } /* an element's place in its group (js/motion.js stagger()): computed where it's set, then inherited */

:root {
  color-scheme: light;
  /* aluminium: a soft cool grey page (the owner: "#f1f3f5, subtle grey") around a lighter sheet (no cream) */
  --desk: #f1f3f5; --sheet: #ffffff; /* the sheet white: on #f1f3f5 a grey sheet vanished; the tinted shadow separates */ --thead: #e2e6ea; --glass: color-mix(in srgb, var(--desk) 96%, transparent); --blur: 20px; /* frosted bars: nearly opaque, a wide blur,
     so what passes under one is a faint even haze, not smudges (at 78% with 16 px a headline became dirty blots; checked at 92/8, 96/20, solid) */
  --ink: #101316; --text: #1b1f23; --mut: #4d555d; --faint: #5a636c; --ghost: #b8bfc6; /* bullets, empty marks */
  --line: #c6ccd2; --grid: #e2e6ea;
  /* signals: the base colour is text (4.5:1 on the sheet and the desk), -bg/-t a tint, -line the border of a tinted box,
     -ink text on the tint. --blue is the brand's accent (links, major titles, highlights): a skin sets its own */
  --blue: #2f43ff; --blue-d: #1a2bd6; --blue-t: #e3e7fb; --blue-line: #c5ccff; --blue-ink: #1e2a9e;
  --title: var(--ink); /* major titles (h1, section h2s): ink (the owner: "mark them as black"); the accent stays for marks and links */
  --ok: #007154; --ok-bg: #dcefe8; --ok-line: #b5e3d2; --ok-ink: #0b4d3b;
  --warn: #7a4f00; --warn-bg: #fbefc8; --bad: #b42d14; --bad-bg: #fbe1da;
  /* fills for squares, bars and marks (too light for text), and their text colours (4.5:1 on the sheet and the desk) */
  --red: #ff4d2e; --yellow: #ffb800; --green: #00a57a; --pink: #e0249a; --violet: #7c3aed; --amber: #f2a900; /* amber: a skin's accent */
  --red-tx: #b4361c; --yellow-tx: #8a5a00; --pink-tx: #b01f84; --violet-tx: #6d28d9;
  --on-fill: #fff; /* text and strokes on a saturated fill (a verdict's mark, a chart's bar): white here, dark at night */
  --hair: rgb(0 0 0 / .14); --shade: rgb(0 0 0 / .08); /* hairlines and soft shadows */
  --sans: "Instrument Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; /* reading and headlines */
  --serif-t: Literata, Georgia, "Times New Roman", serif; /* titles (h1, h2): the owner's pick, 2026-09-28; never running text */
  --pix: "Departure Mono", ui-monospace, monospace;
  --mono: ui-monospace, "Cascadia Code", Consolas, "SFMono-Regular", monospace;
  --plot: var(--sans); /* plots and figures speak the site's type (the owner: "using different typography makes no sense") */
  --label: 11px/1.5 var(--pix); /* every label: uppercase, .02em */
  /* offset shadows under boxes: soft and tinted with the brand's accent, as old ethorns' blue ones (never solid black);
     --drop-blue a little stronger, for the box that matters most on a page (a search, a tool) */
  --drop: color-mix(in srgb, var(--blue) 18%, transparent); --drop-blue: color-mix(in srgb, var(--blue) 30%, transparent);
  /* sizes: every font size a site writes is one of these (kit.py css() fails on any other) */
  --fs-label: 11px; --fs-small: 13px; --fs-ui: 15px; --fs-body: 17px; --fs-lead: 19px; --fs-h3: 21px; --fs-h2: 26px;
  --fs-h1: clamp(28px, 3.6vw, 38px); --fs-display: clamp(36px, 5vw, 54px);
  --fs-poster: clamp(42px, 5.8vw, 80px); /* a page's one shout, poster-size: tufotopies' headline and its reveal */
  /* plots and figures (pgfplots, Ipe): a boxed axis with inward ticks, thin strokes, one dotted rule at most, light fills;
     the series start with the brand's accent (--p1), then red, amber, violet, green, grey, each also told by dash and marker */
  --plot-axis: var(--ink); --plot-axis-w: .8px; --plot-tick: 5px; --plot-grid: color-mix(in srgb, var(--ink) 18%, transparent); --plot-fill: 26%;
  --p1: var(--blue); --p2: #c8322a; --p3: #a86a00; --p4: #7a4fd6; --p5: #0a7d45; --p6: #6b7079;
  /* the reading column and its two gaps: --gap from a header to its content and between boxes, --section above a heading */
  --col: 800px; --gap: 28px; --section: 44px;
  --sheet-w: 840px; /* an inner page's sheet (kit.bar(), .sheet): a site whose column is the sheet sets none */
  --hd-h: 65px; /* the frozen header's height: an anchor lands this far down, plus 16 px (a site with another header sets its own) */
  --gutter: 12px; --hd-row: 56px; /* phones: the header's side gutter (the sheet's) and its row (--hd-h: the row and its line) */
}

/* ---------- what every page starts from ---------- */
* { box-sizing: border-box; }
[hidden] { display: none !important; } /* a class that sets display would override it */
/* No caret in text you can't edit (the owner saw one after a heading): text stays selectable and copyable, only the
   blinking bar goes. Fields keep theirs. Trade-off: with caret browsing on (F7) the caret is invisible outside fields. */
html { -webkit-text-size-adjust: 100%; caret-color: transparent; scroll-padding-top: calc(var(--hd-h) + 16px); } /* an anchor lands clear of the frozen header */
:is(input, textarea, select, [contenteditable]:not([contenteditable=false])) { caret-color: auto; }
body { margin: 0; background: var(--desk); color: var(--text); font: var(--fs-body)/1.65 var(--sans); }
h1, h2 { font-family: var(--serif-t); } /* titles in Literata; a site's own title rules name var(--serif-t) too */
a { color: var(--blue); text-decoration: none; }
a:hover { color: var(--blue-d); text-decoration: underline; }
/* a link in running text is underlined: its colour alone is too close to the text's (WCAG 1.4.1). Not in menus and
   contents lists (nav), nor links a site styles itself (a class) */
:is(p, li, dd):not(nav *) > a:not([class]) { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .18em; }
:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
/* Controls are pressed, not read: a pointer over them, and clicking (twice) never selects their words, so nothing
   looks like a text box you could type in. Text in the page stays selectable; fields keep their caret. */
:where(button, summary, select, label, [role=button]) { cursor: pointer; -webkit-user-select: none; user-select: none; }
/* A frame label (menus, buttons, the language link) at one size in every language (kit.py same()): its longest
   translation sits invisibly in the same cell, so switching language moves nothing. Out of the accessibility tree. */
.same { display: inline-grid; }
.same > *, .same::after { grid-area: 1 / 1; }
.same::after { content: attr(data-twin); visibility: hidden; }
.px { font: var(--label); text-transform: uppercase; letter-spacing: .02em; } /* a label; a site may give its own label elements the same three lines */
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } /* for screen readers only */

/* The skip link (kit.py skip_link()): the page's first link, out of sight until the keyboard reaches it. */
.skip { position: absolute; left: 16px; top: -64px; z-index: 60; padding: 10px 14px; background: var(--sheet); color: var(--ink);
  border: 1px solid var(--ink); box-shadow: 3px 3px 0 var(--drop); font: var(--label); text-transform: uppercase; letter-spacing: .02em; }
.skip:focus { top: 12px; }
.skip:hover { color: var(--ink); text-decoration: none; }

/* The reader's choices (js/prefs.js): the theme button a site places (kit.py theme_button()) says what a click turns
   on, after a half-filled disc that is the same in both themes (a filled square read as "dark mode is on"); it's shown
   only where prefs.js runs, which marks <html class=prefs>
   before the first paint, so nothing moves when it appears. And the hint that offers this page in the reader's
   language, in a corner, until it's dismissed. */
.theme { display: none; align-items: center; gap: 7px; min-height: 0; padding: 0; background: none; border: 0; box-shadow: none; color: inherit; cursor: pointer; }
.prefs .theme { display: inline-flex; }
.theme::before { content: ""; width: 9px; height: 9px; border: 1px solid currentColor; border-radius: 50%; background: linear-gradient(90deg, currentColor 50%, transparent 0); }
.theme .to-light { visibility: hidden; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme=light]) .theme .to-dark { visibility: hidden; }
:root:not([data-theme=light]) .theme .to-light { visibility: visible; }
}
:root[data-theme=dark] .theme .to-dark { visibility: hidden; }
:root[data-theme=dark] .theme .to-light { visibility: visible; }
.langhint { position: fixed; z-index: 50; right: 16px; bottom: 16px; display: flex; align-items: center; background: var(--sheet);
  border: 1px solid var(--ink); box-shadow: 3px 3px 0 var(--drop); font: var(--label); text-transform: uppercase; letter-spacing: .02em; }
.langhint a { padding: 10px 6px 10px 14px; }
.langhint button { align-self: stretch; width: 40px; padding: 0; background: none; border: 0; box-shadow: none; color: var(--mut); font: var(--fs-lead)/1 var(--sans); cursor: pointer; }
.langhint button:hover { color: var(--ink); }
/* phones: a line at the top of the page instead of a box over the text. prefs.js decides in the head and marks
   <html class=hint>, so the line's room is there from the first paint and nothing moves when the hint arrives */
@media (max-width: 640px) {
  .hint body { padding-top: 44px; }
  .langhint { position: absolute; inset: 0 0 auto; height: 44px; justify-content: space-between; border-width: 0 0 1px; border-color: var(--line); box-shadow: none; }
}

/* The family's button (a site may style its own .btn on top: these rules weigh nothing). Disabled, it says so. */
:where(.btn) { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 0 18px;
  background: var(--blue); color: var(--sheet); border: 1px solid var(--blue); border-radius: 2px; font: 600 var(--fs-ui)/1.2 var(--sans); text-decoration: none; }
:where(.btn):hover { background: var(--blue-d); border-color: var(--blue-d); color: var(--sheet); text-decoration: none; }
a:where(.btn), a:where(.btn):hover { color: var(--sheet); text-decoration: none; } /* a link that is a button: its label, not the link colour */
:where(.btn):disabled, :where(.btn)[aria-disabled=true] { background: none; border-color: var(--line); color: var(--mut); cursor: not-allowed; }

/* The site-wide notice (kit.notice()): a band above the header on every page, in the caution colours, obvious but in the
   page's flow (it never sticks or covers anything). Its other languages' wording is drawn invisibly in the same cell
   (.tw), so the band has one height in every language and the language switch moves nothing. */
.notice { background: var(--warn-bg); border-bottom: 2px solid var(--warn); color: var(--ink); }
.notice p { display: grid; max-width: 1120px; margin: 0 auto; padding: 12px 20px; font-size: var(--fs-body); line-height: 1.5; text-align: center; text-wrap: pretty; }
.notice p > span { grid-area: 1 / 1; }
.notice b, .notice a { font-weight: 700; }
.notice a { color: color-mix(in srgb, var(--blue) 75%, var(--ink)); text-decoration: underline; text-underline-offset: 2px; }
/* a link that looks like one (the research: colour and underline); a quarter toward the ink keeps AA on the band in both themes */
.notice .tw { visibility: hidden; }
/* the line under a result (kit.result_note()): an estimate, an automated check, public data */
.rnote { margin: 10px 0 0; font-size: var(--fs-small); line-height: 1.5; color: var(--mut); }

/* The header (kit.header(); carbonode's base.html writes the same markup): one frozen row on frosted glass (a layer of
   its own: a filter on .hd would pin the side menu to the header, not the screen), the site's mark, its menus in label
   type and the language switch apart from them. It tightens once the page scrolls (motion.js stick(): .stuck). --hd-mark:
   the current page's underline. On phones, with a Menu button, the menus open from the side (js/prefs.js: .open). */
.hd { position: sticky; top: 0; z-index: 20; border-bottom: 1px solid var(--line); transition: box-shadow .2s ease; }
.hd::before { content: ""; position: absolute; inset: 0; z-index: -1; background: var(--glass); -webkit-backdrop-filter: blur(var(--blur)); backdrop-filter: blur(var(--blur)); }
.hd.stuck { box-shadow: 0 4px 2px -4px var(--shade); }
.hd .wrap { display: flex; align-items: center; gap: 20px; min-height: 64px; transition: min-height .2s ease; }
.hd.stuck .wrap { min-height: 52px; }
.hd .nav { display: flex; gap: 2px; margin-left: auto; }
.hd .nav a, .langs .lang { padding: 8px 10px; font: var(--label); text-transform: uppercase; letter-spacing: .02em; color: var(--ink); white-space: nowrap; }
.hd .nav a:hover { color: var(--blue); text-decoration: none; }
.hd .nav a[aria-current] { box-shadow: inset 0 -2px var(--hd-mark, var(--blue)); }
.hd .nav .same > span { justify-self: center; } /* each item centred in its width for every language */
.langs { display: flex; align-items: center; padding-left: 10px; border-left: 1px solid var(--line); } /* apart from the menus */
.langs .lang { min-width: 3.4em; padding-inline: 6px; text-align: center; color: var(--mut); } /* EN, ES, PT: one width */
.langs .lang:hover { color: var(--ink); text-decoration: none; }
.bar .langs .lang { padding-block: 0; } /* in the page's bar (eThorns): the bar keeps the family's height */
/* The Menu button (kit.header()'s on phones; eThorns' phone menu summary too): MENU and its state, + closed, − open, as a
   theme button names its state. The same on every site. */
.mbtn { display: inline-flex; align-items: center; gap: 8px; height: 36px; min-height: 0; padding: 0 10px; font: var(--label); text-transform: uppercase;
  letter-spacing: .02em; color: var(--ink); background: var(--sheet); border: 1px solid var(--ink); border-radius: 2px; box-shadow: none; }
.mbtn::after { content: "+"; font: var(--fs-ui) var(--pix); color: var(--faint); }
.mbtn[aria-expanded=true]::after, [open] > .mbtn::after { content: "−"; }
.mbtn:hover, .mbtn:active { background: var(--sheet); color: var(--ink); transform: none; box-shadow: none; } /* a site's button hover never fills it */
.hd .mbtn { display: none; }
@media (max-width: 900px) {
  :root { --hd-h: 57px; }
  .hd .wrap, .hd.stuck .wrap { gap: 12px; min-height: var(--hd-row); padding-inline: var(--gutter); } /* one row and one gutter on every site */
  .prefs .hd .mbtn { display: inline-flex; position: relative; z-index: 41; } /* above its panel (40), so a second tap closes it */
  /* open, the button stays on the panel's top edge: fixed where it sits in a header at the top (the notice band above
     the page would otherwise leave it floating over the list) */
  .prefs .hd.open .mbtn { position: fixed; top: calc((var(--hd-row) - 36px) / 2); right: var(--gutter); }
  .prefs .hd:has(.mbtn) .langs { margin-left: auto; border: 0; }
  .prefs .hd:has(.mbtn) .nav { position: fixed; z-index: 40; top: 0; right: 0; bottom: 0; width: min(300px, 82vw); flex-direction: column; gap: 0;
    margin: 0; padding: 64px 20px 20px; background: var(--sheet); border-left: 1px solid var(--line); box-shadow: -8px 0 24px var(--shade);
    overflow-y: auto; visibility: hidden; transform: translateX(100%); transition: transform .2s ease, visibility .2s; }
  .prefs .hd.open { z-index: 60; }
  .prefs .hd.open .nav { visibility: visible; transform: none; }
  .prefs .hd.open::after { content: ""; position: fixed; inset: 0; z-index: 30; background: color-mix(in srgb, var(--ink) 25%, transparent); } /* a tap closes */
  .prefs .hd:has(.mbtn) .nav a { padding: 14px 4px; border-bottom: 1px solid var(--line); }
  .prefs .hd:has(.mbtn) .nav a[aria-current] { box-shadow: inset 3px 0 var(--hd-mark, var(--blue)); padding-left: 12px; }
  .prefs .hd:has(.mbtn) .nav .same > span { justify-self: start; }
}

/* Offers (kit.py offer(), offers()): a product or plan with its price, shown even while it's closed. A closed one keeps
   its place and price, with a badge that says why and an action that's a waitlist or a disabled button. */
.offers { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 16px; }
.offer { display: flex; flex-direction: column; gap: 10px; padding: 20px; background: var(--sheet); border: 1px solid var(--line); border-radius: 2px; }
.offer :is(h2, h3) { margin: 0; font: 500 var(--fs-lead)/1.3 var(--serif-t); color: var(--ink); } /* a title: Literata, like every h2 */
.offer .price { margin: 0; font: 600 var(--fs-h2)/1.1 var(--sans); color: var(--ink); font-variant-numeric: tabular-nums; }
.offer ul { margin: 0; padding-left: 1.1em; color: var(--text); font-size: var(--fs-ui); }
.offer .btn { align-self: flex-start; margin-top: auto; }
.offer .note { margin: 0; color: var(--mut); font-size: var(--fs-small); }
.offer .state { margin: 0; color: var(--warn); } /* why it's closed; its own class, so a site's badge can't restyle it */
.offer.closed { border-style: dashed; }
.offer .btn:disabled { border-style: dashed; box-shadow: none; transform: none; } /* closed like its card: nothing to press */

/* Ad slots (kit.py ad_slot): in the pages from the start, shown only once ads are switched on (<html class="ads">), at a
   fixed height so nothing jumps when they load. */
.ad { display: none; }
/* The page, one parent on every site (the owner: "all should be variations of the same parent"): "← parent" in a bar
   above the sheet (kit.bar()), then the sheet, which opens with its head (kit.head_block(): the label, the title, the
   intro, centred) and ends with the END rule (kit.rule()). Sites vary through tokens (--sheet-w); ethorns' reading
   column widens both to the column on desktops. */
.bar { display: flex; align-items: center; gap: 14px; max-width: var(--sheet-w); margin: 36px auto 16px; padding: 12px 0; font: var(--label);
  text-transform: uppercase; letter-spacing: .02em; color: var(--faint); }
.bar .up { color: var(--ink); } .bar .up:hover { color: var(--blue); text-decoration: none; }
.sheet { min-width: 0; max-width: var(--sheet-w); margin: 0 auto 24px; padding: 40px 56px 32px; overflow-wrap: break-word; background: var(--sheet);
  border: 1px solid var(--line); border-radius: 2px; }
:is(.sheet, .pagehead) > .meta { margin: 0; text-align: center; font: var(--label); text-transform: uppercase; letter-spacing: .02em; color: var(--ink); }
:is(.sheet, .pagehead) > h1 { margin: 10px 0; text-align: center; font: 400 var(--fs-h1)/1.12 var(--serif-t); letter-spacing: -.02em; color: var(--title); text-wrap: balance; }
:is(.sheet, .pagehead) > .dek { max-width: 580px; margin: 0 auto; text-align: center; color: var(--mut); text-wrap: pretty; }
/* a data view (a search, figures, a record: wide, no sheet) opens with the same head, start-aligned (head_block(start=True)) */
.pagehead.start > * { text-align: start; } .pagehead.start > .dek { max-width: 72ch; margin: 0; }
.rule { margin: var(--gap) 0 0; text-align: center; font: var(--label); text-transform: uppercase; letter-spacing: .02em; color: var(--faint); }
@media (max-width: 900px) {
  .bar { max-width: none; margin: 12px 12px 10px; padding: 4px 0; }
  .sheet { max-width: none; margin: 0 12px 24px; padding: 36px 20px 32px; }
}
/* A table marked by kit.stack(): on a phone each row is a block, its first cell the heading and the rest label and value
   (the column headings move onto the cells, so the head is kept for screen readers only). Desktops keep the table. */
@media (max-width: 700px) {
  table.stack, table.stack > tbody, table.stack > tbody > tr, table.stack > tbody > tr > td { display: block; width: auto; }
  table.stack > thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  table.stack > tbody > tr { padding: 10px 12px; border-top: 1px solid var(--line); }
  table.stack > tbody > tr:first-child { border-top: 0; }
  table.stack > tbody > tr > td { padding: 3px 0; border: 0; }
  table.stack > tbody > tr > td:first-child { font-weight: 600; color: var(--ink); }
  table.stack > tbody > tr > td[data-label]::before { content: attr(data-label); display: block; font: var(--label); text-transform: uppercase;
    letter-spacing: .02em; color: var(--faint); }
}
/* Figures (kit.figure()): a drawing on a plate of grid paper, then its caption as a label. The drawing uses the .dg-* pens:
   type (mono, pix, the text face), ink, hairlines, the status colours, and --c (a chapter's or brand's colour: a tint,
   a line and a label in it; --ct for its text shade). Carbonode's charts are .fig too, without a plate. */
.fig { margin: var(--gap) 0 8px; }
.fig .plate { padding: 28px 16px; background: var(--sheet); border: 1px solid var(--line); border-radius: 2px;
  background-image: linear-gradient(var(--grid) 1px, transparent 1px), linear-gradient(90deg, var(--grid) 1px, transparent 1px); background-size: 16px 16px; }
.fig .plate svg { display: block; max-width: 100%; height: auto; margin: 0 auto; overflow: visible; }
:where(.fig .plate) text { fill: var(--ink); font-family: var(--sans); }
.plate + figcaption { margin-top: 10px; text-align: center; font: var(--label); text-transform: uppercase; letter-spacing: .02em; color: var(--faint); }
.dg-mono { font-family: var(--mono); } .dg-pix { font-family: var(--pix); text-transform: uppercase; } .dg-serif { font-family: var(--sans); }
.dg-ink { fill: var(--ink); } .dg-mut { fill: var(--mut); } .dg-line { stroke: var(--ink); fill: none; }
.dg-ok { fill: var(--green); } .dg-warn { fill: var(--yellow); } .dg-bad { fill: var(--red); } .dg-na { fill: var(--ghost); }
.dg-okt { fill: var(--ok); } .dg-warnt { fill: var(--warn); } .dg-badt { fill: var(--bad); }
.dg-box { fill: var(--sheet); stroke: var(--ink); } /* before the tints, so a tinted box keeps its ink border */
.dg-tint-ok { fill: var(--ok-bg); } .dg-tint-warn { fill: var(--warn-bg); } .dg-tint-bad { fill: var(--bad-bg); }
.dg-edge-ok { stroke: var(--green); } .dg-edge-warn { stroke: var(--yellow); } .dg-edge-bad { stroke: var(--red); }
.dg-mark { fill: none; stroke: var(--sheet); } .dg-mark.warn { stroke: var(--ink); } /* the tick or cross on a status square */
.dg-plain { --c: var(--faint); --ct: var(--faint); }
.dg-tint { fill: color-mix(in srgb, var(--c) 14%, var(--sheet)); } .dg-cline { stroke: var(--ct, var(--c)); } .dg-clabel { fill: var(--ct, var(--c)); }
html.ads .ad { display: block; position: relative; height: 280px; margin: var(--section) 0 var(--gap); }
html.ads .ad::before { content: attr(data-label); position: absolute; top: -18px; left: 0; color: var(--faint); font: var(--label); text-transform: uppercase; letter-spacing: .02em; }
@media (max-width: 900px) { html.ads .ad { height: 250px; } }

/* The family's night, graphite: lifted, cool and not purple. The same tokens for readers whose system asks for dark, or
   who pick it with the theme button (kit.py theme_button()). A site opts in with kit.css(…, dark=True). Written once,
   as a prefers-color-scheme block: kit.css() also applies it under <html data-theme="dark"> and keeps it off under
   data-theme="light" (js/prefs.js). Text colours are 4.5:1 on the sheet and the desk. */
@media (prefers-color-scheme: dark) {
:root:not([data-theme=light]) {
    color-scheme: dark;
    --desk: #2b2f35; --sheet: #353a41; --thead: #3e434a; --glass: color-mix(in srgb, var(--desk) 96%, transparent);
    --ink: #f1f3f5; --text: #e2e6ea; --mut: #b0b8c0; --faint: #aab2ba; --ghost: #4f565e; --line: #4a5058; --grid: #3e434a;
    --blue: #8c9cff; --blue-d: #b0bcff; --blue-t: #2e3552; --blue-line: #4a5590; --blue-ink: #d0d8ff;
    --ok: #2cd49e; --ok-bg: #1f3d34; --ok-line: #2f6e5a; --ok-ink: #9ff0d2;
    --warn: #f0bd5c; --warn-bg: #4a3a1c; --bad: #ff8a73; --bad-bg: #4a2a26;
    --red: #ff6a50; --yellow: #f5b025; --green: #2cd49e; --pink: #f06ac0; --violet: #a57dfb;
    --red-tx: #ff8a73; --yellow-tx: #f5c04a; --pink-tx: #f58ccf; --violet-tx: #bda0ff; --on-fill: #101316; 
    --hair: rgb(255 255 255 / .12); --shade: rgb(0 0 0 / .35); --drop: color-mix(in srgb, var(--blue) 22%, transparent); --drop-blue: color-mix(in srgb, var(--blue) 30%, transparent); 
    --p2: #ff7b72; --p3: #f0b54a; --p4: #b99bff; --p5: #5fd690; --p6: #a6abb5;
  }
}
:root[data-theme=dark] {
    color-scheme: dark;
    --desk: #2b2f35; --sheet: #353a41; --thead: #3e434a; --glass: color-mix(in srgb, var(--desk) 96%, transparent);
    --ink: #f1f3f5; --text: #e2e6ea; --mut: #b0b8c0; --faint: #aab2ba; --ghost: #4f565e; --line: #4a5058; --grid: #3e434a;
    --blue: #8c9cff; --blue-d: #b0bcff; --blue-t: #2e3552; --blue-line: #4a5590; --blue-ink: #d0d8ff;
    --ok: #2cd49e; --ok-bg: #1f3d34; --ok-line: #2f6e5a; --ok-ink: #9ff0d2;
    --warn: #f0bd5c; --warn-bg: #4a3a1c; --bad: #ff8a73; --bad-bg: #4a2a26;
    --red: #ff6a50; --yellow: #f5b025; --green: #2cd49e; --pink: #f06ac0; --violet: #a57dfb;
    --red-tx: #ff8a73; --yellow-tx: #f5c04a; --pink-tx: #f58ccf; --violet-tx: #bda0ff; --on-fill: #101316; 
    --hair: rgb(255 255 255 / .12); --shade: rgb(0 0 0 / .35); --drop: color-mix(in srgb, var(--blue) 22%, transparent); --drop-blue: color-mix(in srgb, var(--blue) 30%, transparent); 
    --p2: #ff7b72; --p3: #f0b54a; --p4: #b99bff; --p5: #5fd690; --p6: #a6abb5;
  }

/* eThorns: a technical manual on paper, one colour per chapter. --c fills, --ct text (4.5:1 on white); the ink by default. */
:root { --c: var(--ink); --ct: var(--ink); }
.c-security { --c: var(--red); --ct: var(--red-tx); }
.c-network { --c: var(--blue); --ct: var(--blue); }
.c-image { --c: var(--pink); --ct: var(--pink-tx); }
.c-text { --c: var(--yellow); --ct: var(--yellow-tx); }
.c-dev { --c: var(--green); --ct: var(--ok); }
.c-pdf { --c: var(--violet); --ct: var(--violet-tx); }
.c-money { --c: var(--warn); --ct: var(--warn); } /* old gold: the only family colour left that doesn't look like another chapter's */

/* eThorns: a technical manual on paper. Serif to read, pixel mono for labels, one colour per chapter.
   The scale, kept on purpose: text 14 / 17 / 18 / 24 / h1; labels 11 (buttons 12); gaps 12 inside a box, 28 between boxes,
   44 above a section; radius 2; borders 1px --ink (things you use) or --line (things you read). */
/* The family's base.css and eThorns' skin (the chapter colours) come first: the build puts them in front of this file. */
:root {
  --hl: #fff1a8; /* highlighter */ --del: #ffd3c8; --ins: #d4f5e9; /* compare texts: light enough for --bad and --ok words at 4.5:1 */
  --pad: 56px; /* the sheet's side padding (base.css .sheet; 20 px on phones): the catalog's frozen bar spans the sheet with it */
  --stick: 40px; /* bottom of the stuck header; app.js measures it */
  /* the link preview's look-alikes: each app's own colours, on purpose (WhatsApp, Facebook, LinkedIn, X); WhatsApp's link
     and time a shade darker than the app's, for 4.5:1 on its bubble */
  --lp-title: #111; --lp-desc: #5b5b5b; --lp-host: #65676b; --lp-img: #e4e6eb; --lp-noimg: #707070; --lp-stripe: #f0f0f0; --lp-stripe2: #e6e6e6;
  --lp-card: #fff; --lp-line: #dadde1; --lp-foot: #f0f2f5; --lp-x-line: #cfd9de; --lp-chip: rgb(0 0 0 / .77);
  --wa-bubble: #d9fdd3; --wa-card: #d1f4cc; --wa-link: #0274a8; --wa-time: #5f6f79; --wa-shadow: rgb(0 0 0 / .13);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme=light]) { --hl: #554600; --del: #4d1d14; --ins: #123d30; }
}
:root[data-theme=dark] { --hl: #554600; --del: #4d1d14; --ins: #123d30; }

@media (min-width: 901px) { :root { --hd-h: 33px; } } /* the frozen bar (kit.bar()) on desktops: anchors land under it */
html:has(dialog[open]) { overflow: hidden; scrollbar-gutter: stable; } /* the palette scrolls, not the page behind it; the scrollbar's room
   stays while it's open, so nothing moves. Only then: a gutter kept all the time was a strip nothing painted, and the
   page and the notice band stopped short of the window's right edge */
code { font: var(--fs-ui) var(--mono); background: var(--desk); padding: 1px 5px; border-radius: 2px; overflow-wrap: anywhere; } /* a long hash or link breaks instead of sticking out */
mark { background: var(--hl); color: inherit; padding: 0 2px; }

/* ---------- labels: every bit of pixel type is this one style (buttons are 12px, the brand and a few marks bigger) ---------- */
kbd, .bar, .meta, .rule, .foot .learn a, .foot .lang, .foot .made, .coffee, .palbtn, .chap > summary, a.chap,
.where b, .bench::before, .bench h3, .card .n, .rel .n, .pager, :is(table.res, table.flow) th, .chip, .catalog .cat h3,
dialog.pal .hint, dialog.pal li a small, .ruler .rl, .ruler .num, .lp figcaption, .lp-noimg {
  font: var(--label); text-transform: uppercase; letter-spacing: .02em; }
/* the square before a chapter name */
.chap > summary::before, a.chap::before, .card .n::before, .catalog .cat h3::before, .chip[class*="c-"]::before {
  content: ""; width: 8px; height: 8px; flex-shrink: 0; background: var(--c); }

/* ---------- shell: contents | page | reading ruler ---------- */
.shell { display: grid; grid-template-columns: 288px minmax(0, 1fr); min-height: 100vh; } /* every name in one line, both languages (meta.json nav) */
/* contents: brand + search pinned on top, reading links + support pinned at the bottom, only the chapters scroll between.
   view-transition-name: pages cross-fade on navigation, the contents stay put (see @view-transition below). */
.toc { position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; border-right: 1px solid var(--line); view-transition-name: toc; }
.toc .head { display: flex; flex-direction: column; gap: 20px; padding: 28px 18px 14px 28px; }
.toc .list { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 12px;
  padding: 6px 10px 16px 28px; scrollbar-width: thin; scrollbar-gutter: stable; } /* the same width with or without a scrollbar */
.foot { display: flex; flex-direction: column; gap: 10px; padding: 14px 18px 18px 28px; border-top: 1px solid var(--line); }
.foot .learn { display: grid; grid-template-columns: repeat(2, max-content); gap: 0 18px; } /* aligned columns: no ragged wrap */
.foot .learn a { display: inline-flex; align-items: center; min-height: 24px; } /* a 24 px touch target each (WCAG 2.5.8) */
.foot .learn a, .foot .lang, .foot .theme { color: var(--mut); }
.foot .made { color: var(--faint); }
.main > .foot { display: none; } /* wide screens: the same footer is pinned under the contents column */
.coffee { display: flex; align-items: center; justify-content: center; height: 36px; padding: 0 10px; border: 1px solid var(--ink); border-radius: 2px;
  background: var(--sheet); color: var(--ink); }
.coffee:hover { text-decoration: none; background: var(--ink); color: var(--sheet); }
.brand { font: var(--fs-body) var(--pix); text-transform: uppercase; color: var(--blue); letter-spacing: .02em; }
.brand:hover { text-decoration: none; color: var(--blue-d); }
.palbtn { display: flex; align-items: center; gap: 8px; width: 100%; height: 36px; padding: 0 10px; background: var(--sheet); color: var(--faint);
  border: 1px solid var(--ink); border-radius: 2px; } /* ink, like every other control: in the phone bar it sits beside the menu */
.palbtn kbd { margin-left: auto; }
.palbtn:hover { color: var(--ink); }
.chap > summary, a.chap { align-items: center; gap: 8px; padding: 2px 0; color: var(--ink); }
.chap > summary { justify-content: flex-start; }
.chap > summary::after { margin-left: auto; }
.chap[open] > summary { margin-bottom: 4px; }
a.chap { display: flex; }
a.chap:hover { text-decoration: none; color: var(--ct); }
a.chap.on { color: var(--ct); }
a.chap.on::after { content: "←"; margin-left: auto; }
.chap ul { list-style: none; margin: 0; padding: 0 0 0 17px; }
/* the open chapter's list scrolls in the room the seven rows leave (toc.js sets its height); a fade where it's cut */
.toc .chap > ul { overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
.toc .chap > ul { --ft: 0px; --fb: 0px; -webkit-mask-image: linear-gradient(transparent, var(--ink) var(--ft), var(--ink) calc(100% - var(--fb)), transparent);
  mask-image: linear-gradient(transparent, var(--ink) var(--ft), var(--ink) calc(100% - var(--fb)), transparent); }
.toc .chap > ul.up { --ft: 22px; } .toc .chap > ul.cut { --fb: 28px; } /* a fade at the end where there's more */
.chap li a { display: grid; grid-template-columns: 10px minmax(0, 1fr); font-size: var(--fs-ui); line-height: 1.75; color: var(--ink); } /* a long name wraps under its text, not under the bullet */
.chap li a::before { content: "•"; color: var(--ghost); }
.chap li a[aria-current=page] { font-weight: 500; }
.chap li a[aria-current=page]::before { color: var(--c); }
.topbar { display: none; }

.main { min-width: 0; padding: 0 0 48px; display: flex; flex-direction: column; }
.main > .sheet { flex: 1 0 auto; } /* a short page's sheet reaches the window's foot: no bare desk under it */ /* the page runs edge to edge: sidebar border to window edge */

/* The engine's bar and sheet (kit.bar(), .sheet), as on every family site, centred in the column beside the contents
   (a 24 px gutter; wider than 1400 px the reading ruler takes the right). Here the bar sticks, tightens and turns to
   frosted glass, a layer reaching 16 px past it, so the text stays on the sheet's edge. */
@media (min-width: 901px) {
  .main { padding-inline: 24px; }
  .main > .bar, .main > .sheet { width: 100%; } /* main is a flex column: auto margins alone would shrink them */
  .bar { position: sticky; top: 0; z-index: 20; transition: padding .2s cubic-bezier(.4, 0, .2, 1); }
  .bar.stuck { padding-block: 8px; }
}
@media (min-width: 1401px) { .main { padding-right: 208px; } } /* .ruler: 196 px, 12 px from the edge; kept on every page, so the sheet never moves */
.bar::after { content: ""; position: absolute; inset: 0 -16px; z-index: -1; opacity: 0; transition: opacity .2s; background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)); backdrop-filter: blur(var(--blur)); box-shadow: 0 4px 2px -4px var(--shade); }
.bar.stuck::after { opacity: 1; }
.bar::before { content: ""; position: absolute; inset: auto -16px 0; height: 1px; opacity: 0; transition: opacity .2s;
  background: linear-gradient(to right, transparent 0%, var(--hair) 5%, var(--hair) 95%, transparent 100%); }
.bar.stuck::before { opacity: 1; }
.bar .langs { margin-left: auto; } /* the engine's language switch, at the bar's end */

/* The sheet: one centered column for everything on every page (header, text, tables, boxes, the tool, the END rule), so
   all blocks share the same two edges; 800px keeps lines readable (~80 characters). The same two gaps everywhere:
   --gap from the header to the content and between boxes, --section above each heading. */
.sheet > :not(.tools-top) { max-width: var(--col); margin-inline: auto; }
.sheet > .dek { max-width: 580px; } /* the subtitle stays a short centered block under the title */
.sheet > .dek + * { margin-top: var(--gap); }
.dek + .prose > :first-child { margin-top: 0; } /* the gap above is the header's; after a tool or buy box, a heading keeps its --section */

/* reading ruler (after makingsoftware.com): a tick every 10px, a line per section, labels on hover,
   and a marker that steps tick by tick as you scroll, like a clock hand. No easing on purpose. */
.ruler { position: fixed; z-index: 15; top: 90px; right: 12px; width: 196px; height: calc(100vh - 130px); }
.ruler .ticks { position: absolute; top: 0; right: 16px; width: 96px; display: flex; flex-direction: column; cursor: pointer; }
.ruler .ticks i { display: flex; justify-content: flex-end; flex: 0 0 10px; }
.ruler .ticks i::before { content: ""; width: 8px; height: 1px; background: var(--hair); transition: width .1s, background-color .1s; }
.ruler .ticks i.past::before { background: var(--shade); }
.ruler .ticks i:hover::before { width: 16px; background: var(--ink); }
.ruler .sec { position: absolute; right: 16px; width: 16px; height: 1px; background: var(--ink); pointer-events: none; }
.ruler .rl { position: absolute; right: 40px; transform: translateY(-50%); max-width: 150px; padding: 0; background: none; border: 0;
  line-height: 1.3; text-align: right; color: var(--ink); opacity: 0; transition: opacity .3s, color .15s; }
.ruler:hover .rl, .ruler:focus-within .rl { opacity: 1; }
.ruler .rl:hover { color: var(--blue); }
.ruler .pos { position: absolute; right: 16px; width: 16px; height: 1px; background: var(--blue); pointer-events: none; }
.ruler .num { position: absolute; right: 24px; top: 0; transform: translateY(-50%); color: var(--blue); transition: opacity .3s; }
.ruler:hover .num { opacity: 0; }

/* ---------- type on the sheet ---------- */
h1 { margin: 10px 0; text-align: center; font-weight: 400; font-size: var(--fs-h1); line-height: 1.12; letter-spacing: -0.02em; color: var(--title); text-wrap: balance; }
.dek { margin: 0 auto; text-align: center; color: var(--mut); text-wrap: pretty; }
.rule { margin-bottom: var(--gap); }
.sheet :where(h2) { margin: var(--section) 0 12px; font-weight: 500; font-size: var(--fs-h2); line-height: 1.3; letter-spacing: -0.01em; color: var(--title); }
.where { display: flex; gap: 10px; align-items: baseline; margin: var(--gap) auto 0; padding: 10px 14px; font-size: var(--fs-ui); line-height: 1.5; border: 1px solid; border-radius: 2px; }
.where b { flex-shrink: 0; }
.where.local { color: var(--ok-ink); background: var(--ok-bg); border-color: var(--ok-line); }
.where.net { color: var(--blue-ink); background: var(--blue-t); border-color: var(--blue-line); }

/* ---------- the tool workbench ---------- */
.bench { position: relative; margin: var(--gap) 0 8px; padding: 26px 20px 20px; border: 1px solid var(--ink); border-radius: 2px;
  background-color: var(--sheet); background-image: linear-gradient(var(--grid) 1px, transparent 1px), linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 16px 16px; box-shadow: 3px 3px 0 var(--drop-blue); } /* old ethorns' soft blue shadow, back */
.bench::before { content: attr(data-label); position: absolute; top: -9px; left: 14px; padding: 0 6px; background: var(--sheet); color: var(--ct); }
.bench > * { position: relative; }
.bench h3 { margin: 22px 0 8px; color: var(--ink); }
.row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 8px 0; }
.row > * { min-width: 0; } /* an item can shrink to the row: a select as wide as its longest option then fits a phone */
.row > .grow { flex: 1 1 240px; }
label { font-size: var(--fs-ui); color: var(--mut); }
input, select, textarea, button { max-width: 100%; font: inherit; color: var(--ink); } /* a file picker is as wide as the browser makes it */
input[type=text], input[type=number], input[type=url], input[type=password], input[type=search], select, textarea {
  min-height: 44px; padding: 9px 12px; background: var(--sheet); border: 1px solid var(--ink); border-radius: 2px; font: var(--fs-ui) var(--mono); }
select { font-family: var(--sans); }
textarea { width: 100%; min-height: 150px; font-size: var(--fs-ui); line-height: 1.5; resize: vertical; }
textarea.short { min-height: 90px; } textarea.rows { min-height: 0; } /* a message box; one sized by its rows */
input:focus, select:focus, textarea:focus { outline: 2px solid var(--blue); outline-offset: 1px; }
input[type=checkbox], input[type=radio] { accent-color: var(--blue); width: 16px; height: 16px; }
input[type=range] { accent-color: var(--blue); }
input[type=color] { width: 44px; height: 44px; padding: 2px; border: 1px solid var(--ink); border-radius: 2px; background: var(--sheet); }
button { cursor: pointer; }
/* a tool's buttons: primary (blue) and .sec; :where() adds no weight, so .sec, .chip and the site controls win */
:where(.bench) button, :where(.bench) a.btn { min-height: 44px; padding: 0 18px; background: var(--blue); color: var(--sheet); border: 1px solid var(--ink); border-radius: 2px;
  font: var(--fs-small) var(--pix); text-transform: uppercase; letter-spacing: .02em; box-shadow: 3px 3px 0 var(--drop); }
:where(.bench) button:where(:enabled):hover, :where(.bench) a.btn:hover { background: var(--blue-d); color: var(--sheet); text-decoration: none; }
:where(.bench) button:where(:enabled):active, :where(.bench) a.btn:active { transform: translate(2px, 2px); box-shadow: 1px 1px 0 var(--drop); }
:where(.bench) button.sec { background: var(--sheet); color: var(--ink); box-shadow: 2px 2px 0 var(--drop); }
:where(.bench) button.sec:where(:enabled):hover { background: var(--desk); }
:where(.bench) button:disabled { opacity: .5; cursor: default; }
:where(.bench) a.btn { display: inline-flex; align-items: center; } /* a link that looks like the tool's button */
input[type=number] { width: 7em; }
.drop { padding: 34px 20px; text-align: center; background: var(--sheet); border: 1px dashed var(--ink); border-radius: 2px; color: var(--mut); cursor: pointer; }
.drop.over { border-color: var(--blue); background: var(--blue-t); color: var(--ink); }
.out { margin: 12px 0; padding: 12px 14px; background: var(--desk); border: 1px solid var(--line); border-radius: 2px;
  font: var(--fs-ui)/1.6 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; } /* long hashes and links wrap; words stay whole */
.out .wd { background: var(--del); text-decoration: line-through; } .out .wi { background: var(--ins); }
.qr svg { display: block; max-width: 100%; height: auto; border: 1px solid var(--line); } /* a white code stays visible on the white frame */
.qr-cam { max-width: 100%; } .qr-pays { font-size: var(--fs-lead); } /* the QR reader's camera, and who a payment QR pays */
.b64-img { display: block; max-width: 100%; max-height: 240px; margin: 8px 0; }
/* the signature pad and the page you place it on: things you use, so ink borders; no scrolling while you draw */
.sig-pad, .sig-view { display: block; max-width: 100%; border: 1px solid var(--ink); cursor: crosshair; }
.sig-pad { width: 100%; height: auto; background: var(--sheet); touch-action: none; } .sig-view { max-height: 85vh; }
.ok { color: var(--ok); } .warn { color: var(--warn); } .bad { color: var(--bad); } .mut { color: var(--mut); }
.files { list-style: none; padding: 0; }
.files li, .channels li { margin: 4px 0; }
.files img, .files canvas { max-width: 96px; max-height: 96px; border-radius: 2px; }
/* PDF page thumbnails (assets/pdf.js thumbs): a grey page until it's drawn */
.thumbs { align-items: flex-start; }
.thumbs > div { width: 120px; max-width: 100%; text-align: center; font-size: var(--fs-ui); }
.thumbs .page, .thumbs canvas { display: block; width: 100%; border: 1px solid var(--line); }
.thumbs .page { aspect-ratio: 1 / 1.414; background: var(--desk); }
.thumbs canvas { height: auto; }
.out.tts-read { max-height: 320px; overflow-y: auto; font: var(--fs-body)/1.8 var(--sans); white-space: normal; background: var(--sheet); } /* the text being read, sentence by sentence */
.tts-read span { cursor: pointer; border-radius: 2px; }
.tts-read span:hover { background: var(--blue-t); }
.tts-read span.on { background: var(--hl); }
.wm-preview canvas { display: block; max-width: 100%; height: auto; margin: 12px 0; border: 1px solid var(--line); }

/* tables: results inside a tool (.res) and in the text (.flow) */
:is(table.res, table.flow) { width: 100%; margin: 14px 0; border-collapse: collapse; background: var(--sheet); border: 1px solid var(--ink); font-size: var(--fs-ui); line-height: 1.5; }
:is(table.res, table.flow) th { padding: 8px 10px; text-align: left; background: var(--thead); border-bottom: 1px solid var(--ink); }
:is(table.res, table.flow) td { padding: 10px; text-align: left; vertical-align: top; border-top: 1px solid var(--line); overflow-wrap: anywhere; }
table.res td.v { font: var(--fs-ui) var(--mono); }
td.ok, td.warn, td.bad { font-weight: 500; }
table.flow td:first-child { font-weight: 500; }
table.flow td:first-child a { white-space: nowrap; } /* a tool name stays on one line; a sentence in the first cell wraps, so the table never outgrows the column */

/* scam and file checkers: a verdict box, then the findings */
.verdict { display: flex; gap: 16px; align-items: center; margin: 18px 0 8px; padding: 16px 18px; border: 1px solid var(--ink); border-radius: 2px; }
.verdict b { display: block; font-weight: 400; font-size: var(--fs-h2); line-height: 1.15; color: var(--ink); }
.verdict p { margin: 4px 0 0; color: var(--text); }
.verdict .mark { display: grid; place-items: center; flex-shrink: 0; width: 46px; height: 46px; font: var(--fs-h2) var(--pix); color: var(--on-fill); }
.verdict.bad { background: var(--bad-bg); } .verdict.bad .mark { background: var(--red); }
.verdict.warn { background: var(--warn-bg); } .verdict.warn .mark { background: var(--yellow); color: var(--ink); }
.verdict.ok { background: var(--ok-bg); } .verdict.ok .mark { background: var(--green); }
.findings { list-style: none; margin: 0; padding: 0; }
.findings li { position: relative; padding: 8px 0 8px 22px; border-top: 1px solid var(--line); }
.findings li::before { content: "•"; position: absolute; left: 6px; color: var(--faint); }
.findings li.bad::before { content: "!"; color: var(--bad); font-weight: 600; } .findings li.warn::before { content: "!"; color: var(--warn); }
.findings li.ok::before { content: "✓"; color: var(--ok); }
/* what-to-do: situation chips are sentences, so they're set in the text face */
.situations { margin: 8px 0 14px; } .situations .chip { font: var(--fs-ui)/1.4 var(--sans); text-transform: none; letter-spacing: 0; }

/* figures: kit.figure(), the plate and the .dg-* pens are the engine's (base.css); the motion is below */

/* ---------- tool page extras ---------- */
.steps { counter-reset: s; list-style: none; padding: 0; margin: 0; display: grid; gap: 10px; }
.steps li { counter-increment: s; display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: 10px; align-items: baseline; }
.steps li::before { content: counter(s); display: grid; place-items: center; width: 26px; height: 26px; font: var(--fs-small) var(--pix); color: var(--sheet); background: var(--ct); }
.sheet > details { border-top: 1px solid var(--line); padding: 12px 0; }
.sheet > details:last-of-type { border-bottom: 1px solid var(--line); }
summary { cursor: pointer; list-style: none; display: flex; gap: 12px; justify-content: space-between; font-weight: 500; color: var(--ink); }
summary::-webkit-details-marker { display: none; }
summary::after { content: "+"; font: var(--fs-ui) var(--pix); color: var(--faint); }
details[open] > summary::after { content: "−"; } /* ">": inside the open phone menu, closed chapters keep their + */
details p { margin: 10px 0 0; color: var(--mut); }
.rel { list-style: none; margin: 0; padding: 0; }
.rel li { display: grid; grid-template-columns: 44px minmax(0, 1fr); gap: 10px; padding: 10px 0; border-top: 1px solid var(--line); }
.rel li:last-child { border-bottom: 1px solid var(--line); }
.rel .n { line-height: 1.9; color: var(--ct); } /* 1.9: on the baseline of the 17px title beside it */
.rel small { display: block; font-size: var(--fs-ui); line-height: 1.5; color: var(--mut); }
.pager { display: flex; justify-content: space-between; gap: 16px; }
@media (max-width: 640px) { .pager { flex-direction: column; gap: 10px; } .pager > :last-child { align-self: flex-end; text-align: right; } } /* phones: one link a row, so each name keeps one line in both languages */

/* prose pages and guides */
.prose ul, .prose ol { padding-left: 1.2em; }
.prose h3 { margin: 22px 0 6px; font-weight: 500; font-size: var(--fs-lead); line-height: 1.35; color: var(--ink); }
.prose blockquote { margin: 18px 0; padding: 2px 16px; border-left: 2px solid var(--blue); color: var(--mut); }

/* ---------- cards: the home catalog and the guides ---------- */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.card { display: flex; flex-direction: column; gap: 6px; padding: 16px 18px; border: 1px solid var(--line); border-radius: 2px; color: var(--ink); box-shadow: 3px 3px 0 var(--drop); }
.card:hover { border-color: var(--c); text-decoration: none; color: var(--ink); }
.card .n { display: flex; align-items: center; gap: 8px; color: var(--ct); }
.card b { font-weight: 400; font-size: var(--fs-lead); line-height: 1.3; }
.card small { font-size: var(--fs-ui); line-height: 1.5; color: var(--mut); }
/* home: a chapter-colour square, the title and one line; the only other mark is a blue dot on the tools that go online */
.catalog .card b { position: relative; padding-left: 19px; }
.catalog .card b::before { content: ""; position: absolute; left: 0; top: .45em; width: 8px; height: 8px; background: var(--c); }
.catalog .card small { padding-left: 19px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.card b.net::after { content: "\25CF" / ""; margin-left: 7px; font-size: var(--fs-label); vertical-align: 3px; color: var(--blue); }
.more { margin-top: 12px; }
.none { text-align: center; color: var(--mut); }

/* ---------- home ---------- */
.bigsearch { display: flex; align-items: center; gap: 12px; width: 100%; min-height: 56px; padding: 10px 16px; background: var(--sheet); color: var(--faint);
  border: 1px solid var(--ink); border-radius: 2px; box-shadow: 3px 3px 0 var(--drop-blue); font: var(--fs-lead)/1.35 var(--sans);
  text-align: left; } /* the text wraps on a narrow phone rather than being cut off */
.bigsearch:hover { color: var(--mut); }
.bigsearch kbd { margin-left: auto; color: var(--faint); }
/* the filterable catalog: its bar's background spans the sheet, its contents line up with the column (100% = the sheet's content width) */
.tools-top { position: sticky; top: var(--stick); z-index: 10; margin: var(--gap) calc(-1 * var(--pad)) 18px;
  padding: 10px calc(var(--pad) + max(0px, (100% - var(--col)) / 2));
  background: color-mix(in srgb, var(--sheet) 96%, transparent); -webkit-backdrop-filter: blur(var(--blur)); backdrop-filter: blur(var(--blur)); border-bottom: 1px solid transparent; transition: border-color .2s; }
.tools-top.stuck { border-bottom-color: var(--line); }
.tools-head { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 20px; margin-bottom: 10px; }
.tools-head h2 { margin: 0; }
.tools-head input { flex: 1 1 240px; min-height: 44px; font: var(--fs-body) var(--sans); }
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip { display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 0 11px; background: var(--sheet); color: var(--ink); border: 1px solid var(--line); border-radius: 2px; box-shadow: none; }
.chip:hover { background: var(--desk); border-color: var(--ink); color: var(--ink); } /* the tool buttons' hover (above) would make the text white on this light fill */
.chip.on { background: var(--ink); color: var(--sheet); border-color: var(--ink); }
.chip.on[class*="c-"] { background: var(--c); border-color: var(--c); color: var(--sheet); }
.chip.on.c-text { color: var(--ink); } /* white on yellow is unreadable */
.chip.on[class*="c-"]::before { background: currentColor; }
.catalog .cat { margin-bottom: var(--gap); scroll-margin-top: 161px; } /* below the frozen filters too (the page's padding covers the bar) */
.catalog .cat h3 { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; padding-bottom: 8px; border-bottom: 2px solid var(--c); font-weight: 400; color: var(--ct); }

/* ---------- palette ---------- */
dialog.pal { width: min(600px, calc(100% - 32px)); margin-top: 12vh; padding: 0; background: var(--sheet); color: var(--ink);
  border: 1px solid var(--ink); border-radius: 2px; box-shadow: 6px 6px 0 var(--drop), 0 24px 60px var(--shade); }
dialog.pal::backdrop { background: color-mix(in srgb, var(--desk) 55%, transparent); /* lighter than --glass: the page shows through */ -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
dialog.pal input { width: 100%; min-height: 56px; padding: 14px 16px; border: 0; border-bottom: 1px solid var(--ink); border-radius: 0; font: var(--fs-lead) var(--sans); background: transparent; }
dialog.pal input:focus { outline: none; border-bottom: 2px solid var(--blue); padding-bottom: 13px; } /* the field's own rule shows the focus; 1px less padding keeps its height */
dialog.pal .hint { margin: 0; padding: 10px 16px 0; color: var(--faint); }
dialog.pal ul { list-style: none; margin: 0; padding: 6px; max-height: 56vh; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
dialog.pal li a { display: flex; gap: 12px; align-items: baseline; padding: 9px 10px; color: var(--ink); border-radius: 2px; }
dialog.pal li a small { min-width: 30px; color: var(--ct); }
dialog.pal li[aria-selected=true] a { background: var(--blue-t); text-decoration: none; }

/* ---------- link preview: rough look-alikes of how each app renders a shared link (their own colours, on purpose) ---------- */
.lp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 26px 22px; margin: 14px 0 20px; }
.lp { margin: 0; display: flex; flex-direction: column; gap: 8px; min-width: 0; font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
.lp figcaption { color: var(--ink); }
.lp .lp-why, .lp-note { font: var(--fs-ui)/1.45 var(--sans); color: var(--mut); }
.lp .lp-why { min-height: 3.2em; }
.lp b { font-weight: 600; font-size: var(--fs-ui); line-height: 1.3; color: var(--lp-title); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.lp-d { font-size: var(--fs-small); line-height: 1.35; color: var(--lp-desc); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.lp-d.one { -webkit-line-clamp: 1; }
.lp-host { font-size: var(--fs-small); color: var(--lp-host); overflow-wrap: anywhere; }
.lp > .lp-host { color: var(--mut); } /* X's "From …" line sits on our page, not in the look-alike */
.lp-host.up { text-transform: uppercase; }
.lp-img { display: block; width: 100%; object-fit: cover; background: var(--lp-img); }
.lp-img.wide { aspect-ratio: 1.91 / 1; }
.lp-img.sq { width: 104px; height: 104px; flex-shrink: 0; }
.lp-noimg { display: grid; place-items: center; background: repeating-linear-gradient(45deg, var(--lp-stripe) 0 8px, var(--lp-stripe2) 8px 16px); color: var(--lp-noimg); }
.lp-bubble { display: flex; flex-direction: column; padding: 4px 4px 5px; background: var(--wa-bubble); border-radius: 8px 0 8px 8px; box-shadow: 0 1px .5px var(--wa-shadow); }
.lp-wa-card { display: flex; flex-direction: column; gap: 3px; padding-bottom: 8px; background: var(--wa-card); border-radius: 6px; overflow: hidden; }
.lp-wa-card > :not(.lp-img) { padding: 0 10px; }
.lp-wa-card .lp-img { margin-bottom: 6px; }
.lp-link { padding: 6px 6px 0; font-size: var(--fs-small); color: var(--wa-link); overflow-wrap: anywhere; }
.lp-time { align-self: flex-end; padding: 0 6px; font-size: var(--fs-label); color: var(--wa-time); }
.lp-box { display: flex; flex-direction: column; overflow: hidden; background: var(--lp-card); border: 1px solid var(--lp-line); }
.lp-foot { display: flex; flex-direction: column; gap: 3px; padding: 10px 12px; background: var(--lp-foot); }
.lp-li .lp-foot, .lp-x .lp-foot { background: var(--lp-card); }
.lp-box.round { border-radius: 16px; border-color: var(--lp-x-line); }
.lp-box.small { flex-direction: row; align-items: stretch; }
.lp-box.small .lp-foot { justify-content: center; min-width: 0; border-left: 1px solid var(--lp-x-line); }
.lp-over { position: relative; }
.lp-chip { position: absolute; left: 12px; bottom: 12px; max-width: calc(100% - 24px); padding: 2px 6px; border-radius: 4px; background: var(--lp-chip);
  color: var(--lp-card); font-size: var(--fs-small); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lp-box.thumb, .lp-wa-card.thumb { display: grid; grid-template-columns: 96px minmax(0, 1fr); column-gap: 10px; align-items: center; }
.lp-box.thumb > .lp-img, .lp-box.thumb .lp-over, .lp-wa-card.thumb > .lp-img { grid-row: span 4; width: 96px; height: 96px; aspect-ratio: auto; margin: 0; }
.lp-wa-card.thumb { padding: 0; } .lp-wa-card.thumb > :not(.lp-img) { padding: 0 8px 0 0; }
.lp-box.thumb .lp-foot { padding: 8px 10px 8px 0; background: var(--lp-card); }

/* ---------- motion (skipped for people who ask for less) ---------- */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
  .sheet { animation: rise .55s cubic-bezier(.2, .7, .2, 1) both; }
  .card, .chap li a, .rel a, .chip { transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease, background-color .15s ease, color .15s ease; }
  .card:hover { transform: translate(-2px, -2px); box-shadow: 3px 3px 0 color-mix(in srgb, var(--c) 35%, transparent); }
  .chap li a:hover { transform: translateX(3px); text-decoration: none; }
  :where(.bench) :is(button:not(.chip), a.btn) { transition: transform .08s ease, box-shadow .08s ease, background-color .15s ease; }
  :where(.bench) :is(button:not(.chip):enabled, a.btn):hover { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--drop); }
  .bigsearch { transition: box-shadow .15s ease, transform .15s ease; }
  .bigsearch:hover { transform: translate(-1px, -1px); box-shadow: 5px 5px 0 var(--drop-blue); }
  .card.show { animation: fade .3s ease both; }
  dialog.pal[open] { animation: pop-in .18s cubic-bezier(.2, .8, .2, 1); }
  dialog.pal[open]::backdrop { animation: fade .18s ease; }
  details[open] > p { animation: fade .25s ease; }
  .bench > * { animation: fade .3s ease both; }
  /* figures draw themselves: rects grow from the left, lines draw, marks pop, the grade gets stamped */
  .fig .grow { transform-box: fill-box; transform-origin: left center; animation: grow-x .45s cubic-bezier(.2, .7, .2, 1) both; }
  .fig .draw { stroke-dasharray: 1200; stroke-dashoffset: 1200; animation: draw 1.1s ease forwards; }
  .fig .fade { animation: fade .5s ease both; }
  .fig .pop { transform-box: fill-box; transform-origin: center; animation: pop .4s cubic-bezier(.3, 1.6, .5, 1) both; }
  .fig .stamp { transform-box: fill-box; transform-origin: center; animation: stamp .45s cubic-bezier(.2, 1.8, .4, 1) both; }
  .d1 { animation-delay: .05s; } .d2 { animation-delay: .15s; } .d3 { animation-delay: .3s; } .d4 { animation-delay: .42s; }
  .d5 { animation-delay: .54s; } .d6 { animation-delay: .7s; } .d7 { animation-delay: .85s; } .d8 { animation-delay: 1s; }
}
@keyframes rise { from { opacity: 0; transform: translateY(12px); } }
@keyframes fade { from { opacity: 0; } }
@keyframes grow-x { from { transform: scaleX(0); } }
@keyframes draw { to { stroke-dashoffset: 0; } }
@keyframes pop { from { opacity: 0; transform: scale(.3); } }
@keyframes stamp { from { opacity: 0; transform: scale(1.7) rotate(-10deg); } }
@keyframes pop-in { from { opacity: 0; transform: translateY(-8px) scale(.985); } }

/* ---------- narrow screens ---------- */
@media (max-width: 1400px) { .ruler { display: none; } }
@media (max-width: 900px) {
  :root { --pad: 20px; }
  .shell { display: block; }
  .toc { display: none; }
  /* phone: one frozen row (brand, search, language, menu). The menu opens a panel that fills the screen below it, and
     the page behind stops scrolling, so the list can't scroll away or leave half the page showing. */
  .topbar { --tb: 57px; position: sticky; top: 0; z-index: 30; display: flex; align-items: center; gap: 10px; height: var(--tb); padding: 0 var(--gutter);
    background: var(--glass); -webkit-backdrop-filter: blur(var(--blur)); backdrop-filter: blur(var(--blur)); border-bottom: 1px solid var(--line); }
  .topbar .palbtn { flex: 1; min-width: 0; }
  .topbar .menu > summary { justify-content: center; } /* the engine's Menu button (.mbtn) */
  .topbar .panel { position: absolute; top: 100%; left: 0; right: 0; display: flex; flex-direction: column; background: var(--desk);
    height: calc(100vh - var(--tb) + 1px); height: calc(100dvh - var(--tb) + 1px); } /* +1px: it starts inside the bar's bottom border */
  .topbar .panel .list { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 14px; padding: 18px 20px; }
  .topbar .panel .foot, .main > .foot { padding: 14px 20px 18px; }
  .main > .foot { display: flex; margin: 32px 0 -40px; } /* edge to edge, like the menu */
  html:has(.topbar .menu[open]) { overflow: hidden; }
  .bar::before, .bar::after { display: none; }
  .bar .langs, .bar:not(:has(.up)) { display: none; } /* the topbar already has the brand and the language */
  .tools-top { position: static; margin-inline: 0; padding-inline: 0; background: none; -webkit-backdrop-filter: none; backdrop-filter: none; } /* frozen, it would cover half a phone; the topbar has search */
  .main { padding: 0 0 40px; }
  .bench { padding: 24px 12px 14px; }
  .cards { grid-template-columns: minmax(0, 1fr); }
  /* phone: the catalog as a compact list of joined rows, about 62 px a row */
  .catalog .cards { gap: 0; }
  .catalog .card { gap: 2px; padding: 10px 12px; margin-top: -1px; border-radius: 0; }
  .catalog .card b { font-size: var(--fs-body); }
  .catalog .cat { scroll-margin-top: 0; }
  .bigsearch { font-size: var(--fs-body); }
  .bigsearch kbd, .palbtn kbd { display: none; }
  .where { flex-direction: column; gap: 4px; }
}
@media (max-width: 640px) { .fig1 { display: none; } table.res, table.flow { display: block; overflow-x: auto; } } /* unreadable at phone width; the tools come first */

/* Moving between pages: the new page fades in and the contents column stays where it was (Chrome, Edge, Safari 18.2+;
   other browsers just load the page). assets/toc.js puts back its scroll position and folded chapters first. */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: .15s; }
@media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; } }

/* bench heights: written by test/bench.mjs (node test/bench.mjs after a build; --check in the tests). Each tool's box at its
   mounted height on desktop, in its taller language, reserved before its script runs, so nothing under it jumps. */
@media (min-width: 901px) {
  .bench[data-tool="base64"] { min-height: 491px; }
  .bench[data-tool="business-days-colombia"] { min-height: 751px; }
  .bench[data-tool="case-converter"] { min-height: 497px; }
  .bench[data-tool="certificate-decoder"] { min-height: 389px; }
  .bench[data-tool="color-contrast-checker"] { min-height: 860px; }
  .bench[data-tool="compress-pdf"] { min-height: 227px; }
  .bench[data-tool="cron-explainer"] { min-height: 891px; }
  .bench[data-tool="csv-json-converter"] { min-height: 467px; }
  .bench[data-tool="cuenta-de-cobro"] { min-height: 1043px; }
  .bench[data-tool="delete-pdf-pages"] { min-height: 164px; }
  .bench[data-tool="dmarc-record-generator"] { min-height: 1129px; }
  .bench[data-tool="dmarc-report-reader"] { min-height: 219px; }
  .bench[data-tool="dns-lookup"] { min-height: 193px; }
  .bench[data-tool="dns-propagation"] { min-height: 222px; }
  .bench[data-tool="domain-age"] { min-height: 192px; }
  .bench[data-tool="email-header-analyzer"] { min-height: 434px; }
  .bench[data-tool="email-security"] { min-height: 108px; }
  .bench[data-tool="eps-to-png"] { min-height: 227px; }
  .bench[data-tool="exif-remover"] { min-height: 164px; }
  .bench[data-tool="file-checker"] { min-height: 210px; }
  .bench[data-tool="gmf-4x1000"] { min-height: 778px; }
  .bench[data-tool="hacked-what-to-do"] { min-height: 393px; }
  .bench[data-tool="hash-generator"] { min-height: 841px; }
  .bench[data-tool="heic-to-jpg"] { min-height: 425px; }
  .bench[data-tool="html-entity-encoder"] { min-height: 727px; }
  .bench[data-tool="id-watermark"] { min-height: 505px; }
  .bench[data-tool="image-compressor"] { min-height: 375px; }
  .bench[data-tool="image-converter"] { min-height: 343px; }
  .bench[data-tool="image-resizer"] { min-height: 747px; }
  .bench[data-tool="image-to-base64"] { min-height: 147px; }
  .bench[data-tool="is-it-down"] { min-height: 163px; }
  .bench[data-tool="iva-colombia"] { min-height: 384px; }
  .bench[data-tool="jpg-to-pdf"] { min-height: 379px; }
  .bench[data-tool="json-formatter"] { min-height: 322px; }
  .bench[data-tool="jwt-decoder"] { min-height: 207px; }
  .bench[data-tool="link-cleaner"] { min-height: 298px; }
  .bench[data-tool="link-preview"] { min-height: 108px; }
  .bench[data-tool="lookalike-domain-checker"] { min-height: 163px; }
  .bench[data-tool="lorem-ipsum"] { min-height: 767px; }
  .bench[data-tool="merge-pdf"] { min-height: 271px; }
  .bench[data-tool="net-salary-colombia"] { min-height: 896px; }
  .bench[data-tool="nit-check-digit"] { min-height: 297px; }
  .bench[data-tool="number-base-converter"] { min-height: 777px; }
  .bench[data-tool="ocr-pdf"] { min-height: 227px; }
  .bench[data-tool="office-to-pdf"] { min-height: 227px; }
  .bench[data-tool="overtime-colombia"] { min-height: 1214px; }
  .bench[data-tool="password-generator"] { min-height: 793px; }
  .bench[data-tool="pdf-page-numbers"] { min-height: 164px; }
  .bench[data-tool="pdf-to-jpg"] { min-height: 164px; }
  .bench[data-tool="pdf-to-png"] { min-height: 164px; }
  .bench[data-tool="pdf-to-svg"] { min-height: 227px; }
  .bench[data-tool="pdf-to-word"] { min-height: 227px; }
  .bench[data-tool="pwned-password"] { min-height: 221px; }
  .bench[data-tool="qr-code-generator"] { min-height: 620px; }
  .bench[data-tool="qr-code-reader"] { min-height: 207px; }
  .bench[data-tool="regex-tester"] { min-height: 731px; }
  .bench[data-tool="remove-duplicate-lines"] { min-height: 437px; }
  .bench[data-tool="rotate-pdf"] { min-height: 164px; }
  .bench[data-tool="scam-checker"] { min-height: 354px; }
  .bench[data-tool="severance-colombia"] { min-height: 972px; }
  .bench[data-tool="sign-pdf"] { min-height: 661px; }
  .bench[data-tool="spf-record-builder"] { min-height: 1078px; }
  .bench[data-tool="split-pdf"] { min-height: 164px; }
  .bench[data-tool="subnet-calculator"] { min-height: 966px; }
  .bench[data-tool="svg-to-png"] { min-height: 310px; }
  .bench[data-tool="text-diff"] { min-height: 249px; }
  .bench[data-tool="text-encrypt"] { min-height: 515px; }
  .bench[data-tool="text-to-speech"] { min-height: 685px; }
  .bench[data-tool="timestamp-converter"] { min-height: 796px; }
  .bench[data-tool="tls-checker"] { min-height: 192px; }
  .bench[data-tool="unlock-pdf"] { min-height: 227px; }
  .bench[data-tool="url-encode"] { min-height: 461px; }
  .bench[data-tool="url-unroller"] { min-height: 108px; }
  .bench[data-tool="uuid-generator"] { min-height: 515px; }
  .bench[data-tool="uvt-converter"] { min-height: 748px; }
  .bench[data-tool="watermark-pdf"] { min-height: 164px; }
  .bench[data-tool="website-scan"] { min-height: 192px; }
  .bench[data-tool="whats-my-ip"] { min-height: 591px; }
  .bench[data-tool="word-counter"] { min-height: 635px; }
  .bench[data-tool="yaml-json-converter"] { min-height: 469px; }
}
/* end of bench heights */
