/*
Theme Name: Off Your Plate Books
Theme URI: https://offyourplatebooks.ca
Author: Off Your Plate Books Ltd.
Description: Hand-built classic theme for Off Your Plate Books. No page builder, no parent theme, no framework, no JavaScript on the front end. Every section of the site is a registered block pattern, so copy is edited in the normal WordPress editor. Carries a Draft mode switch that shows a draft bar and blocks search engines from one place, off by default since launch.
Version: 0.18.0
Requires at least: 6.4
Tested up to: 7.1.4
Requires PHP: 7.4
License: Proprietary — all rights reserved
Text Domain: off-your-plate

Built 1 November 2026. The design, the palette and the geometry of the mark are
documented in 03-WEBSITE.md and 20-BRANDING-AND-DEMAND.md Part 2c. The static
site in website/ is the reference rendering: if this theme and those files ever
disagree, the static files are what was signed off.

0.18.0 (8 November 2026): A MARKETING CONSENT BOX ON THE SNAPSHOT FORM. Peter is
keeping enquiry details for future marketing, and the privacy policy moved to
Version 1.1 to say so. The box is what makes that lawful rather than merely
disclosed: unticked by default, not required, and in its own sentence rather
than folded into the fine print, because consent bundled into other text is not
consent. It is also what makes "future" true -- an enquiry on its own gives
implied consent that expires, while a ticked box does not.

   Stored as _oyp_consent and shown as a Marketing column on the Snapshot
   requests screen, so "did they agree?" is never a database question asked in a
   hurry. The fine print under the form stops claiming the details go nowhere
   else, and now says plainly that the QuickBooks reports are never used for
   marketing -- which is Peter's instruction: enquiry details only, no financial
   data kept.

   .formrow.formcheck is new in both stylesheets. .formrow stacks its children
   and gives every input 11px of padding and a border, which on a checkbox draws
   a large grey square; both are undone for this row, and the input sits inside
   its label so the words are part of the hit area.

0.17.0 (8 November 2026): THE STYLESHEET CACHE-BUSTER HAD NEVER MOVED. functions.php
carried a hardcoded define( 'OYP_VERSION', '0.4.0' ), and that constant is the
`?ver=` on style.css -- the thing a browser uses to decide whether its cached
copy is still good. So every CSS change from 0.5.0 to 0.16.0 went out under a
version string that had not moved, and a returning visitor kept being served the
stylesheet they already had: the width system, the masthead rework, the cards,
the phone pass, none of it. It now reads wp_get_theme()->get('Version'), so
bumping the header is enough and it cannot drift again.

   Found by looking at the rendered page after the domain move and seeing it ask
   for style.css?ver=0.4.0 while the file itself said 0.16.0.

0.16.0 (8 November 2026): THE EXPORT SHEET LINKED A 404. The email sent to every
person who asks for a Snapshot ended "The four terms are set out in full at" and
then home_url('/free-ledger-snapshot/'), which is not the slug. The page is
/snapshot/. Found by reading a sheet that had just been delivered, not the code.

   Same class as the privacy link fixed in 0.15.0, in the same file, and 0.15.0
   missed it because the search was for the word "privacy" rather than for every
   internal URL the theme builds. Every home_url() path has now been walked and
   fetched: /, /privacy/ and /snapshot/ all return 200, and the theme constructs
   no others. That walk is the check to repeat, not a grep for one word.

   NOTE, not a theme matter: the site timezone is UTC+0 while the practice is in
   Chilliwack. The sheet therefore names tomorrow's date for the Profit and Loss
   end date and the two ageing dates from about 16:00 Pacific onward, which will
   not match what the client's own QuickBooks calls "today". Settings > General.

0.15.0 (8 November 2026): THE SNAPSHOT FORM ASKS ABOUT THE BUSINESS. After the
Havn Snapshot called badly kept books "in reasonable order" (six reports could
not see the backlog, the GST coding or whose revenue it was), the request form
gained three sections: the business (legal name, website, kind of business from
a list keyed to the industry references with Other written in, what it sells and
who pays, year-end month), where the books stand (five short questions: money
held for others, GST, last reconciliation, For Review count, prior system, plus
the old hours band), and you. Answers survive a failed submission for fifteen
minutes. The export sheet asks for seven reports, adding the Profit and Loss by
month, and names the trial-balance dates from the year-end given. The notice to
help@ carries every answer and the industry reference to read. No JavaScript:
the Other box is shown by CSS :has(). NOT RUN IN PLAYGROUND: no PHP on the
machine that wrote it; brackets checked by script only. Test before upload.

0.15.0 (8 November 2026): THE SNAPSHOT FORM'S PRIVACY LINK WAS A 404. Under a
form collecting a name and an email address, "Privacy policy" pointed at
home_url('/privacy-policy/') -- WordPress's own auto-created stub, which on this
site is a Draft. The live page returned HTTP 404. It now points at '/privacy/',
which is the real policy. Found by reading the rendered form, not the source.

   Shipping this also carries the longer Snapshot form to the live site. The
   installed copy of inc/snapshot-requests.php was 398 lines asking four things
   (website, name, email, hours); the workspace copy is 677 lines and asks what
   the page copy and the privacy policy now describe -- legal name, website,
   kind of business, what it sells and who pays, year-end month, and the short
   answers about where the books stand. Until this is installed, the privacy
   policy overstates what the form collects.

0.14.0 (8 November 2026): THE MAIL TRANSPORT BECAME A CHOICE. Peter picked a
send-only key at a transactional provider over a Google app password, the
reasoning being that an app password for help@ is a plaintext IMAP key to the
client-facing mailbox, sitting on a shared host, while a transactional provider
has no inbox behind the credential at all.

   `OYP_SMTP_HOST` and `OYP_SMTP_PORT` are now read from wp-config.php, falling
   back to smtp.gmail.com and 587. They had to stop being `const` in
   inc/mail.php: wp-config.php loads long before the theme, so defining either
   there and then declaring a `const` of the same name here is a redeclaration
   -- PHP keeps the first and warns, which prints above the page wherever
   WP_DEBUG_DISPLAY is on.

   The From header is no longer rewritten off Google. Gmail refuses a From it
   has not verified, so there the header is still forced to the authenticated
   mailbox with the intended address kept as Reply-To. A provider authenticates
   a KEY and it is the verified DOMAIN that entitles us to the address, so
   forcing From to the username would replace a correct
   help@offyourplatebooks.ca with something like `resend`, which is not an
   address. Off Google the From is left exactly as sent unless OYP_SMTP_FROM
   says otherwise, and the envelope sender is only set when it is a real
   address. The wp-admin notice stops naming Google.

0.13.0 (8 November 2026): LAUNCH DAY, at Peter's instruction. Draft mode now
defaults to OFF. It defaulted to ON for the whole draft period so that a fresh
install was locked down until somebody decided otherwise — which inverted into
the wrong default on the one day it mattered, because the theme is uploaded
fresh to the live site, no theme_mod exists yet, and the default is what
applies. A TRUE default would have greeted the first live visitor with a DRAFT
bar and a noindex tag. The switch still works both ways: turn it ON for a
staging copy. Alongside it, a list of pages that are written but deliberately
not published — Pricing and Construction — which now carry a noindex meta and a
robots.txt Disallow of their own, independent of Draft mode. Setting a page to
Draft in wp-admin already keeps it unindexed, so the list earns its place in
exactly one case: a page on it that is later published, as Construction may be
if it becomes an ad landing page, stays unindexed until its slug comes off the
list. The X-Robots-Tag header is unchanged and remains site-wide only, because
wp_headers runs before the query is resolved and cannot know the page.

   Later the same day, Peter: "can you keep the draft labels on unpublished
   pages?" So the draft bar now has two flavours. Site-wide while Draft mode is
   on, as before; and on any page in the unpublished list even when Draft mode
   is OFF, reading "NOT PUBLISHED. This page is written but parked." The
   site-wide wording wins where both apply, the whole site being a draft being
   the bigger statement. The reason a parked page carries a label did not go
   away when the site launched, which is the whole point.

   Folded into 0.13.0 rather than minted as 0.14.0: 0.13.0 was built an hour
   earlier, has never been installed anywhere, and this is the same launch-day
   change finished properly.

0.12.0 (7 November 2026): About and then For accountants left the header menu
for the footer, where both already were, at Peter's instruction. The header is
three items. THE NAV WENT BACK BESIDE THE WORDMARK — three labels need 274.3px
against the 350.5px there — so the row of its own introduced in 0.11.0 is gone
and the masthead is 100px again instead of 138px. On a phone all three show:
the rule that hid the section anchor there has been removed. In wp-admin,
remove About and For accountants from the header menu; both stay in the footer
menu. Also added: a FOOTER MENU FALLBACK. The footer had none, and with both
pages now reachable only from the footer an unassigned footer menu would have
left it with no links at all and stranded them.

0.11.0 (7 November 2026): 'The system' is back in the menu, and the nav moved
to its own row under the wordmark so all five labels fit whole. On a phone
the nav still shows four: the section anchor is hidden there.

0.10.0 (7 November 2026): the Ledger Diagnostic joined the menu and 'The
system' left it for the footer, which is a nav-fallback change here and a
menu change in wp-admin. No stylesheet change.

0.9.0 (7 November 2026): the phone masthead stops sticking, and its menu
links are 44px tall instead of 21px. Nothing above 560px changes.

0.8.0 (7 November 2026): one width for the whole page. The masthead, the
draft bar and the footer left the 1040px band and joined the content column,
which needed the nav tightened to .82rem on a 14px gap; the lockup is now
driven by --wordmark and shrinks on a phone, with the tagline following it.

0.7.0 (7 November 2026): the header plate carries its well again, so the O
matches the email signature and the letterhead; the tagline is set to the
wordmark's exact width; and the footer's registration line is gone, along
with the Customizer fields for the registration number, the street address
and the telephone, which nothing else read.

0.6.0 (7 November 2026): no stylesheet change. Pricing is parked until the
rate card is settled, so Pricing came out of the nav fallback and the CTA
pattern's second button, the tiers pattern is marked as a parked scaffold,
and the home page copy was revised. Version bumped so the upload is the one
that matches the pasted page bodies.

Executed and verified 1 November 2026 in WordPress Playground on WordPress
7.1.4 and PHP 8.5.12: installs, activates with no theme errors, registers all
sixteen patterns, renders the front page and the 404 with no notice, warning,
deprecation or fatal, and lays out correctly at 1280px and 390px.
*/

/* ─────────────────────────────────────────────────────────────────────────
   Typefaces, served from this theme's own fonts/ folder. Nothing is fetched
   from a font CDN, so no visitor request leaves this domain. Subset to latin
   and latin-ext; the unicode-range on each face means a file is downloaded
   only when a page actually uses a character from it. Licences and the full
   list are in fonts/LICENSE.txt.
   ───────────────────────────────────────────────────────────────────────── */
  /* Inter */
  @font-face{
    font-family:'Inter'; font-style:normal; font-weight:400; font-display:swap;
    src:url(fonts/inter-latin.woff2) format('woff2');
    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:'Inter'; font-style:normal; font-weight:400; font-display:swap;
    src:url(fonts/inter-latin-ext.woff2) format('woff2');
    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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face{
    font-family:'Inter'; font-style:normal; font-weight:500; font-display:swap;
    src:url(fonts/inter-latin.woff2) format('woff2');
    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:'Inter'; font-style:normal; font-weight:500; font-display:swap;
    src:url(fonts/inter-latin-ext.woff2) format('woff2');
    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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face{
    font-family:'Inter'; font-style:normal; font-weight:600; font-display:swap;
    src:url(fonts/inter-latin.woff2) format('woff2');
    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:'Inter'; font-style:normal; font-weight:600; font-display:swap;
    src:url(fonts/inter-latin-ext.woff2) format('woff2');
    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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  /* Source Serif 4 */
  @font-face{
    font-family:'Source Serif 4'; font-style:normal; font-weight:600; font-display:swap;
    src:url(fonts/source-serif-4-latin.woff2) format('woff2');
    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:'Source Serif 4'; font-style:normal; font-weight:600; font-display:swap;
    src:url(fonts/source-serif-4-latin-ext.woff2) format('woff2');
    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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face{
    font-family:'Source Serif 4'; font-style:normal; font-weight:700; font-display:swap;
    src:url(fonts/source-serif-4-latin.woff2) format('woff2');
    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:'Source Serif 4'; font-style:normal; font-weight:700; font-display:swap;
    src:url(fonts/source-serif-4-latin-ext.woff2) format('woff2');
    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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  /* IBM Plex Mono */
  @font-face{
    font-family:'IBM Plex Mono'; font-style:normal; font-weight:400; font-display:swap;
    src:url(fonts/ibm-plex-mono-400-latin.woff2) format('woff2');
    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:'IBM Plex Mono'; font-style:normal; font-weight:400; font-display:swap;
    src:url(fonts/ibm-plex-mono-400-latin-ext.woff2) format('woff2');
    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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face{
    font-family:'IBM Plex Mono'; font-style:normal; font-weight:500; font-display:swap;
    src:url(fonts/ibm-plex-mono-500-latin.woff2) format('woff2');
    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:'IBM Plex Mono'; font-style:normal; font-weight:500; font-display:swap;
    src:url(fonts/ibm-plex-mono-500-latin-ext.woff2) format('woff2');
    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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }

  :root{
    /* ONE text measure for the whole site. Every block of reading matter is
       set to this, and so is the content column, so nothing on a page is a
       different width from the thing above it. Added 7 November 2026 at
       Peter's request. The band — masthead, draft bar, footer — and the price
       table keep the wider .wrap. The band was retired the same evening: see
       .wrap below. */
    --measure:68ch;
    /* THE WORDMARK'S SIZE, as a variable so the whole lockup scales as one
       thing. The tagline is a fixed fraction of it (see .brandline), so the
       two keep their shared left and right edge at any size — change this one
       number and the tagline follows. 7 November 2026. */
    --wordmark:1.85rem;
    --paper:#FBF8F3;
    --surface:#FFFFFF;
    --surface-2:#F3EEE5;
    --ink:#1A1A17;
    --ink-2:#55534C;
    --ink-3:#6A675E;
    --rule:#CFC7B8;
    --rule-soft:#E2DCD1;
    --accent:#1F5D4C;
    --accent-ink:#FFFFFF;
    --accent-soft:#E6EFE9;
    --shadow:0 1px 0 rgba(23,28,24,.06);
  }
  /* The dark greys were lifted twice on 2 Nov 2026. First ink-3 #827E73 →
     #8D897E, because at the old value the eyebrows, small print and stamp were
     4.22:1 on --surface — under the 4.5:1 WCAG AA floor. Then both greys again,
     at Peter's instruction: he reads this on a dark screen and both were still
     too faint in use. AA was never the standard being aimed at here, it is the
     floor.
       ink-2 #ABA79B → #C6C2B6   7.60 → 10.27 on paper
       ink-3 #8D897E → #A8A398   5.24 →  7.28 on paper
     Both clear 6.2:1 on the darkest surface. Hue unchanged; only lightness
     moves, so the palette still reads as one family. The light palette was
     already clear at 5.01–5.65 and is untouched. */
  @media (prefers-color-scheme: dark){
    :root:not([data-theme="light"]){
      --paper:#16150F;
      --surface:#1D1C15;
      --surface-2:#24231B;
      --ink:#EDE9E0;
      --ink-2:#C6C2B6;
      --ink-3:#A8A398;
      --rule:#383629;
      --rule-soft:#2B2A20;
      --accent:#6FB89E;
      --accent-ink:#16150F;
      --accent-soft:#1E2A24;
      --shadow:0 1px 0 rgba(0,0,0,.4);
    }
  }
  :root[data-theme="dark"]{
    --paper:#16150F; --surface:#1D1C15; --surface-2:#24231B;
    --ink:#EDE9E0; --ink-2:#C6C2B6; --ink-3:#A8A398;
    --rule:#383629; --rule-soft:#2B2A20;
    --accent:#6FB89E; --accent-ink:#16150F; --accent-soft:#1E2A24;
    --shadow:0 1px 0 rgba(0,0,0,.4);
  }

  *{box-sizing:border-box}
  body{
    margin:0; background:var(--paper); color:var(--ink);
    font-family:Inter,"Segoe UI",system-ui,-apple-system,sans-serif;
    font-size:16px; line-height:1.62; -webkit-font-smoothing:antialiased;
  }
  /* ONE width on the site. The masthead, the draft bar, every section of
     content and the footer all sit in the same column, so the page has a
     single left edge from the logo to the last footer link.
     This was tried earlier on 7 November and reverted, because the wordmark,
     its tagline and FIVE nav links would not fit on one row inside it. Two
     things changed that evening: Pricing came out of the menu, and the nav was
     tightened to .82rem on a 14px gap. The masthead now needs 676px of the
     686px available — 10px of slack, measured, not estimated.
     IF A NAV ITEM IS EVER ADDED, re-measure. There is one row's worth of room
     and no more; past it the nav wraps under the wordmark.
     class="wrap wide" is the one opt-out and takes the old 1040px band. */
  .wrap{max-width:calc(var(--measure) + 40px); margin:0 auto; padding-inline:20px}
  .wrap.wide{max-width:1040px}
  .prose{max-width:var(--measure)}
  h1,h2,h3{font-family:"Source Serif 4",Georgia,"Times New Roman",serif; font-weight:600; text-wrap:balance; margin:0}
  h1{font-size:clamp(2rem,5.4vw,3.35rem); line-height:1.1; letter-spacing:-.015em}
  h2{font-size:clamp(1.45rem,3.2vw,2.05rem); line-height:1.18; letter-spacing:-.01em}
  h3{font-size:1.06rem; line-height:1.35}
  p{margin:0}
  a{color:var(--accent)}
  .mono{font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace}
  .eyebrow{
    font-family:"IBM Plex Mono",ui-monospace,monospace; font-size:.7rem; letter-spacing:.14em;
    text-transform:uppercase; color:var(--ink-3);
  }
  .lede{font-size:clamp(1.02rem,2vw,1.2rem); color:var(--ink-2)}

  /* ── banner ─────────────────────────────── */
  .topbar{
    border-bottom:1px solid var(--rule); background:var(--surface);
    position:sticky; top:env(safe-area-inset-top,0px); z-index:5;
  }
  .topbar .wrap{display:flex; align-items:center; justify-content:space-between; gap:16px 20px; padding-block:15px; flex-wrap:wrap}
  /* The name leads the bar. 1.18rem → 1.46rem → 1.85rem, both steps on 2 Nov
     2026: the mark is a plate standing in for the O, so the wordmark reads
     "ff Your Plate" and the name has to be big enough to be read rather than
     worked out. The nav below is held at .86rem so the gap does the work.
     Optical size moves with the size. Measured off this site's own font file
     (wght 600) by canvas ink extents: the f top is .74406em at 23.36px and
     .74469em at 29.6px, so 110% of it is .81916em against the .817em the plate
     is set at — 0.06px apart at header size, inside a device pixel. The
     recorded geometry stands. */
  .brand{font-family:"Source Serif 4",Georgia,serif; font-size:var(--wordmark); font-weight:600; letter-spacing:-.01em}
  .brand span{color:var(--accent)}
  /* The tagline, under the wordmark (Peter, 2 Nov 2026). It sits with the name
     rather than in the hero: that way it carries on every page, and it is not a
     fourth statement of the same idea stacked on top of the eyebrow and the h1.
     Inter rather than the mono the eyebrows use — a subtitle to the name, not
     another label. flex-start so the home link stays the width of the wordmark
     instead of stretching to the tagline. */
  .brandblock{display:flex; flex-direction:column; align-items:flex-start; gap:1px}
  /* THE TAGLINE IS SET TO THE WORDMARK'S WIDTH (Peter, 7 November 2026), so the
     two lines share a left and a right edge and read as one lockup. Both are
     fixed strings in fixed fonts, so the width is deterministic and solved for
     rather than approximated: the wordmark measures 313.92px at 1.85rem, and
     the tagline's 35 characters reach the same width at .9375rem with .1057em
     of tracking — measured in the browser, ink edge to ink edge, to within
     .02px. CHANGE EITHER STRING OR EITHER FONT AND THIS MUST BE RE-MEASURED;
     it will not self-correct. A 1.6px trailing letter-space sits past the last
     full stop, which is why the box is wider than the ink.
     THE SIZE IS TIED TO THE WORDMARK'S, at .506757 of it (15px of 29.6px), and
     the tracking is in em. Both widths are then linear in --wordmark, so the
     match survives the lockup being scaled — it holds to about 2px at the
     1.6rem mobile size, where text hinting stops the wordmark scaling quite
     linearly. */
  .brandline{font-size:calc(var(--wordmark) * .506757); letter-spacing:.1057em; line-height:1.35; color:var(--ink-2)}
  /* THE NAV SITS BESIDE THE WORDMARK, which is where it began and where it is
     again (Peter, 7 November 2026, third change that evening).

     The arc, because the figures are the whole argument. The row beside the
     wordmark holds 350.5px: 686.4px of ink less the 315.5px lockup and the
     20px flex gap. When the Ledger Diagnostic made a fifth item, five whole
     labels needed 454px, overflowed, and dropped below the wordmark of their
     own accord; rather than cut the labels the nav was given a row of its own,
     at a cost of 38px of masthead height. Peter then moved About to the footer
     (four labels, 397.2px — still 46.7px over, so the own row stood) and then
     For accountants as well. Three labels need 274.3px, which fits with 76.2px
     to spare, so the own row has no work left to do and the masthead is back
     to 100px.

     HEADROOM: a fourth item fits only if its label is under about 58px — which
     "About" (37px) and "Pricing" (~48px) are and "For accountants" (100px) is
     not. Past that the nav drops below the wordmark again, which is a safe
     failure rather than an overflow, but it costs 38px. Re-measure before
     adding one; every figure here was measured in the browser at 1024px. */
  .topbar nav{display:flex; gap:18px; font-size:.86rem; flex-wrap:wrap}
  .topbar nav a{color:var(--ink-2); text-decoration:none}
  .topbar nav a:hover{color:var(--accent)}

  /* ── buttons ────────────────────────────── */
  .btn{
    display:inline-block; padding:13px 22px; border-radius:2px; text-decoration:none;
    font-size:.94rem; font-weight:500; border:1px solid var(--accent);
    background:var(--accent); color:var(--accent-ink);
  }
  .btn:hover{filter:brightness(1.08)}
  .btn.ghost{background:transparent; color:var(--ink); border-color:var(--rule)}
  .btn.ghost:hover{border-color:var(--accent); color:var(--accent); filter:none}
  .btns{display:flex; gap:12px; flex-wrap:wrap}
  a:focus-visible,button:focus-visible{outline:2px solid var(--accent); outline-offset:3px}


  /* ── the Snapshot request form (2 Nov 2026) ──────────────────────────
     A plain POST form: no JavaScript, so the privacy page's promise holds.
     .oyp-hp is the honeypot — off-screen rather than display:none, because
     some bots skip anything that is display:none, and aria-hidden plus
     tabindex="-1" on the input keeps it away from people using a screen
     reader or the keyboard. */
  .oypform{display:flex; flex-direction:column; gap:18px; max-width:30rem}
  .formrow{display:flex; flex-direction:column; gap:6px}
  .formrow label{font-size:.9rem; color:var(--ink-2)}
  .formrow input, .formrow select{
    font:inherit; font-size:1rem; color:var(--ink); background:var(--surface);
    border:1px solid var(--rule); border-radius:2px; padding:11px 12px;
  }
  .formrow input:focus-visible, .formrow select:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
  /* THE MARKETING CONSENT ROW (8 November 2026). A checkbox inside its label,
     so the words are part of the hit area and a tired thumb cannot miss.
     .formrow stacks its children in a column and gives every input the full
     text-field treatment -- 11px of padding and a border -- which on a
     checkbox produces a large grey square. Both are undone here rather than
     by giving the row a different class, so it still inherits the spacing. */
  .formrow.formcheck label{
    display:flex; gap:10px; align-items:flex-start;
    line-height:1.5; cursor:pointer; color:var(--ink-2);
  }
  .formrow.formcheck input{
    flex:none; width:auto; padding:0; margin:.2em 0 0; accent-color:var(--accent);
  }
  .oypform .btn{align-self:flex-start; cursor:pointer; font-family:inherit}
  .formfine{font-size:.86rem; color:var(--ink-3); max-width:var(--measure)}
  .formerr{font-size:.95rem; color:var(--ink); border-left:2px solid var(--accent); padding-left:14px}
  .formnote{
    border:1px solid var(--rule); background:var(--surface); padding:26px 24px;
    max-width:var(--measure); display:flex; flex-direction:column; gap:10px;
  }
  .formnote p{color:var(--ink-2)}
  .oyp-hp{position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden}

  /* ── Snapshot form sections (8 Nov 2026, theme 0.15.0) ───────────────
     Three fieldsets: the business, where the books stand, you. The "Other"
     box under Kind of business is hidden by :has() until Other is chosen;
     a browser without :has() simply shows it, which does no harm. */
  .formset{border:0; margin:0; padding:0; min-width:0; display:flex; flex-direction:column; gap:16px}
  .formset input, .formset select{min-width:0; max-width:100%}
  .formset legend{font-family:"Source Serif 4",Georgia,"Times New Roman",serif; font-weight:600; font-size:1.15rem; color:var(--ink); padding:0; margin-bottom:4px}
  .formhint{font-size:.9rem; color:var(--ink-3); max-width:var(--measure); margin:0}
  .oypform:has(#oyp_type) .oyp-other{display:none}
  .oypform:has(#oyp_type option[value="other"]:checked) .oyp-other{display:flex}

  /* ── sections ───────────────────────────── */
  section{border-top:1px solid var(--rule-soft)}
  /* The content column's width now comes from .wrap itself, which the whole
     page shares. Only the vertical rhythm is set here. */
  section > .wp-block-group.wrap.wide{max-width:1040px}
  section > .wrap{padding-block:clamp(42px,6vw,78px)}
  #hero{border-top:none}
  #hero > .wrap{padding-block:clamp(48px,7vw,86px)}
  .stack{display:flex; flex-direction:column; gap:22px}
  .stack-sm{display:flex; flex-direction:column; gap:12px}
  .head{display:flex; flex-direction:column; gap:10px; margin-bottom:34px; max-width:var(--measure)}

  /* ── findings: ruled ledger rows ────────── */
  .findings{display:flex; flex-direction:column; max-width:var(--measure); border-top:1px solid var(--rule)}
  .finding{
    display:grid; grid-template-columns:76px 1fr; gap:0 24px;
    padding-block:26px; border-bottom:1px solid var(--rule-soft); align-items:start;
  }
  .ref{
    font-family:"IBM Plex Mono",monospace; font-size:.72rem; letter-spacing:.1em;
    color:var(--ink-3); padding-top:.55rem; border-right:1px solid var(--rule-soft);
    align-self:stretch;
  }
  .figure{
    font-family:"Source Serif 4",Georgia,serif; font-variant-numeric:tabular-nums;
    font-size:clamp(1.6rem,4.4vw,2.35rem); color:var(--accent); line-height:1.1;
    letter-spacing:-.02em;
  }
  .finding p{color:var(--ink-2); font-size:.97rem; max-width:var(--measure)}
  .finding .what{color:var(--ink); font-weight:500}

  /* ── rules grid ─────────────────────────── */
  .rules{display:grid; grid-template-columns:repeat(auto-fit,minmax(248px,1fr)); gap:1px; background:var(--rule-soft); border:1px solid var(--rule-soft)}
  .rule-cell{background:var(--surface); padding:26px 22px; display:flex; flex-direction:column; gap:9px}
  .rule-cell p{font-size:.93rem; color:var(--ink-2)}

  /* ── table ──────────────────────────────── */
  .tablewrap{overflow-x:auto; border:1px solid var(--rule)}
  table{border-collapse:collapse; width:100%; min-width:540px; background:var(--surface); font-size:.93rem}
  th,td{text-align:left; padding:13px 16px; border-bottom:1px solid var(--rule-soft); vertical-align:top}
  thead th{
    font-family:"IBM Plex Mono",monospace; font-size:.7rem; letter-spacing:.11em;
    text-transform:uppercase; color:var(--ink-3); font-weight:500; background:var(--surface-2);
  }
  tbody tr:nth-child(even) td{background:var(--surface-2)}
  tbody tr:last-child td{border-bottom:none}
  td.ours{color:var(--ink); font-weight:500}
  td.theirs{color:var(--ink-3)}

  /* ── pricing ────────────────────────────── */
  .tiers{display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:16px}
  .tier{background:var(--surface); border:1px solid var(--rule); padding:24px 20px; display:flex; flex-direction:column; gap:12px}
  .tier.lead{border-color:var(--accent); border-width:1px 1px 3px 1px}
  .price{font-family:"Source Serif 4",Georgia,serif; font-variant-numeric:tabular-nums; font-size:1.85rem; letter-spacing:-.02em}
  .price small{font-size:.78rem; color:var(--ink-3); letter-spacing:0}
  .tier ul{margin:0; padding-left:1.05em; color:var(--ink-2); font-size:.9rem; display:flex; flex-direction:column; gap:6px}
  .tier ul li::marker{color:var(--ink-3)}

  /* ── quiet blocks ───────────────────────── */
  .note{background:var(--surface); border:1px solid var(--rule); border-top:3px solid var(--accent); padding:22px 24px; display:flex; flex-direction:column; gap:10px}
  .note p{font-size:.95rem; color:var(--ink-2)}
  .kill,.path{font-family:"IBM Plex Mono",monospace; font-size:.85rem; color:var(--ink); background:var(--surface-2); padding:2px 7px; border:1px solid var(--rule-soft); display:inline-block}
  .steps{display:grid; grid-template-columns:repeat(auto-fit,minmax(272px,1fr)); gap:1px; background:var(--rule-soft); border:1px solid var(--rule-soft); list-style:none; margin:0; padding:0}
  .steps li{background:var(--surface); padding:26px 22px; display:flex; flex-direction:column; gap:9px}
  .steps .n{font-family:"Source Serif 4",Georgia,serif; font-size:1.7rem; font-weight:700; color:var(--accent); line-height:1}
  .steps p{font-size:.93rem; color:var(--ink-2)}
  /* Four steps tile 2x2, not 3+1. The 4 November copy revision cut this list from six
     items to four, and from about 860px up the grid has room for three 272px tracks and
     orphans the fourth -- two empty cells showing the rule colour through the 1px gap.
     860px is where a 1040px wrap first fits three tracks. Below it auto-fit already gives
     two columns, then one. A six-item list is not matched and still tiles 3x2. */
  @media (min-width:860px){
    .steps:has(>li:nth-child(4):last-child){grid-template-columns:repeat(2,1fr)}
  }
  .glance{border:1px solid var(--rule); background:var(--surface)}
  .glance-head{display:flex; flex-wrap:wrap; gap:12px; justify-content:space-between; align-items:baseline; padding:16px 22px; border-bottom:1px solid var(--rule-soft)}
  .glance-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:1px; background:var(--rule-soft)}
  .glance-cell{background:var(--surface); padding:22px}
  .glance-cell .big{font-family:"Source Serif 4",Georgia,serif; font-size:1.8rem; font-weight:700; margin-top:8px}
  .glance-cell p:last-child{font-size:.88rem; color:var(--ink-2); margin-top:5px}
  .disclosures{display:grid; grid-template-columns:repeat(auto-fit,minmax(256px,1fr)); gap:24px 28px}
  /* Four cards went three-then-one in the 1040px band. Two by two, which is
     what four .steps already do, so the two grids now behave the same way. */
  .disclosures.four,
  .disclosures:has(>div:nth-child(4):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}
  @media (max-width:560px){
    .disclosures.four,
    .disclosures:has(>div:nth-child(4):last-child){grid-template-columns:1fr}
    /* THE LOCKUP SHRINKS ON A PHONE, and the tagline follows it down on its
       own because both are driven by --wordmark. Re-measured on 7 November 2026
       with the header down to three items. At 375px the shrink is now worth
       9px of masthead (129px to 120px) and no longer decides how many rows the
       nav takes. AT 320px IT IS LOAD-BEARING: the full-size lockup is 315.5px
       against 280px of row, so the tagline wraps and the masthead goes to
       197px; shrunk, the lockup is 273.5px and the masthead is 120px. That is
       77px, and it is why this rule stays. Figures recorded here before the tap
       targets and the own-row nav (173px / 128px / 327px) predate both and
       were wrong. */
    :root{--wordmark:1.6rem}
    /* THE ROW STAYS TIGHTENED, for a different reason than it was tightened.
       12px and .8rem were set to fit five labels beside a full-size wordmark.
       Three labels no longer need it at 375px — but at 320px, the narrowest
       phone still worth building for, they need 274.3px of 280px at the
       desktop 18px / .86rem and only 245.6px tightened. 5.7px of slack is not
       slack; 34.4px is. So it stays. */
    .topbar nav{gap:12px; font-size:.8rem}
    /* ALL THREE SHOW ON A PHONE (7 November 2026). "The system" was hidden
       here from the moment the nav grew to five, because at 375px the fifth
       item pushed the row over and cost 55px of masthead. With About and For
       accountants in the footer the three that remain need 245.6px of the
       335px available, so the rule has nothing left to do and the anchor is
       back. The masthead is 120px either way. */

    /* THE MASTHEAD STOPS STICKING ON A PHONE (Peter, 7 November 2026). At
       375 x 812 it is 128px — 15.7% of the screen — and sticky, it took that
       much off every screen of reading rather than only the first. Static, the
       height is paid once at the top of the page and the menu is one scroll
       away. Unsticking it is also what pays for the tap targets below: the
       height they need no longer costs anything on every screen. */
    .topbar{position:static}

    /* TAP TARGETS. The links were 21px tall against the 44px guidance — four
       small marks in a row near the top of the screen, where thumbs are least
       accurate. 11.5px above and below makes each one 44px, and the masthead's
       own padding comes down so the row costs less than the 23px it adds.
       WIDTH IS NOT FIXED AND CANNOT BE HERE: "About" is 36px wide, and the
       inline padding that would widen it pushes the four links onto a second
       row at 375px. Height is the dimension that matters for a horizontal row.
       The 12px gap is left as dead space on purpose — a near miss should land
       on nothing rather than on the wrong page. */
    .topbar nav a{display:inline-block; padding-block:11.5px}
    .topbar .wrap{padding-block:11px}
    .topbar nav{margin-block:-11.5px}
  }
  .disclosures h3{margin-bottom:7px}
  .disclosures p{font-size:.93rem; color:var(--ink-2)}

  .cta{background:var(--surface); border:1px solid var(--rule); padding:clamp(28px,4.5vw,44px); display:flex; flex-direction:column; gap:18px}

  footer{border-top:1px solid var(--rule); background:var(--surface)}
  footer .wrap{padding-block:34px; display:flex; flex-direction:column; gap:14px}
  footer p, footer a{font-size:.84rem; color:var(--ink-3)}
  .ph{border-bottom:1px dotted var(--ink-3)}

  @media (max-width:520px){
    .finding{grid-template-columns:1fr; gap:14px}
    .ref{border-right:none; border-bottom:1px solid var(--rule-soft); padding-top:0; padding-bottom:8px}
  }

  /* ── added 2026-11-01 for the Snapshot, Security and For-accountants pages ── */

  /* a page title block that is not a hero */
  .pagehead{border-top:none}
  .pagehead > .wrap{padding-block:clamp(38px,5.5vw,64px)}
  .crumb{font-family:"IBM Plex Mono",ui-monospace,monospace; font-size:.72rem; letter-spacing:.1em; text-transform:uppercase}
  .crumb a{color:var(--ink-3); text-decoration:none}
  .crumb a:hover{color:var(--accent)}

  /* question-and-answer blocks: the Security page */
  .qa{display:flex; flex-direction:column; border-top:1px solid var(--rule)}
  .qa > div{padding-block:28px; border-bottom:1px solid var(--rule-soft); display:flex; flex-direction:column; gap:10px}
  .qa h3{font-size:1.14rem; max-width:var(--measure)}
  .qa p{font-size:.96rem; color:var(--ink-2); max-width:var(--measure)}
  .qa p strong{color:var(--ink); font-weight:500}

  /* a numbered sequence read top to bottom, unlike the .steps grid */
  .seq{list-style:none; margin:0; padding:0; max-width:var(--measure); display:flex; flex-direction:column; border-top:1px solid var(--rule)}
  .seq > li{
    display:grid; grid-template-columns:52px 1fr; gap:0 22px; padding-block:24px;
    border-bottom:1px solid var(--rule-soft); align-items:start;
  }
  .seq .n{
    font-family:"Source Serif 4",Georgia,serif; font-size:1.45rem; font-weight:700; color:var(--accent);
    line-height:1.1; border-right:1px solid var(--rule-soft); align-self:stretch; padding-top:1px;
  }
  .seq .body{display:flex; flex-direction:column; gap:8px}
  .seq p{font-size:.95rem; color:var(--ink-2); max-width:var(--measure)}

  /* a letter, set wider than .prose but still a single measure */
  .letter{max-width:var(--measure); display:flex; flex-direction:column; gap:20px}
  .letter p{font-size:1.01rem; color:var(--ink-2)}
  .letter p strong{color:var(--ink); font-weight:500}
  .letter h3{margin-top:14px}
  .sig{font-family:"Source Serif 4",Georgia,serif; font-size:1.05rem; color:var(--ink)}

  /* plain ruled list, used for "what you get" and "what we never do" */
  .plain{list-style:none; margin:0; padding:0; max-width:var(--measure); display:flex; flex-direction:column; border-top:1px solid var(--rule-soft)}
  .plain li{padding-block:15px; border-bottom:1px solid var(--rule-soft); font-size:.96rem; color:var(--ink-2); display:flex; gap:14px}
  .plain li .mark{color:var(--accent); flex:none; font-family:"IBM Plex Mono",monospace; font-size:.9rem; padding-top:.1rem}
  .plain li .mark.no{color:var(--ink-3)}
  .plain li strong{color:var(--ink); font-weight:500}

  /* a two-up block where one side is the rule and the other the consequence */
  .pair{display:grid; grid-template-columns:repeat(auto-fit,minmax(282px,1fr)); gap:1px; background:var(--rule-soft); border:1px solid var(--rule-soft)}
  .pair > div{background:var(--surface); padding:24px 22px; display:flex; flex-direction:column; gap:9px}
  .pair p{font-size:.93rem; color:var(--ink-2)}

  .tag{
    font-family:"IBM Plex Mono",monospace; font-size:.68rem; letter-spacing:.1em; text-transform:uppercase;
    color:var(--accent); background:var(--accent-soft); border:1px solid var(--accent-soft);
    padding:3px 8px; display:inline-block; align-self:flex-start;
  }
  .tag.off{color:var(--ink-3); background:var(--surface-2); border-color:var(--rule-soft)}

  @media (max-width:520px){
    .seq > li{grid-template-columns:1fr; gap:10px}
    .seq .n{border-right:none; border-bottom:1px solid var(--rule-soft); padding-bottom:6px}
  }

  /* the brand is now a link on every page */
  a.brand{color:var(--ink); text-decoration:none; display:inline-block}
  a.brand:hover span{text-decoration:underline}

  /* ── added 2026-11-01 for the privacy policy and terms of use ── */
  .legal{max-width:var(--measure); display:flex; flex-direction:column; gap:30px}
  .legal > section{border-top:none}
  .legal h3{font-size:1.1rem; margin-bottom:9px}
  .legal h4{
    font-family:Inter,system-ui,sans-serif; font-size:.95rem; font-weight:600; color:var(--ink);
    margin:18px 0 6px;
  }
  .legal p{font-size:.96rem; color:var(--ink-2); margin-bottom:10px}
  .legal p:last-child{margin-bottom:0}
  .legal p strong{color:var(--ink); font-weight:500}
  .legal ul{margin:4px 0 10px; padding-left:1.1em; color:var(--ink-2); font-size:.96rem; display:flex; flex-direction:column; gap:7px}
  .legal ul li::marker{color:var(--ink-3)}
  .legal .num{
    font-family:"IBM Plex Mono",ui-monospace,monospace; font-size:.75rem; letter-spacing:.1em;
    color:var(--accent); display:block; margin-bottom:6px;
  }
  .summary{background:var(--surface-2); border:1px solid var(--rule); padding:26px 24px; display:flex; flex-direction:column; gap:12px; max-width:var(--measure)}
  .summary h2{font-size:1.3rem}
  .summary p{font-size:.97rem; color:var(--ink-2)}
  .summary p strong{color:var(--ink); font-weight:500}
  .toc{display:flex; flex-wrap:wrap; gap:8px 20px; font-size:.9rem; max-width:var(--measure)}
  .toc a{color:var(--ink-2); text-decoration:none; border-bottom:1px solid var(--rule-soft)}
  .toc a:hover{color:var(--accent); border-color:var(--accent)}
  .stamp{font-family:"IBM Plex Mono",ui-monospace,monospace; font-size:.76rem; color:var(--ink-3); letter-spacing:.04em}

  /* ── the mark, chosen 1 Nov 2026: option 3, "the plate is the O" ──────────
     The ring replaces the O of "Off", so the visible text is "ff Your Plate"
     and every .brand link carries aria-label="Off Your Plate Books".

     Geometry is measured from this site's own font file, not estimated:
     website/fonts/source-serif-4-latin.woff2, instantiated at the weight and
     optical size .brand actually uses (wght 600, opsz 18.88). Per em:
       f top .74295 · O advance .71200 · O ink width .62901
       O top .68065 · O bottom -.01659 · cap height .67000
     The plate is sized off the f, not the cap height (Peter, 2 Nov 2026). The
     ascender is the taller reference, and at 110% of it the plate breaks the
     ascender line above and the baseline below by .037em at each end:
       diameter .74295 x 1.10 = .817em · overhang (.817 - .74295) / 2 = .037em
     That overhang is what stops the shape reading as a letter and starts it
     reading as an object. It is 23% taller than the .665em it replaced.
     The plate is drawn in perspective, as it is seen from the table rather than
     from above, which is what makes it unmistakably a plate — and what stops it
     being a letter O. The visible text is still "ff Your Plate", so the
     aria-label is doing more work than it was.
     The note that stood here recorded O advance 64.57 and cap height 65.72 per
     100px. Neither matches the font file. The file wins; the discrepancy is
     logged in 20-BRANDING-AND-DEMAND.md Part 2c.
     RIM AND WELL, from 7 November 2026. One ellipse until then, on the
     reasoning that the well's stroke would not clear a pixel at header size.
     That was arrived at by carrying print's 1.74 stroke across unchanged. It
     is the wrong sum: the rim is already heavier at this size than in print
     (5.12 against 4.17), so the well scales with it at the approved 6 : 2.5
     ratio — 5.12 x 2.5/6 = 2.13, which is 1.16px at header size and holds.
     Peter looked at the header beside the email signature and the specimen
     letterhead, which both carry the well, and said the O was not the current
     mark. It is now the same mark everywhere it is drawn above 32px.
     THE SQUARE, and the one place this mark is NOT used. It is 1.4045 : 1, and
     a favicon, an app icon and a social avatar are all square. The letterboxed
     ellipse was built and looked at on 2 Nov; at 16px it was 10.2px tall and
     too slight for a tab. So:
       square slots  — favicon, app icon, social avatar — take a CIRCLE
       everything else — header, lockup, print device — takes the ELLIPSE
     website/favicon.svg and brand/plate-device-small.svg hold that circle. The
     identity carries two plates; that is the accepted cost of the aspect, not
     an inconsistency to tidy away.
     If the webfont is ever swapped, re-measure: Georgia Bold puts its f top at
     .75635 against Source Serif's .74295, and every number here moves. */
  .oyp-o{
    /* OPTION 4, the three-quarter plate (Peter, 2 Nov 2026, after seeing both
       built and rendered). Same height rule as option 1 — 110% of the f — but
       the ellipse is 1.4045 : 1, so it is 1.1475em wide against the O's .712em
       advance: .435em wider, about 8px at header size. */
    width:1.1475em; height:.817em;
    margin-right:.01em;               /* the ellipse already overhangs the O's advance by .435em */
    transform:translateY(.037em);     /* half the overhang, so it hangs as far below as above */
    vertical-align:baseline;
    color:var(--accent);
    flex:none;
  }
  /* Rim stroke held at .094em, the weight the ring carried at .665em, so the
     plate changed shape without getting heavier: 5.12 of 44.5 at .817em is
     .0940em. rx 28.69 ry 19.69 plus half the stroke fills the 62.5 x 44.5
     viewBox edge to edge.
     THE WELL is pulled in to .63 of the rim's radii, not print's .6723. At the
     heavier small-size stroke the two rings would otherwise close up where the
     ellipse is shallowest: .63 reproduces print's own face gap at the top and
     bottom (3.67 units against 3.65) at the cost of a slightly smaller well,
     and the gap is what disappears first. Rendered at 1x and 4x before it was
     kept. Print: brand/plate-device.svg, unchanged. */
  .oyp-o ellipse{fill:none; stroke:currentColor}
  .oyp-o .rim{stroke-width:5.12}
  .oyp-o .well{stroke-width:2.13}

  /* THE BOOKS DEVICE, added 3 November 2026 when the name became Off Your Plate
     Books. It sits AFTER the wordmark; the plate keeps the O of "Off", so the
     lockup now carries two devices and one accent colour across both.
     20-BRANDING-AND-DEMAND.md Part 2d.

     Same box as the plate — 62.5 x 44.5 — and the same .817em height, so the two
     devices are the same size in the line and the slot rules did not change.
     This is the SMALL cut: three books, a shelf, and no title band, because the
     band's 1.74 units compute to under a pixel at header size. The print cut,
     brand/books-device.svg, carries the band.

     translateY(.047em) lands the shelf's own centre line on the baseline, so the
     books stand on the type's baseline rather than on the bottom of their box.
     Unlike the plate it does NOT overhang: the plate overhangs because it is
     standing in for a letter, and this one is not. */
  .oyp-books{
    width:1.1475em; height:.817em;
    margin-left:.16em;
    transform:translateY(.047em);
    vertical-align:baseline;
    color:var(--accent);
    flex:none;
  }
  .oyp-books path{fill:none; stroke:currentColor; stroke-width:5.12}
  .brand span{color:inherit}        /* the accent now lives in the ring, not in the word "Plate" */

  /* the about page's photograph-and-letter split */
  @media (max-width:720px){
    .aboutgrid{grid-template-columns:1fr !important}
  }

  /* the diagnostic landing page's form, built but not wired */
  .form{display:grid; grid-template-columns:repeat(auto-fit,minmax(248px,1fr)); gap:18px}
  .field{display:flex; flex-direction:column; gap:7px}
  .field label{font-size:.85rem; font-weight:500; color:var(--ink)}
  .field input, .field select{
    font-family:inherit; font-size:.95rem; padding:11px 13px; color:var(--ink);
    background:var(--surface); border:1px solid var(--rule); border-radius:2px;
  }
  .field input:disabled, .field select:disabled{background:var(--surface-2); color:var(--ink-3)}
  .field.wide{grid-column:1/-1}

  /* ── the draft bar. DELETE this rule and the .draftbar markup to go live ── */
  .draftbar{background:#8C2E2E; color:#FFF6F4}
  .draftbar .wrap{padding-block:9px; display:flex; flex-wrap:wrap; gap:4px 14px; align-items:baseline}
  .draftbar p{font-size:.82rem; line-height:1.45}
  .draftbar strong{font-weight:600}


  /* ═══════════════════════════════════════════════════════════════════════
     WORDPRESS COMPATIBILITY
     Everything below exists only because WordPress wraps content in its own
     elements. It changes no design decision above.
     ═══════════════════════════════════════════════════════════════════════ */

  /* 1 · The header menu. wp_nav_menu outputs a <ul>, so the flex row moves to it. */
  .topbar nav ul{display:flex; flex-wrap:wrap; gap:18px; margin:0; padding:0; list-style:none}
  .topbar nav li{margin:0}
  .topbar nav .current-menu-item > a,
  .topbar nav .current_page_item > a{color:var(--accent)}
  footer nav ul{display:flex; flex-wrap:wrap; gap:6px 14px; margin:0; padding:0; list-style:none}
  footer nav li::after{content:"·"; color:var(--rule); margin-left:14px}
  footer nav li:last-child::after{content:""}

  /* 2 · The admin bar is 32px tall and would sit over the sticky header. */
  body.admin-bar .topbar{top:32px}
  @media screen and (max-width:782px){
    body.admin-bar .topbar{top:46px}
  }

  /* 3 · A core/group carrying one of our layout classes must keep OUR display.
     WordPress's own layout rules are more specific than a bare class, so each
     grid is restated at group specificity. */
  .wp-block-group.disclosures, .wp-block-group.glance-grid,
  .wp-block-group.rules, .wp-block-group.tiers,
  .wp-block-group.steps, .wp-block-group.pair,
  .wp-block-group.form{display:grid}
  .wp-block-group.disclosures{grid-template-columns:repeat(auto-fit,minmax(256px,1fr)); gap:24px}
  .wp-block-group.tiers{grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:16px}
  .wp-block-group.pair{grid-template-columns:repeat(auto-fit,minmax(282px,1fr)); gap:1px}
  .wp-block-group.findings, .wp-block-group.qa,
  .wp-block-group.stack, .wp-block-group.stack-sm,
  .wp-block-group.letter, .wp-block-group.legal,
  .wp-block-group.cta, .wp-block-group.note{display:flex; flex-direction:column}
  .wp-block-group.stack{gap:22px}
  .wp-block-group.stack-sm{gap:12px}
  .wp-block-group.cta{gap:18px}
  .wp-block-group.note{gap:10px}
  .wp-block-group.letter{gap:20px}
  .wp-block-group.legal{gap:30px}
  .wp-block-group.btns{display:flex; flex-direction:row; flex-wrap:wrap; gap:12px}

  /* 4 · Core paragraphs and headings inside our components keep our margins. */
  .wrap .wp-block-paragraph{margin:0}
  .legal .wp-block-paragraph{margin-bottom:10px}
  .legal .wp-block-paragraph:last-child{margin-bottom:0}

  /* 5 · Core blocks we do use. */
  .wp-block-table{margin:0}
  .wp-block-table table{border-collapse:collapse; width:100%; min-width:540px; background:var(--surface); font-size:.93rem}
  .wp-block-table th, .wp-block-table td{text-align:left; padding:13px 16px; border-bottom:1px solid var(--rule-soft); vertical-align:top}
  .wp-block-table thead th{
    font-family:"IBM Plex Mono",monospace; font-size:.7rem; letter-spacing:.11em;
    text-transform:uppercase; color:var(--ink-3); font-weight:500; background:var(--surface-2);
  }
  .wp-block-table tbody tr:nth-child(even) td{background:var(--surface-2)}
  .wp-block-image img{max-width:100%; height:auto; display:block}
  .wp-block-buttons{display:flex; flex-wrap:wrap; gap:12px}
  .wp-block-button__link{
    display:inline-block; padding:13px 22px; border-radius:2px; text-decoration:none;
    font-size:.94rem; font-weight:500; border:1px solid var(--accent);
    background:var(--accent); color:var(--accent-ink);
  }

  /* 6 · Accessibility helper WordPress expects a theme to provide. */
  .screen-reader-text{
    border:0; clip-path:inset(50%); height:1px; margin:-1px; overflow:hidden;
    padding:0; position:absolute; width:1px; word-wrap:normal !important;
  }
  .skip-link{
    position:absolute; left:-9999px; top:0; z-index:100; background:var(--ink);
    color:var(--paper); padding:12px 18px; text-decoration:none;
  }
  .skip-link:focus{left:0}

  /* 7 · The editor canvas, so what is typed looks like what ships. */
  .editor-styles-wrapper{background:var(--paper); color:var(--ink)}

  /* 8 · Classes the block patterns rely on, restated at group specificity. */
  .wp-block-group.head{display:flex; flex-direction:column; gap:10px; margin-bottom:34px; max-width:var(--measure)}
  .wp-block-group.wrap{max-width:calc(var(--measure) + 40px); margin-inline:auto; padding-inline:20px}
  .wp-block-group.wrap.wide{max-width:1040px}
  .wp-block-group.prose, .wp-block-paragraph.prose{max-width:var(--measure)}

  /* The hero section: a class, not an id, so the editor cannot lose it.
     Matches what #hero did on the static build. */
  section.hero-section{border-top:none}
  section.hero-section > .wrap{padding-block:clamp(48px,7vw,86px)}

  /* The hero is set larger than the rest of the page (Peter, 2 Nov 2026). At
     the page's own sizes the opening used about 770px of the 1000px band and
     left a dead column down the right of it. The measure is still 64ch — the
     type grew, not the line length — so the band fills without the lines
     getting long to read. */
  #hero .eyebrow, .hero-section .eyebrow{font-size:.95rem; letter-spacing:.12em}
  #hero h1, .hero-section h1{font-size:clamp(2.1rem,5.9vw,4.05rem)}
  #hero .lede, .hero-section .lede{font-size:clamp(1.08rem,2.4vw,1.45rem)}
