/* Launderworks's own public site.

   ------------------------------------------------------------------
   THE DIRECTION, recorded before the code, because the next person to edit
   this file needs to know what is being kept.
   ------------------------------------------------------------------

   THESIS. Launderworks is the machine that runs a machine business, so the site is
   built like the equipment it manages: enamelled panels in fields of colour, a
   brass plate for every number, and the console shown at size rather than
   described. It refuses the arrangement this category always ships, a centred
   hero over three soft-shadowed white cards on a pale gradient, and it refuses
   that arrangement's predictable opposite, the hairline broadsheet, which is
   what this file used to be.

   OWN-WORLD. Deep spruce enamel owns whole bands of the page; porcelain sits
   between them; brass carries numerals, the primary button and the mark beside
   the wordmark. Square shoulders, a 2px shoulder line where other sites put a
   shadow. Every figure, label and plate is mono; display is a grotesque pushed
   to a hard scale. Bands alternate porcelain, spruce, porcelain, so a visitor
   knows where they are on the page from the colour alone with the text blurred
   out.

   STORY. An operator with machines in other people's houses learns in one
   viewport that Launderworks is three products, sees the console doing its job, and
   reaches one of two actions: set the business up, or talk to a person.

   FIRST VIEWPORT. Spruce field, full bleed, nothing above it but the nav.
   Plate top left, headline across 15ch at the top of the scale, lede, two
   buttons with the brass one first, and the placements screen drawn at size
   beside them, bleeding off the right edge above 64rem.

   FORM. Enamelled equipment panel and data plate, pinned by the brief rather
   than chosen: the founder named storable.com and tenantinc.com and asked for
   colour-blocked sections, large product imagery, feature grids and proof
   bands.

   ------------------------------------------------------------------
   WHAT THE BROWSER WILL NOT LET THIS FILE DO
   ------------------------------------------------------------------

   SEPARATE FROM /styles.css AND FROM /launderworks/console.css ON PURPOSE. Those two
   begin with custom properties whose values are template tokens, because they
   are rendered per hostname out of a tenant's stored palette. This file has no
   tokens in it at all. It is Launderworks's brand, it is the same on every host that
   serves it, and an operator must never be able to restyle the page that sells
   the platform.

   NO IMPORTS, NO WEBFONTS, NO INLINE ANYTHING. MARKETING_CSP in
   functions/_middleware.js is default-src none, script-src none, style-src
   self, img-src self and data, font-src self. So an @import from a font CDN
   fetches nothing, an inline style block is dropped, and the pages cannot run
   script even from their own origin. Each of those failures is silent in the
   browser and invisible in a file:// preview. Commit 2475cbe exists because of
   the inline half of this.

   THE PRODUCT IMAGERY IS INLINE SVG, and that is the policy again rather than
   a preference. img-src allows self, so an .svg or .png committed here would
   load; what it would not do is follow the colour scheme, because an image element
   cannot see this sheet's custom properties and would need a second copy of
   every colour baked into it, plus a third for dark mode. Drawing the screens
   as markup means one palette, defined once, and the .s-* classes at the
   bottom of this file are the whole of the mechanism. Everything drawn that
   way is an ILLUSTRATION of the console and is labelled as one in the caption
   under it. No figure on this site is a photograph of a real operator's data.

   FONTS ARE SYSTEM STACKS, which is a decision and not an omission. font-src
   is self, so shipping a face means committing the file, and this repository
   deliberately carries no third-party binary assets. The stacks below name a
   real first choice per platform rather than falling back to a bare
   sans-serif, which is the difference between a chosen face and whatever the
   browser happens to default to. */

:root {
  /* The whole palette, on bare :root. The dark block below redefines these
     same names and nothing else, because a colour defined only inside a media
     query is a colour that does not exist for a visitor whose system reports
     no preference. */

  /* Grounds. Porcelain rather than white, and rather than cream: this is the
     enamel of a machine cabinet, very slightly green, not paper. */
  --porcelain: #f1f2ee;
  --porcelain-2: #e5e7e0;
  --panel: #ffffff;

  /* The field. Whole bands of the page are painted in this, which is the one
     structural difference between this sheet and the one it replaced. */
  --field: #0d2a26;
  --field-2: #12362f;
  --field-ink: #eef1ec;
  --field-soft: #9fb3ac;

  --ink: #101816;
  --ink-soft: #4d5c58;

  --rule: #d3d7cd;
  --rule-strong: #b9bfb2;

  /* Brass, and two values because one cannot do both jobs. The unlit value is
     3.4:1 on porcelain, which is right for a border and a numeral and wrong
     for anything small; the lit value would be 1.9:1 there and is 7.0:1 on the
     field. Measured against the two grounds, not guessed. */
  --brass: #b3761f;
  --brass-lit: #e0a44a;
  --brass-ink: #17110a;

  /* Links carry the field's hue rather than the brass, because brass at text
     size on porcelain is 2.7:1 and a link has to clear 4.5:1. This is 6.8:1. */
  --link: #0a5f52;

  /* The edge of a control, kept outside the greys above for the reason
     styles.css already had to learn once: WCAG 1.4.11 asks 3:1 for a component
     boundary, and --rule is a grouping device that reads at about 1.3:1. */
  --edge: #7d8781;

  /* The primary button is brass on every ground, which is the strongest single
     brand signal on the page. These two flip inside .band-deep, because the
     button is the one place the colour is a background rather than a
     foreground and the unlit brass is too close to the field behind it. */
  --cta-bg: var(--brass);
  --cta-fg: var(--brass-ink);

  --font-text: "Segoe UI Variable Text", "Segoe UI", -apple-system, BlinkMacSystemFont,
    "Helvetica Neue", Arial, sans-serif;
  --font-display: "Segoe UI Variable Display", "Segoe UI", -apple-system, BlinkMacSystemFont,
    "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", "SF Mono", "Segoe UI Mono", "Roboto Mono",
    Menlo, Consolas, monospace;

  --wrap: 74rem;
  --gutter: 1.5rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --porcelain: #0a100e;
    --porcelain-2: #101815;
    --panel: #131b18;

    /* The field has to invert its relationship to the ground or the bands stop
       being bands. In light it is darker than what surrounds it; in dark it is
       lighter, and the alternation reads exactly the same way. */
    --field: #10312b;
    --field-2: #17423a;
    --field-ink: #e9efec;
    --field-soft: #9ab3ac;

    --ink: #e7ece9;
    --ink-soft: #94a49f;

    --rule: #212b28;
    --rule-strong: #33413d;

    --brass: #d9a049;
    --brass-lit: #e9b263;
    --brass-ink: #0f1512;

    --link: #58c6ad;
    --edge: #55645f;
  }
}

*, *::before, *::after { box-sizing: border-box; }

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  /* Explicit, not inherited. The page is served inside chrome that paints its
     own ground, and a transparent body borrows whatever is behind it. */
  background: var(--porcelain);
  color: var(--ink);
  font: 1.0625rem/1.62 var(--font-text);
  -webkit-text-size-adjust: 100%;
  font-variant-numeric: tabular-nums;
  /* The hero art is the one element allowed to leave the measure, and this is
     what stops that bleed becoming a horizontal scrollbar on a narrow window. */
  overflow-x: hidden;
}

img, svg { max-width: 100%; }

/* ---------- type ----------
   One loud thing, the display scale, and nothing else raises its voice. */

h1, h2, h3, h4 {
  margin: 0 0 .5em;
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -.028em;
}
h1 { font-size: clamp(2.5rem, 6.6vw, 4.5rem); line-height: 1; letter-spacing: -.042em; }
h2 { font-size: clamp(1.85rem, 4vw, 2.9rem); line-height: 1.06; letter-spacing: -.034em; }
h3 { font-size: 1.1875rem; line-height: 1.28; letter-spacing: -.018em; }
h4 { font-size: 1rem; line-height: 1.3; letter-spacing: -.012em; }

/* 34em, roughly 66 characters. This is a convergence rather than a new number:
   .measure, .band-head p and .checks li were all already at 34em, so the
   paragraph default was the one thing in the sheet setting a wider column
   than everything the sheet itself reaches for. */
p { margin: 0 0 1.15em; max-width: 34em; }
p:last-child { margin-bottom: 0; }
.measure { max-width: 34em; }

/* Every hover on this site was an instant colour snap, because the sheet
   carried no transition at all. This is most of the difference between a page
   that feels built and one that feels generated, and it costs four rules.

   Colour only, never a transform and never a size. A transition on geometry
   moves text under a cursor that is already travelling toward it. And the
   focus ring at :focus-visible below is deliberately left instant: a focus
   outline that fades in is a focus outline that is not there yet. */
a { color: var(--link); text-decoration-thickness: 1px; text-underline-offset: .18em;
    transition: color .18s ease, text-decoration-color .18s ease; }
a:hover { text-decoration-thickness: 2px; }

:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; }
strong { font-weight: 700; }

.skip {
  position: absolute; left: -9999px;
  background: var(--field); color: var(--field-ink);
  padding: .7rem 1rem; z-index: 200;
  font: 600 .8125rem/1 var(--font-mono);
}
.skip:focus { left: .5rem; top: .5rem; }

/* ---------- the plate ----------
   Every eyebrow, section number, table head and caption. It is the identity
   signal that survives the content being removed: a small mono line inside a
   hair box, the way a serial plate is riveted to the back of a machine. */

.plate {
  display: inline-block;
  font: 600 .6875rem/1 var(--font-mono);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  border: 1px solid var(--rule-strong);
  padding: .45rem .6rem;
  margin: 0 0 1.5rem;
}
.plate-brass { color: var(--brass); border-color: var(--brass); }
.plate-bare { border: 0; padding: 0; margin-bottom: .9rem; }

/* ---------- bands ----------
   Colour-blocked full-bleed sections. This is the layout system and there is
   not a second one: a page is a stack of bands and each band holds one .wrap. */

.wrap { width: min(var(--wrap), 100% - (var(--gutter) * 2)); margin-inline: auto; }
.wrap-narrow { width: min(48rem, 100% - (var(--gutter) * 2)); margin-inline: auto; }

/* Air between sections is the cheapest editorial signal there is, and it is
   most of what separates a consulting firm's page from a software company's
   before a word of it is read. Vertical padding on a full-bleed block with no
   sibling depending on its height, so it cannot reflow anything. .band-tight
   and .band-tight-end below override their own side and still do. */
.band { padding: clamp(4rem, 9vw, 8.5rem) 0; }
.band-shade { background: var(--porcelain-2); }
.band-deep {
  background: var(--field);
  color: var(--field-ink);
  /* Everything inside a deep band reads the same token names with different
     values, so a card, a row list or a button works in either ground with no
     variant class of its own. */
  --brass: var(--brass-lit);
  --cta-bg: var(--brass-lit);
  --ink: var(--field-ink);
  --ink-soft: var(--field-soft);
  --rule: #1d453d;
  --rule-strong: #2c574d;
  --panel: var(--field-2);
  --porcelain-2: var(--field-2);
  --link: var(--brass-lit);
  --edge: #4d6f66;
}
.band-tight { padding-top: clamp(2rem, 4vw, 3rem); }
.band-tight-end { padding-bottom: clamp(2rem, 4vw, 3rem); }

/* ANYTHING A LINK CAN LAND ON HAS TO CLEAR THE STICKY BAR ABOVE IT. The bar is
   67px tall, so without this an anchor puts its own target at scroll position
   zero, which is underneath the nav: measured on /#talk, the heading landed at
   y=0 and the plate naming the section sat 58px off the top of the screen. The
   visitor reads the second paragraph of the thing they just clicked.

   Two selectors because a fragment points at two different kinds of element
   here. The product page's cards link the section itself, and every "talk to
   us" on the site, plus the GET redirect in functions/api/leads.js, links the
   heading inside a band head. The heading gets the larger figure so the plate
   sitting above it clears the bar too, since the plate is what names the
   section. */
.band[id] { scroll-margin-top: 5rem; }
.band-head > h2[id] { scroll-margin-top: 9rem; }

/* ---------- navigation ----------
   Sticky, because a sales site that scrolls a visitor away from its own buy
   button is a sales site with one button fewer.

   NO DROPDOWN, AND THAT IS THE POLICY AGAIN. A hover menu a touch device can
   open needs script, and script-src is none here. The products are reached from
   the cards on the home page and from a column in the footer, both of which
   deep link the sections of /product by fragment, which is two routes where a
   menu would have been one.

   There was a third, a second level rail under this bar carrying the product
   sub pages. Those pages were folded into one /product page and the rail went
   with them, along with the .rail rules that used to live below. */

.nav {
  position: sticky; top: 0; z-index: 100;
  background: var(--field);
  color: var(--field-ink);
  border-bottom: 1px solid #1d453d;
}
.nav-inner {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .6rem 2rem; padding: .85rem 0;
}

.wordmark {
  font: 700 1.25rem/1 var(--font-display);
  letter-spacing: -.035em;
  color: var(--field-ink);
  text-decoration: none;
  margin-right: auto;
  display: inline-flex; align-items: baseline; gap: .18em;
}
.wordmark::after {
  content: "";
  width: .3rem; height: .3rem;
  background: var(--brass-lit);
  display: inline-block;
}
.wordmark:hover { color: var(--brass-lit); }
/* Colour only. A transition on geometry moves the target under a cursor that
   is already travelling toward it. */
.wordmark { transition: color .18s ease; }

.nav-links { display: flex; flex-wrap: wrap; gap: .3rem 1.5rem; list-style: none; margin: 0; padding: 0; }
.nav-links a {
  font: 600 .8125rem/1.4 var(--font-mono);
  letter-spacing: .05em;
  color: var(--field-soft);
  text-decoration: none;
  transition: color .18s ease;
}
.nav-links a:hover { color: var(--field-ink); }
.nav-links a[aria-current="page"] { color: var(--brass-lit); }

.nav-cta { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem 1.25rem; }
.nav-cta .btn { padding: .7rem 1.1rem; }
.nav-cta a.nav-signin {
  font: 600 .8125rem/1.4 var(--font-mono);
  letter-spacing: .05em; color: var(--field-ink); text-decoration: none;
}
.nav-cta a.nav-signin:hover { color: var(--brass-lit); }
.nav-cta a.nav-signin[aria-current="page"] { color: var(--brass-lit); }

/* ---------- hero ---------- */

.hero { padding: clamp(3rem, 7vw, 5.5rem) 0 clamp(3rem, 7vw, 5rem); }
.hero h1 { max-width: 15ch; }
.hero .lede { margin-top: 1.5rem; }

/* The copy column. It carries no look of its own and exists so the hero's two
   halves are both named rather than one named and one positional, because a
   bare div sitting beside .hero-art is the kind of thing a later edit reorders
   without noticing. The zero minimum overrides the grid default of auto, under
   which one long unbroken word in a heading widens the track and shoves the
   screen off the side of the page. */
.hero-copy { min-width: 0; }

.hero-grid { display: grid; gap: clamp(2.5rem, 5vw, 4rem); align-items: center; }
@media (min-width: 64rem) {
  .hero-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); }
  /* The screen runs off the right edge of the measure, which is the one place
     on this site where anything is allowed to leave the grid. A product shot
     fully contained inside a card reads as a thumbnail; one that overruns
     reads as a window onto something bigger. */
  .hero-art { margin-right: calc((100vw - min(var(--wrap), 100vw - 3rem)) / -2 - .5rem); }
}

.lede {
  font-size: clamp(1.125rem, 1.8vw, 1.375rem);
  line-height: 1.45;
  color: var(--ink-soft);
  max-width: 32em;
}
.lede strong { color: var(--ink); font-weight: 650; }

/* ---------- section headings inside a band ---------- */

/* A hairline over every section head, and the eyebrow above it loses its box.
   That pairing is the most recognisable mark an editorial or consulting page
   has, and both halves of it were already in this file: .plate-bare has been
   here since the plate was written and was used on no page at all.

   The rule is on the head rather than on .band, so a section that opens with
   something other than a heading does not get a stray line across it. */
.band-head {
  max-width: 42rem; margin-bottom: clamp(2.25rem, 4vw, 3.25rem);
  border-top: 1px solid var(--rule-strong); padding-top: 1.25rem;
}
/* 18ch, not 20ch. The heading breaks a line earlier and the right third of the
   wrap opens up, so the page reads as deliberately asymmetric rather than as
   centred with a ragged edge. */
.band-head h2 { margin-bottom: .55rem; max-width: 18ch; }
/* The standfirst, the most read line in each section. At a flat 1.125rem it
   inherited the body's leading and read as an ordinary paragraph that happened
   to sit near a heading. A step up and a tighter line is the shape that says
   "this line introduces the section". The cap is a max-width inside a 46rem
   container, so it cannot push a sibling. */
.band-head p {
  color: var(--ink-soft);
  font-size: clamp(1.1875rem, 1.5vw, 1.3125rem);
  line-height: 1.5;
  max-width: 32em;
}

/* ---------- cards ----------
   Filled panels separated by the grid's own background showing through a 1px
   gap, so a run of cards is one object with divisions rather than several
   objects with shadows. No rounded corners and no accent bar down the left
   edge: that bar is the house style of every generated page on the internet
   this year, and the shoulder line along the top of .panel does the same job
   once, in the brass, where it means something. */

.grid {
  display: grid; gap: 1px;
  background: var(--rule); border: 1px solid var(--rule);
  margin: 0; padding: 0; list-style: none;
}
@media (min-width: 40rem) { .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 52rem) { .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 62rem) { .grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.card { background: var(--panel); padding: clamp(1.5rem, 2.6vw, 2rem); }
.card h3 { margin-bottom: .4rem; }
.card p { color: var(--ink-soft); font-size: .9375rem; margin: 0; max-width: 32em; }
.card p + p { margin-top: .7rem; }
.card-icon { display: block; width: 2.5rem; height: 2.5rem; margin-bottom: 1.1rem; }
.card-num {
  display: block;
  font: 700 .75rem/1 var(--font-mono);
  letter-spacing: .12em; color: var(--brass); margin-bottom: 1rem;
}
.card-more {
  display: inline-block; margin-top: 1.1rem;
  font: 600 .8125rem/1 var(--font-mono); letter-spacing: .05em;
  color: var(--link); text-decoration: none;
  transition: color .18s ease;
  border-bottom: 1px solid currentColor; padding-bottom: .25rem;
}
.card-more:hover { color: var(--ink); }

/* ---------- the product cards ----------
   The biggest thing on the home page after the hero, because what is in the
   price and what is an add-on is the one thing the home page asks a visitor to
   take in. The whole panel is the link target rather than a word inside it. */

.prods { display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); }
@media (min-width: 58rem) {
  .prods { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* Two columns, for a grid whose card count is not a multiple of three. The
     add-on set is two, and at three columns that reads as a row with a hole in
     it, which looks like a layout that ran out rather than a group that ended. */
  .prods-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Five, as three across and then two across, on six tracks rather than
     three. A five card group has no honest arrangement on equal columns: two
     across leaves an orphan, three across leaves a hole, and either one reads
     as a grid that ran out of products. Spanning two tracks and then three
     fills every cell, and the wider bottom pair reads as deliberate. The rule
     is positional (the fourth and fifth children) rather than a class on the
     cards, so reordering the group in the markup cannot leave a card carrying
     a width that belongs to a position it no longer sits in. */
  .prods-5 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .prods-5 > * { grid-column: span 2; }
  .prods-5 > :nth-child(4),
  .prods-5 > :nth-child(5) { grid-column: span 3; }
}

/* The brass shoulder arrives on hover as an INSET shadow, not a border. A
   border appearing on hover is 2px of new box that pushes every line of the
   card down as the cursor lands on it. An inset shadow paints inside the box
   already laid out, so nothing moves. Same shoulder grammar as .panel and
   .stat, which is the point: the hover state is the card admitting it is one
   of this sheet's modules rather than a new decoration. */
.prod {
  background: var(--panel);
  padding: clamp(1.75rem, 3vw, 2.5rem);
  display: flex; flex-direction: column; gap: .75rem;
  text-decoration: none; color: inherit;
  box-shadow: inset 0 2px 0 transparent;
  transition: background-color .18s ease, box-shadow .18s ease;
}
.prod:hover { box-shadow: inset 0 2px 0 var(--brass); }
.prod:hover { background: var(--porcelain-2); }
.prod:hover .prod-name { color: var(--link); }
.prod-plate {
  font: 600 .6875rem/1 var(--font-mono); letter-spacing: .14em;
  text-transform: uppercase; color: var(--brass);
}
.prod-name { font: 700 1.5rem/1.1 var(--font-display); letter-spacing: -.03em; margin: 0; }
.prod p { color: var(--ink-soft); font-size: .9375rem; margin: 0; }
.prod-go {
  margin-top: auto; padding-top: 1.25rem;
  font: 600 .8125rem/1 var(--font-mono); letter-spacing: .05em; color: var(--link);
}
.prod-art { display: block; width: 100%; height: auto; margin-bottom: .5rem; }

/* ---------- proof band ----------
   Numbers in the mono face at display size. Every value on this site is a
   published price, a count of something in this repository, or a property of
   the code. Nothing here is a customer statistic, because there is no honest
   way to put one on the page yet. */

/* MARGIN-BLOCK, NEVER THE MARGIN SHORTHAND, and this cost the stats band its
   left gutter. .stats is a ul and wants the browser's default vertical margin
   gone, but it is set on the same element as .wrap, and .wrap centres the page
   with margin-inline: auto. `margin: 0` here is a later rule writing 0 into
   BOTH axes, so the band stopped centring, sat flush against the left edge of
   the viewport and cut the first stat's sentence off mid word.

   Nothing could have caught it but looking. The markup is correct, the CSS
   parses, every class resolves, and 1618 tests passed with the page in that
   state. Any rule that lands on an element also carrying .wrap has to leave the
   inline axis alone. */
.stats { display: grid; gap: clamp(1.75rem, 3vw, 2.5rem) 2rem; margin-block: 0; padding: 0; list-style: none; }
@media (min-width: 40rem) { .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 58rem) { .stats { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.stat { border-top: 2px solid var(--brass); padding-top: 1rem; }
.stat-value {
  display: block;
  font: 700 clamp(2rem, 3.6vw, 2.75rem)/1 var(--font-mono);
  letter-spacing: -.04em; color: var(--ink); margin-bottom: .5rem;
}
.stat-label { display: block; font-size: .9375rem; color: var(--ink-soft); max-width: 22em; }

/* ---------- the ordered explanation ---------- */

.steps {
  counter-reset: step; list-style: none; margin: 0; padding: 0;
  display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule);
}
@media (min-width: 52rem) {
  .steps-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .steps-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.steps li { counter-increment: step; background: var(--panel); padding: clamp(1.5rem, 2.6vw, 2rem); }
.steps li::before {
  content: counter(step, decimal-leading-zero);
  display: block;
  font: 700 .75rem/1 var(--font-mono);
  letter-spacing: .12em; color: var(--brass); margin-bottom: 1rem;
}
.steps h3 { margin-bottom: .4rem; }
.steps p { margin: 0; color: var(--ink-soft); font-size: .9375rem; }

/* ---------- definition rows ---------- */

.rows { margin: 0; }
/* --rule-strong, not --rule. Measured against the porcelain ground, --rule is
   1.30:1, which is a divider nobody can see, and this list is the most
   editorial module on the product page. --rule-strong is 1.67:1. */
.rows > div { display: grid; gap: .3rem 2.5rem; padding: 1.35rem 0; border-top: 1px solid var(--rule-strong); }
.rows > div:first-child { border-top: 2px solid var(--brass); }
@media (min-width: 52rem) { .rows > div { grid-template-columns: 17rem minmax(0, 1fr); } }
.rows dt { font-weight: 700; letter-spacing: -.015em; }
.rows dd { margin: 0; color: var(--ink-soft); font-size: .9375rem; }
.rows dd > :last-child { margin-bottom: 0; }

/* ---------- checklists ---------- */

.checks { list-style: none; margin: 0; padding: 0; display: grid; gap: .8rem; }
.checks li { position: relative; padding-left: 1.9rem; font-size: .9375rem; color: var(--ink-soft); max-width: 34em; }
/* A rule rather than a tick. A tick is the grammar of a pricing table, where it
   means "included in this column", and these lists are the substance of the
   product page rather than a comparison of tiers. The plate above each section
   already says what is included, so the tick said it a second time.

   Replace this rule wholesale rather than editing properties inside it. The
   border pair and the rotate all have to go together, or what is left is a 2px
   sliver turned 45 degrees on a brass ground. The text does not move: the mark
   is absolutely positioned inside a padding-left that is not changing. */
.checks li::before {
  content: "";
  position: absolute; left: 0; top: .72em;
  width: .9rem; height: 2px;
  background: var(--brass);
}
.checks strong { color: var(--ink); }

/* ---------- panel and note ---------- */

.panel {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-top: 2px solid var(--brass);
  padding: clamp(1.5rem, 3vw, 2.25rem);
}
.panel > :last-child { margin-bottom: 0; }
.panel .plate { margin-bottom: 1rem; }
.panel h3 { margin-bottom: .5rem; }

.note { color: var(--ink-soft); font-size: .9375rem; max-width: 36em; }

/* ---------- pull quote ----------
   One sentence a page, set at heading scale with no attribution, because it is
   Launderworks's own claim and dressing it as a testimonial would be a fabrication. */

.quote { margin: 0; max-width: 24ch; }
.quote p {
  font: 700 clamp(1.6rem, 3.4vw, 2.5rem)/1.1 var(--font-display);
  letter-spacing: -.035em; margin: 0; max-width: none;
}
.quote-note { margin-top: 1.25rem; color: var(--ink-soft); font-size: .9375rem; max-width: 34em; }

/* ---------- split: prose beside a drawn screen ---------- */

.split { display: grid; gap: clamp(2rem, 4vw, 3.5rem); align-items: center; }
@media (min-width: 60rem) {
  .split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .split-wide-art { grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr); }
  .split-flip > :first-child { order: 2; }
}

/* ---------- product shots ----------
   A figure holding an inline SVG and a caption. The caption is not decoration:
   every screen on this site is drawn, and a visitor is entitled to know that
   before they read a number off one. */

.shot { margin: 0; }
.shot-frame {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--rule-strong);
  background: var(--panel);
}
.shot-cap {
  display: block; margin-top: .8rem;
  font: 500 .6875rem/1.5 var(--font-mono);
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft);
}

/* ---------- buttons ---------- */

.actions { display: flex; flex-wrap: wrap; gap: .8rem 1rem; align-items: center; margin-top: 2.25rem; }

/* A PLATE USED AS A TRAILING NOTE, which is the one place it needs spacing it
   does not carry itself. .plate-bare has margin-bottom and no margin-top,
   because a plate is nearly always a small label sitting ABOVE the thing it
   names. Under a row of buttons the relationship inverts, and with no top
   margin the shipped result was the price line touching the call to action
   with a gap of exactly zero pixels. It read as one crowded block rather than
   a button and its footnote, on the hero of five of the ten pages.

   Spaced as a relationship rather than by loosening .plate-bare, so every
   plate that is still a leading label keeps the spacing it was given. */
.actions + .plate { margin-top: 1rem; }

.btn {
  display: inline-block;
  transition: background-color .18s ease, border-color .18s ease, color .18s ease;
  font: 700 .875rem/1 var(--font-mono);
  letter-spacing: .04em;
  padding: 1rem 1.6rem;
  text-decoration: none;
  border: 2px solid var(--cta-bg);
  background: var(--cta-bg);
  color: var(--cta-fg);
  cursor: pointer;
}
.btn:hover { background: var(--ink); border-color: var(--ink); color: var(--porcelain); }
.band-deep .btn:hover,
.nav .btn:hover { background: var(--field-ink); border-color: var(--field-ink); color: var(--field); }
.btn:disabled { opacity: .6; cursor: default; }

.btn-line { background: transparent; color: var(--ink); border-color: var(--edge); }
.btn-line:hover { background: transparent; border-color: var(--ink); color: var(--ink); }
.band-deep .btn-line:hover { background: transparent; border-color: var(--field-ink); color: var(--field-ink); }

/* ---------- closing call to action ---------- */

.cta { display: grid; gap: 2rem; align-items: end; }
@media (min-width: 58rem) { .cta { grid-template-columns: minmax(0, 1fr) auto; } }
.cta h2 { max-width: 17ch; margin-bottom: .6rem; }
.cta p { color: var(--ink-soft); max-width: 34em; }
.cta .actions { margin-top: 0; }

/* ---------- lead form ----------

   THE ONLY PLACE THE HONEYPOT CAN BE HIDDEN. style-src is self with no
   unsafe-inline, so a style attribute on the input would be dropped by the
   browser and the field would render, visibly, on the live site while looking
   perfect in a local file:// preview. It has to be a rule in this sheet.
   functions/lib/marketing.test.mjs forbids the attribute outright, which is
   what stops that being rediscovered the hard way.

   It is moved off screen rather than given display:none. Some bots skip fields
   that are display:none precisely because that is the obvious tell, and an
   off-screen field is filled by more of them. aria-hidden and tabindex of -1
   on the markup keep it away from anybody using a keyboard or a screen reader,
   which is the accessibility half of the same problem. */

.honey { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form { margin: 0; }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 0 1.5rem; }
.field { margin-bottom: 1.15rem; }
.field label {
  display: block; margin-bottom: .45rem;
  font: 600 .6875rem/1.4 var(--font-mono);
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft);
}
.req { color: var(--brass); }

.field input,
.field textarea {
  width: 100%;
  padding: .8rem .85rem;
  border: 1px solid var(--edge);
  border-radius: 0;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: .9375rem;
}
.field textarea { resize: vertical; min-height: 7rem; }
.field input:focus,
.field textarea:focus { outline: 2px solid var(--brass); outline-offset: 1px; border-color: var(--brass); }

.form-actions { display: flex; flex-wrap: wrap; gap: .8rem 1.25rem; align-items: center; margin-top: .6rem; }
.form-actions .plate { margin-bottom: 0; }

/* The promise under the submit button. The most load-bearing sentence on the
   page, because a form is asking somebody to trust silence, so it gets its own
   rule rather than inheriting the muted note style used for asides. */
.form-promise {
  max-width: 44rem; margin: 1.25rem 0 0;
  color: var(--ink); font-size: .9375rem; line-height: 1.55;
  border-left: 2px solid var(--brass); padding-left: 1rem;
}

/* ---------- sign in ----------
   The one page on this site with a control that does something, so it is the
   one page whose main element is a panel beside an argument rather than a
   stack of bands. */

.signin {
  display: grid; gap: clamp(2.5rem, 5vw, 4rem); align-items: start;
  padding: clamp(3rem, 6vw, 5rem) 0;
}
@media (min-width: 58rem) { .signin { grid-template-columns: minmax(0, 1fr) minmax(0, 26rem); } }
.signin-card {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-top: 3px solid var(--brass);
  padding: clamp(1.75rem, 3.5vw, 2.5rem);
}
.signin-card h2 { font-size: 1.5rem; margin-bottom: 1.5rem; }
.signin-card .btn { width: 100%; text-align: center; }
.signin-aside h1 { max-width: 12ch; }
.signin-links { margin-top: 1.5rem; display: grid; gap: .65rem; font-size: .9375rem; }
.signin-links a { color: var(--link); }

/* The live region above the form. Coloured through a data attribute rather
   than a class, so marketing/sign-in.js carries no copy of this sheet's
   vocabulary. It is hidden until it has something to say; a permanently
   present empty box above a login form reads as a page that failed to load.

   THE ACCENT IS ON THE TOP EDGE, not the left one, and that is the sheet's
   grammar rather than a preference. .panel, .stat, .rows and .signin-card all
   carry a shoulder along the top in brass; this was the one element reaching
   for a coloured tab down the left side instead. That tab is also the single
   most recognisable tell of a generated interface, which is the same argument
   the comment above .grid already makes about accent bars on cards, so the
   consistent choice and the distinctive one are the same choice here.

   The colour is load bearing and stays: it is the only thing besides the words
   that separates a refusal from a confirmation, and somebody reading a login
   error is not reading carefully. */
.msg {
  margin: 0 0 1.25rem; padding: .85rem 1rem;
  font-size: .9375rem;
  border-top: 2px solid var(--edge);
  background: var(--porcelain-2);
}
.msg[data-kind="error"] { border-top-color: #b3261e; }
.msg[data-kind="ok"] { border-top-color: var(--brass); }
@media (prefers-color-scheme: dark) {
  .msg[data-kind="error"] { border-top-color: #f2857c; }
}

/* ---------- footer ---------- */

.foot { background: var(--field); color: var(--field-ink); padding: clamp(3rem, 6vw, 4.5rem) 0 2.5rem; }
.foot a { color: var(--field-soft); text-decoration: none; transition: color .18s ease; }
.foot a:hover { color: var(--field-ink); }
/* Without this rule an aria-current in the footer is real for a screen reader
   and invisible to everybody else, which is worse than not marking it. */
.foot a[aria-current="page"] { color: var(--brass-lit); }

.foot-grid { display: grid; gap: 2.5rem 2rem; }
@media (min-width: 46rem) { .foot-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 62rem) { .foot-grid { grid-template-columns: 1.5fr repeat(3, minmax(0, 1fr)); } }

.foot-brand .wordmark { margin-right: 0; margin-bottom: .9rem; }
.foot-brand p { color: var(--field-soft); font-size: .9375rem; max-width: 24em; }
.foot-col h2 {
  font: 600 .6875rem/1 var(--font-mono);
  letter-spacing: .14em; text-transform: uppercase; color: var(--field-soft); margin: 0 0 1.1rem;
}
.foot-links { list-style: none; margin: 0; padding: 0; display: grid; gap: .65rem; }
.foot-links a { font-size: .9375rem; }

.foot-base {
  margin-top: clamp(2.5rem, 5vw, 3.5rem); padding-top: 1.5rem;
  border-top: 1px solid #1d453d;
  display: flex; flex-wrap: wrap; gap: .6rem 2rem; align-items: baseline;
}
.foot-base p {
  margin: 0; margin-right: auto; max-width: none;
  font: 500 .75rem/1.5 var(--font-mono); letter-spacing: .05em; color: var(--field-soft);
}
.foot-base a { font: 500 .75rem/1.5 var(--font-mono); letter-spacing: .05em; }

/* ---------- the drawn screens ----------
   Classes rather than fill attributes, so one palette serves both colour
   schemes and the deep bands. An image element could not do this, which is the
   argument in the header of this file. */

.s-paper { fill: var(--panel); }
.s-ground { fill: var(--porcelain); }
.s-chrome { fill: var(--porcelain-2); }
.s-line { stroke: var(--rule); stroke-width: 1; fill: none; }
.s-line-strong { stroke: var(--rule-strong); stroke-width: 1; fill: none; }
.s-ink { fill: var(--ink); }
.s-soft { fill: var(--ink-soft); }
.s-faint { fill: var(--rule); }
.s-brass { fill: var(--brass); }
.s-brass-line { stroke: var(--brass); stroke-width: 2; fill: none; }
.s-field { fill: var(--field); }
.s-field-2 { fill: var(--field-2); }
.s-on-field { fill: var(--field-ink); }
.s-label { font: 600 9px var(--font-mono); letter-spacing: .1em; fill: var(--ink-soft); }
.s-value { font: 700 11px var(--font-mono); fill: var(--ink); }
.s-value-brass { font: 700 11px var(--font-mono); fill: var(--brass); }
.s-head { font: 700 13px var(--font-display); fill: var(--ink); }
.s-icon { stroke: var(--brass); stroke-width: 2; fill: none; stroke-linecap: square; }
