/* ═══════════════════════════════════════════════════════════════════════════
   Organically Affiliated — the PUBLIC SITE's layer.

   ── WHAT THIS IS AND WHY IT IS SEPARATE FROM oa.css ────────────────────────
   oa.css is the whole product's design system: the dashboards, the account
   pages and the admin screens all take their colours, fields and buttons from
   it. This file sits on top of it and is loaded ONLY by the pages a brand,
   a partner or a stranger sees before they have an account: the homepage, the
   join page, sign in, sign up, the password pages, the two Shopify return
   pages, the 404, and the three legal pages.

   ── IT DOES NOT TOUCH THE DASHBOARDS. THAT IS THE POINT. ───────────────────
   Every value it changes is redefined inside `.oa-site`, which is a class on
   the <body> of a public page and on nothing else. A dashboard never carries
   it, so a dashboard resolves --bg, --line and the rest to oa.css's own
   values exactly as it did before this file existed. The alternative was to
   edit the tokens in oa.css directly, which would have warmed every dashboard
   surface as a side effect of redesigning a marketing page.

   ── WHY THE SURFACE TOKENS MOVE AT ALL ─────────────────────────────────────
   The public palette is warmer than the product's: paper rather than off
   white, stone rather than grey, a warm hairline rather than a cool one. Two
   text greys had to move with them. --faint is the one that matters: at
   oa.css's #6F756A it is 4.7:1 on the old near white background and only
   3.94:1 on the stone band, which is under AA for body text. Inside .oa-site
   it is #63695E, which is 4.70:1 on stone and 5.42:1 on paper, so it is safe
   as text on every surface this file introduces. Checked, not eyeballed.

   ── THE SERIF IS SELF HOSTED ───────────────────────────────────────────────
   Fraunces, variable, latin and latin-ext, served from /font. Self hosted
   rather than linked from Google because a linked font is two cross origin
   round trips in a chain: fetch the stylesheet, parse it, then fetch the font.
   From here it is one same origin file that a <link rel="preload"> in the page
   head can start during HTML parse.

   It carries a real optical size axis, so a 64px headline is set from the
   display cut and a 20px subhead from the text cut, automatically, with no
   rule written anywhere. That is most of why it was chosen.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── THE SERIF ────────────────────────────────────────────────────────────
   Two faces, split on unicode-range exactly as Google splits them, so a page
   of ordinary English never downloads the accented glyphs. The join page is
   the one that needs latin-ext: it prints a brand's own name at 44px, and a
   brand called Café Provisions must not render three letters in the serif and
   one in the sans. */
@font-face{
  font-family:'Fraunces';
  font-style:normal;
  font-weight:400 700;
  font-display:swap;
  src:url('/font/fraunces-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:'Fraunces';
  font-style:normal;
  font-weight:400 700;
  font-display:swap;
  src:url('/font/fraunces-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-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

/* ═══ TOKENS, SCOPED ═══════════════════════════════════════════════════════ */
.oa-site{
  /* Surfaces. Paper is the page; white is what lifts off it; stone is the
     alternating band and the field a screenshot is mounted on. */
  --bg:#FBFAF6;
  --card:#FFFFFF;
  --warm:#EDEAE0;
  --sunk:#F2F0E8;

  /* Lines, warmed to match. --line-strong is left at oa.css's value because
     it is an input border and was audited at 3.28:1 for that job. */
  --line:#DAD5C7;
  --card-edge:#BCB6A4;

  /* Text. --ink, --body and --mute clear AA comfortably on every surface
     above and are unchanged. --faint is darkened; see the header. */
  --faint:#63695E;

  /* Brand. The green does not move. Its tinted pair is warmed so it sits on
     paper rather than on the old near white. */
  --accent-soft:#E7EDE1;
  --accent-line:#CBD6C3;

  /* The one dark surface on the site. A deep forest rather than a near black,
     because a black slab in the middle of a warm page reads as a different
     website. */
  --deep:#2A3524;
  --on-deep:#FFFFFF;
  --on-deep-soft:#CFD5C8;   /* 8.58:1 on --deep */
  --on-deep-faint:#AAB2A3;  /* 5.88:1 on --deep */

  /* Type. The sans is oa.css's Inter, untouched, so nothing in the product
     shifts. The serif is for headings, the price, the wordmark and the brand
     name on a join page, and for nothing else. */
  --font-display:'Fraunces','Iowan Old Style','Palatino Linotype',Palatino,Georgia,'Times New Roman',serif;

  /* Display scale. Three sizes, fluid, each with its own leading and tracking
     because a 64px serif and a 24px serif do not want the same of either.

     ── THE CEILINGS MOVED UP on 2026-09-25 ────────────────────────────────
     All three stopped growing at about 1300px, so a 1920 window and a 1300
     one had identical headings over a container that is now much wider.
     Each one is set to land on its old value at 1280 and to keep going to
     1920: d1 68 to 88, d2 44 to 56, d3 24 to 30. The minimums are untouched,
     so the phone is the page it was. */
  --d1:clamp(2.5rem,1.75rem + 3.125vw,5.5rem);
  --d2:clamp(1.9rem,1.25rem + 1.875vw,3.5rem);
  --d3:clamp(1.3rem,.75rem + .9375vw,1.875rem);

  /* Section rhythm. One value, so no section can drift. The ceiling was 128,
     which it reached at 1524; a taller page wants taller bands. */
  --sec-y:clamp(72px,8.4vw,160px);

  /* ═══ THE CONTAINER ══════════════════════════════════════════════════
     Was a flat 1120px with 32px gutters, so at 1920 the page was a 1056px
     column of content with 400px of empty paper on each side, and it read
     as a phone layout somebody had opened on a desktop.

     A ceiling AND a fluid gutter, rather than a percentage width. A
     percentage alone gives 1728px of text at 1920, which is past any
     measure; a fixed gutter alone leaves the same dead margin it always
     did. This grows the content to about 1360px at 1920 and keeps a gutter
     proportional to the window. At 476px and below the gutter is 20px,
     which is what the old phone rule set, so nothing under that moves. */
  --wrap-max:1520px;
  --wrap-pad:clamp(20px,4.2vw,88px);

  /* ═══ THE SCALE, FLUID FROM 1024 UP ══════════════════════════════════
     oa.css fixes every gap and every type size in pixels, which is right for
     a dashboard: a dense screen of rows and fields should not swell with the
     window. A marketing page is the opposite, and widening the container
     alone would only have spread the same small type over more paper.

     So the WHOLE SCALE moves rather than a handful of headings. Redefining
     oa.css's own tokens inside .oa-site means the spacing, the type, the
     buttons, the cards, the screenshot mounts and the form fields all grow
     together and no layout changes: one set of numbers instead of fifty
     rules, and the dashboards, which never carry this class, resolve every
     one of these to oa.css's own value exactly as before.

     EVERY ONE IS clamp(today, fluid, today x 1.22). Below 1024 nothing moves
     at all, so the phone and the small laptop are the page they were. From
     1024 to 1920 everything grows by the same 22% and then stops. For a
     token V the middle term is V*0.7486px + V*0.02455vw, which is exactly V
     at 1024 and 1.22V at 1920.

     --s1 and --s2 keep their 4px and 8px, and so do the radii. They are the
     hairline gaps inside a pill and beside an icon, and 4.9px is not a
     different number from 4px; making them fluid would add noise to the
     file and nothing to the page. */
  --s3:clamp(12px,calc(8.98px + .295vw),14.64px);
  --s4:clamp(16px,calc(11.98px + .393vw),19.52px);
  --s5:clamp(20px,calc(14.97px + .491vw),24.4px);
  --s6:clamp(24px,calc(17.97px + .589vw),29.28px);
  --s7:clamp(32px,calc(23.96px + .786vw),39.04px);
  --s8:clamp(40px,calc(29.94px + .982vw),48.8px);
  --s9:clamp(56px,calc(41.92px + 1.375vw),68.32px);
  --s10:clamp(80px,calc(59.89px + 1.964vw),97.6px);

  --t-micro:clamp(12px,calc(8.98px + .295vw),14.64px);
  --t-tiny:clamp(13px,calc(9.73px + .319vw),15.86px);
  --t-small:clamp(15px,calc(11.23px + .368vw),18.3px);
  --t-base:clamp(16px,calc(11.98px + .393vw),19.52px);
  --t-lead:clamp(17px,calc(12.73px + .417vw),20.74px);
  --t-h4:clamp(20px,calc(14.97px + .491vw),24.4px);
  --t-h3:clamp(24px,calc(17.97px + .589vw),29.28px);
  --t-h2:clamp(30px,calc(22.46px + .737vw),36.6px);

  background:var(--bg);
}

/* ── SMOOTH SCROLL FOR THE SECTION LINKS IN THE BAR ──────────────────────
   scroll-padding keeps a section's heading clear of the sticky bar when the
   browser lands on it, whether it got there from a click or from a reload
   with a #hash already in the address. oa.css's reduced motion block already
   forces scroll-behavior back to auto, so this is not repeated here. */
html:has(.oa-site){scroll-behavior:smooth;scroll-padding-top:96px;}

/* ═══ TYPE ═════════════════════════════════════════════════════════════════ */
.oa-site h1,.oa-site h2,.oa-site h3,
.oa-site .d1,.oa-site .d2,.oa-site .d3{
  font-family:var(--font-display);
  font-optical-sizing:auto;
  color:var(--ink);
  text-wrap:balance;
}
.oa-site .d1,.oa-site h1{font-size:var(--d1);font-weight:500;line-height:1.04;letter-spacing:-.025em;}
.oa-site .d2,.oa-site h2{font-size:var(--d2);font-weight:500;line-height:1.1;letter-spacing:-.02em;}
.oa-site .d3,.oa-site h3{font-size:var(--d3);font-weight:600;line-height:1.28;letter-spacing:-.012em;}

/* The measure. Body text never runs the full 1120px container. */
.oa-site .lede{
  font-size:clamp(1.0625rem,1rem + .35vw,1.25rem);
  line-height:1.6;color:var(--body);max-width:36em;
}
.oa-site .sub{
  font-size:var(--t-lead);line-height:1.7;color:var(--body);max-width:62ch;
}

/* ═══ LAYOUT ═══════════════════════════════════════════════════════════════ */
/* See --wrap-max and --wrap-pad. The bar and the footer take the same two, so
   the wordmark sits over the content rather than 200px inside it. */
.wrap{width:100%;max-width:var(--wrap-max);margin:0 auto;
  padding-left:var(--wrap-pad);padding-right:var(--wrap-pad);}
.sec{padding:var(--sec-y) 0;}
.sec-stone{background:var(--warm);}
.sec-deep{background:var(--deep);}
/* ── THE MEASURE ON A SECTION HEADING ─────────────────────────────────────
   In px, not in ch. `ch` resolves against the element's OWN font size, and
   this is a <div> at the body size, so 34ch was 34 characters of 17px text,
   about 290px, and every heading on the page broke into three cramped lines
   inside it. The heading is 44px. A width for a heading has to be stated at
   the heading's size or not in characters at all.

   It grows with the heading. At 620px flat, a d2 that now reaches 56px broke
   into three lines at 1920 inside a container half the screen wide. */
.sec-head{max-width:min(100%,clamp(620px,48vw,860px));}
.sec-head .sub{margin-top:var(--s4);}
.sec-head + *{margin-top:clamp(40px,4.4vw,64px);}

/* A hairline that belongs to the content rather than to a box. Used to open
   a column or separate two facts, never to draw a card. */
.rule{border:0;border-top:1px solid var(--line);margin:0;}

/* The phone gutter used to live here. --wrap-pad's own floor is 20px, which
   is what this rule set, so it is the same number said once. */

/* ═══ THE TOP BAR ══════════════════════════════════════════════════════════
   Sticky, translucent over the paper. It carries the three section links the
   page is long enough to need, plus the two things somebody arrives wanting:
   a way in and a way to start. */
.site-bar{
  position:sticky;top:0;z-index:60;
  background:rgba(251,250,246,.84);
  backdrop-filter:blur(14px) saturate(1.4);
  -webkit-backdrop-filter:blur(14px) saturate(1.4);
  border-bottom:1px solid var(--line);
}
.site-bar-in{
  max-width:var(--wrap-max);margin:0 auto;padding:var(--s3) var(--wrap-pad);
  display:flex;align-items:center;gap:var(--s6);
}
.site-logo{
  display:inline-flex;align-items:center;gap:10px;
  text-decoration:none;color:var(--ink);flex-shrink:0;
}
/* The monogram from the app icon, set in the same serif as the headings, so
   the mark and the page are visibly one thing. */
.site-monogram{
  width:clamp(32px,calc(23.96px + .786vw),39.04px);
  height:clamp(32px,calc(23.96px + .786vw),39.04px);
  border-radius:9px;flex-shrink:0;
  background:var(--accent);color:#fff;
  display:grid;place-items:center;
  font-family:var(--font-display);font-optical-sizing:auto;
  font-size:var(--t-small);font-weight:600;letter-spacing:.01em;line-height:1;
  padding-top:1px;
}
.site-wordmark{
  font-family:var(--font-display);font-optical-sizing:auto;
  font-size:clamp(19px,calc(14.22px + .467vw),23.18px);
  font-weight:600;letter-spacing:-.018em;white-space:nowrap;
}
.site-nav{display:flex;align-items:center;gap:var(--s6);margin-left:auto;}
.site-nav a{
  font-size:var(--t-small);font-weight:500;color:var(--mute);
  text-decoration:none;padding:6px 0;position:relative;
  transition:color var(--fast) var(--ease);
}
.site-nav a:hover{color:var(--ink);}
.site-act{display:flex;align-items:center;gap:var(--s5);flex-shrink:0;}
.site-act .site-login{
  font-size:var(--t-small);font-weight:600;color:var(--ink);text-decoration:none;
}
.site-act .site-login:hover{color:var(--accent);}

/* The phone bar. A real menu, not the desktop row squeezed. */
.site-burger{
  display:none;appearance:none;background:none;cursor:pointer;
  margin-left:auto;border:1px solid var(--line);border-radius:var(--r-sm);
  width:40px;height:40px;place-items:center;color:var(--ink);
  transition:border-color var(--fast) var(--ease),background var(--fast) var(--ease);
}
.site-burger:hover{border-color:var(--line-strong);background:var(--card);}
.site-burger .i-close{display:none;}
.site-burger[aria-expanded="true"] .i-open{display:none;}
.site-burger[aria-expanded="true"] .i-close{display:block;}
.site-menu{
  display:none;border-top:1px solid var(--line);background:var(--bg);
  padding:var(--s4) var(--s5) var(--s6);
}
.site-menu[data-open]{display:block;animation:oa-drop .18s var(--ease);}
.site-menu a:not(.oa-btn){
  display:block;padding:var(--s4) 0;border-bottom:1px solid var(--line);
  font-size:var(--t-lead);font-weight:500;color:var(--ink);text-decoration:none;
}
.site-menu .oa-btn{margin-top:var(--s5);}
@keyframes oa-drop{from{opacity:0;transform:translateY(-6px);}to{opacity:1;transform:none;}}

@media(max-width:880px){
  .site-nav,.site-act{display:none;}
  .site-burger{display:grid;}
}

/* ═══ THE FOOTER ═══════════════════════════════════════════════════════════ */
.site-foot{border-top:1px solid var(--line);background:var(--bg);}
.site-foot-in{
  max-width:var(--wrap-max);margin:0 auto;padding:var(--s9) var(--wrap-pad);
  display:flex;justify-content:space-between;align-items:flex-start;
  gap:var(--s7);flex-wrap:wrap;
}
.site-foot .site-logo{margin-bottom:var(--s4);}
.foot-info{font-size:var(--t-tiny);color:var(--mute);line-height:1.9;}
.foot-info a{color:var(--mute);text-decoration:none;}
.foot-info a:hover{color:var(--ink);text-decoration:underline;}
.foot-links{display:flex;gap:var(--s6);font-size:var(--t-tiny);font-weight:500;}
.foot-links a{color:var(--mute);text-decoration:none;}
.foot-links a:hover{color:var(--ink);}
@media(max-width:680px){.site-foot-in{padding:var(--s8) var(--wrap-pad);gap:var(--s5);}}

/* ═══ PRODUCT SCREENSHOTS, PRESENTED AS OBJECTS ════════════════════════════
   Three layers, and each one is doing a job.

   THE FIELD is a stone panel wider than the picture. It gives the screenshot
   somewhere to sit, which is what stops a screenshot of a white interface
   from dissolving into a white page.

   THE MOUNT is the white border around the picture, six pixels of it, with
   the shadow on it rather than on the image. A shadow on the image itself
   reads as a drop shadow on a rectangle; a shadow under a mount reads as a
   thing resting on a surface.

   THE PICTURE is the product, uncropped past what the capture script already
   decided, and every one of them opens full screen.

   The whole mount is a <button>, so it is reachable by Tab and announced as
   something that does something, which an <img> with a click handler is not. */
.shot{margin:0;}
.shot-field{
  background:var(--warm);
  border:1px solid var(--line);
  border-radius:clamp(16px,2vw,28px);
  padding:clamp(12px,2.6vw,40px);
}
/* ON A STONE SECTION THERE IS NO FIELD, because the section is already one.
   Nesting a second panel inside it put a white frame around a white frame
   around a white screenshot, and the mount, which is the thing that makes a
   picture read as an object, disappeared into it. The mount grows instead, so
   the white margin around the picture is visible against the stone. */
.sec-stone .shot-field{background:none;border:0;padding:0;}
.sec-stone .shot-mount{padding:10px;border-radius:18px;}
.sec-stone .shot-mount img{border-radius:10px;}
.shot-mount{
  display:block;width:100%;margin:0;padding:6px;cursor:zoom-in;
  border:1px solid var(--line);border-radius:14px;background:var(--card);
  position:relative;
  box-shadow:0 1px 2px rgba(29,32,26,.05),
             0 14px 28px -12px rgba(29,32,26,.16),
             0 36px 68px -30px rgba(29,32,26,.22);
  transition:box-shadow var(--slow) var(--ease),transform var(--slow) var(--ease);
}
.shot-mount:hover{
  transform:translateY(-2px);
  box-shadow:0 1px 2px rgba(29,32,26,.05),
             0 18px 34px -12px rgba(29,32,26,.18),
             0 44px 80px -30px rgba(29,32,26,.26);
}
.shot-mount img{display:block;width:100%;height:auto;border-radius:9px;}
.shot-zoom{
  position:absolute;right:16px;bottom:16px;
  width:34px;height:34px;border-radius:var(--r-pill);
  background:rgba(255,255,255,.94);color:var(--ink);
  display:grid;place-items:center;
  box-shadow:0 1px 3px rgba(29,32,26,.2),0 6px 16px rgba(29,32,26,.16);
  opacity:0;transform:translateY(4px);
  transition:opacity var(--fast) var(--ease),transform var(--fast) var(--ease);
}
.shot-mount:hover .shot-zoom,.shot-mount:focus-visible .shot-zoom{opacity:1;transform:none;}
/* THE AFFORDANCE IS ALWAYS THERE ON A SMALL SCREEN. A touch device has no
   hover, so a control that appears on hover never appears at all.

   It used to also be true that a screenshot was unreadable at 390px whatever
   was done with it. Two of them now have their OWN phone crop, served by a
   <picture> in the page, so that is no longer the reason. The other two are
   wide by nature, a revenue figure with five stat cards and a table of orders,
   and for those the lightbox is still how the picture is read. Both queries,
   because a narrow desktop window has a mouse and still needs the hint. */
@media(hover:none),(max-width:900px){.shot-zoom{opacity:1;transform:none;}}
.shot figcaption{
  margin-top:var(--s4);font-size:var(--t-small);color:var(--mute);line-height:1.6;
  max-width:640px;
}

/* ═══ THE LIGHTBOX ═════════════════════════════════════════════════════════
   One element, appended once, reused by every screenshot on the page. It is a
   real modal: aria-modal, focus moved in on open and put back on the trigger
   on close, Tab held inside it, and Escape, the backdrop and the close button
   all shut it. See /oa-lightbox.js for the half of that which is behaviour. */
.oa-lb{
  position:fixed;inset:0;z-index:200;
  display:grid;place-items:center;
  padding:clamp(16px,3vw,44px);
}
.oa-lb[hidden]{display:none;}
.oa-lb-veil{
  position:absolute;inset:0;background:rgba(21,24,19,.88);
  backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);
  animation:oa-fade .2s var(--ease);
}
.oa-lb-fig{
  position:relative;margin:0;display:flex;flex-direction:column;
  align-items:center;gap:var(--s4);max-width:100%;max-height:100%;
  animation:oa-lift .22s var(--ease);
}
.oa-lb-fig img{
  display:block;max-width:100%;max-height:calc(100vh - 150px);
  width:auto;height:auto;object-fit:contain;
  border-radius:10px;background:var(--card);cursor:zoom-out;
  box-shadow:0 30px 90px rgba(0,0,0,.55);
}
.oa-lb-cap{
  font-size:var(--t-small);line-height:1.6;color:#D9D5C9;
  text-align:center;max-width:64ch;
}
.oa-lb-close{
  position:fixed;top:clamp(12px,2vw,24px);right:clamp(12px,2vw,24px);
  appearance:none;cursor:pointer;
  width:44px;height:44px;border-radius:var(--r-pill);
  border:1px solid rgba(255,255,255,.28);background:rgba(255,255,255,.1);
  color:#fff;display:grid;place-items:center;
  transition:background var(--fast) var(--ease),border-color var(--fast) var(--ease);
}
.oa-lb-close:hover{background:rgba(255,255,255,.2);border-color:rgba(255,255,255,.5);}
.oa-lb-close:focus-visible{outline:3px solid #fff;outline-offset:2px;}
@keyframes oa-fade{from{opacity:0;}to{opacity:1;}}
@keyframes oa-lift{from{opacity:0;transform:scale(.985);}to{opacity:1;transform:none;}}

/* ═══ BUTTONS ══════════════════════════════════════════════════════════════
   The shape, the sizes and every state come from oa.css. The public site
   changes one thing: the corner. A pill on a marketing page and a 14px corner
   inside the product is a deliberate difference, not a drift. The product is
   full of rectangles a button has to sit among; a marketing page is not, and
   the softer shape is the warmer one. */
.oa-site .oa-btn{border-radius:var(--r-pill);}
.oa-site .oa-btn-lg{padding:var(--s4) var(--s7);}

/* ═══ ICONS ════════════════════════════════════════════════════════════════
   Lucide, inlined. One set on every public page, drawn at the set's own
   geometry: 24 unit grid, 2 unit stroke, round caps and joins. Inlined rather
   than fetched because eleven icons are smaller than one request, and because
   an icon that inherits currentColor cannot drift away from the text it
   labels. */
.i{width:1em;height:1em;flex-shrink:0;stroke:currentColor;fill:none;
   stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}

/* ═══ PROSE, for the legal pages ═══════════════════════════════════════════
   A single measured column. The headings are the serif, the body is the sans,
   and the numbers stay in the heading rather than becoming a decoration
   beside it. */
.doc{max-width:66ch;margin:0 auto;}
.doc-head{margin-bottom:var(--s8);padding-bottom:var(--s6);border-bottom:1px solid var(--line);}
.doc-head h1{margin-bottom:var(--s3);}
.doc-updated{font-size:var(--t-small);color:var(--faint);}
.doc h2{
  font-size:clamp(1.25rem,1.16rem + .3vw,1.4rem);font-weight:600;
  letter-spacing:-.012em;line-height:1.3;
  margin:var(--s9) 0 var(--s4);
}
.doc h2:first-of-type{margin-top:0;}
.doc p,.doc ul,.doc ol{
  font-size:var(--t-lead);color:var(--body);line-height:1.75;margin-bottom:var(--s5);
}
.doc ul,.doc ol{padding-left:var(--s6);}
.doc li{margin-bottom:var(--s3);}
.doc a{color:var(--accent);text-decoration:underline;text-underline-offset:3px;
  text-decoration-thickness:1px;text-decoration-color:var(--accent-line);}
.doc a:hover{text-decoration-color:var(--accent);}
/* ── A TABLE IN A POLICY ───────────────────────────────────────────────────
   The privacy policy lists its subprocessors in a four column table. The page
   it replaced carried `table{width:100%}` in its own stylesheet, and losing
   that let the table size to its content and push the document 28px wider than
   a 390px screen. It is back, with the border treatment the rest of the
   document uses.

   AND A SCROLL CONTAINER AROUND IT. Four columns inside 350px is legible only
   in the sense that every word is technically on the page. `.doc-table` lets
   the table keep a workable minimum and scroll inside its own box, so the
   PAGE never scrolls sideways, which is the part that feels broken. */
.doc-table{
  overflow-x:auto;-webkit-overflow-scrolling:touch;
  margin-bottom:var(--s6);
  border:1px solid var(--line);border-radius:var(--r-md);
}
/* THE MINIMUM IS A PHONE RULE ONLY. At the document's own measure the four
   columns fit and wrap, which is what the page this replaced did, so forcing
   44rem at every width made the table scroll inside its box on a 1440px screen
   with the last column cut off. Below 700px the column is narrower than any
   four column table can usefully be, and there the minimum applies and the box
   scrolls: crushed to 350px every cell gets a four character measure and each
   sentence ladders down twenty lines, which is worse than scrolling. */
.doc table{width:100%;border-collapse:collapse;font-size:var(--t-small);}
@media(max-width:700px){.doc table{min-width:40rem;}}
.doc th,.doc td{
  text-align:left;padding:var(--s4) var(--s5);vertical-align:top;
  border-bottom:1px solid var(--line);color:var(--body);line-height:1.65;
}
.doc thead th{
  font-weight:700;color:var(--ink);background:var(--sunk);
  border-bottom:1px solid var(--line);
}
.doc tbody tr:last-child td{border-bottom:0;}
.doc td a{overflow-wrap:anywhere;}

.doc-sign{
  margin-top:var(--s9);padding-top:var(--s6);border-top:1px solid var(--line);
  font-size:var(--t-small);color:var(--mute);
}

/* ═══ THE CENTRED CARD, for sign in and the system pages ═══════════════════
   Every page that is one short thing on an otherwise empty screen uses this:
   log in, forgot password, reset password, the closed signup page, the two
   Shopify return pages, the confirmation page and the 404. They looked like
   seven different products before, each with its own copy of a card, a field
   and a button. */
.oa-site.shell{
  min-height:100vh;display:flex;flex-direction:column;
  align-items:center;justify-content:center;
  padding:var(--s8) var(--s5);
}
.shell-in{width:100%;max-width:440px;}
.shell-wide{max-width:600px;}
.shell-mark{margin-bottom:var(--s7);text-align:center;}
.shell-mark .site-logo{justify-content:center;margin-bottom:var(--s7);}
/* `.oa-site h1` above is (0,1,1) and a lone class is (0,1,0), so a bare
   .shell-title loses to it whatever the order, and every one of these pages
   opened with a 68px display headline over a 440px card. Qualified, so it
   wins. */
.oa-site .shell-title{font-family:var(--font-display);font-optical-sizing:auto;
  font-size:clamp(1.6rem,1.4rem + .8vw,2rem);font-weight:500;
  letter-spacing:-.02em;line-height:1.15;color:var(--ink);text-wrap:balance;}
.shell-sub{margin:var(--s3) auto 0;font-size:var(--t-base);color:var(--mute);
  line-height:1.6;max-width:46ch;}

/* A mark inside the card: the tick on a confirmation, the envelope on an
   application received, the warning on a link that has expired. One shape,
   tinted by meaning, drawn from the same icon set as everything else. */
.shell-icon{
  width:60px;height:60px;border-radius:var(--r-pill);margin:0 auto var(--s5);
  display:grid;place-items:center;
  background:var(--accent-soft);color:var(--accent);
}
.shell-icon .i{width:26px;height:26px;}
.shell-icon-warn{background:var(--amber-soft);color:var(--amber-text);}

/* A spinner, for the two Shopify return pages, which genuinely are waiting on
   a round trip somebody cannot do anything about. oa.css reduces the
   animation to nothing under prefers-reduced-motion, which leaves a static
   ring: still a legible "waiting", and not a thing spinning at somebody who
   asked for that not to happen. */
.shell-spin{
  width:30px;height:30px;margin:0 auto var(--s5);border-radius:var(--r-pill);
  border:3px solid var(--accent-soft);border-top-color:var(--accent);
  animation:oa-spin .8s linear infinite;
}
@keyframes oa-spin{to{transform:rotate(360deg);}}

/* A value the page is quoting back: a store domain, a referral link. Monospace
   only where the thing really is a machine string. */
.shell-value{
  background:var(--sunk);border:1px solid var(--line);border-radius:var(--r-md);
  padding:var(--s4) var(--s5);font-size:var(--t-small);color:var(--ink);
  overflow-wrap:anywhere;text-align:left;
}
.shell-value-mono{font-family:ui-monospace,'SF Mono','Fira Code',Menlo,monospace;font-size:var(--t-tiny);}
.shell-card{
  background:var(--card);border:1px solid var(--line);
  border-radius:var(--r-xl);box-shadow:var(--sh-md);
  padding:clamp(24px,4vw,36px);
}
.shell-foot{
  margin-top:var(--s6);text-align:center;font-size:var(--t-small);
  color:var(--mute);line-height:2;
}
.shell-foot a{color:var(--accent);font-weight:600;text-decoration:none;}
.shell-foot a:hover{text-decoration:underline;}
.shell-legal{
  margin-top:var(--s8);text-align:center;font-size:var(--t-tiny);color:var(--faint);
}
.shell-legal a{color:var(--faint);text-decoration:none;margin:0 var(--s2);}
.shell-legal a:hover{color:var(--ink);}

/* A message inside the card: an error, a confirmation, a note about the
   store being connected. One shape, four intents. */
.note{
  border-radius:var(--r-md);padding:var(--s4) var(--s5);
  font-size:var(--t-small);line-height:1.6;
}
.note-accent{background:var(--accent-soft);border:1px solid var(--accent-line);color:var(--body);}
.note-error{background:var(--danger-soft);border:1px solid #EFD3CE;color:var(--danger);font-weight:500;}
.note-warn{background:var(--amber-soft);border:1px solid #EADFC2;color:var(--amber-text);}
.note-quiet{background:var(--sunk);border:1px solid var(--line);color:var(--body);}
.note b{color:var(--ink);}
.note-error b{color:inherit;}

/* ═══ MOTION ═══════════════════════════════════════════════════════════════
   ONE orchestrated moment on the site, and it is the homepage settling on
   load: the headline, then the sentence under it, then the product panel.
   There is no scroll triggered reveal anywhere, deliberately. Every section
   fading up as it enters the viewport is the default a page gets when nobody
   decided, it fights the reader on the way back up, and it makes a screenshot
   arrive after the eye does.

   oa.css already reduces every animation and transition to nothing under
   prefers-reduced-motion. The `@media (prefers-reduced-motion: no-preference)`
   wrapper here is the other half of that: with reduced motion asked for, the
   elements are simply never given the starting state, so there is nothing to
   recover from if the animation does not run. */
@media(prefers-reduced-motion:no-preference){
  .settle{opacity:0;animation:oa-settle .62s var(--ease) forwards;}
  .settle-1{animation-delay:.02s;}
  .settle-2{animation-delay:.12s;}
  .settle-3{animation-delay:.22s;}
  .settle-4{animation-delay:.34s;}
}
@keyframes oa-settle{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:none;}}
