/* fonts.css */
/* =============================================================================
   Asiam3 font delivery
   =============================================================================

   The layer above the design system: the font files themselves, declared once
   and consumed by the typography tokens in `tokens.css`. Nothing here chooses
   which face a heading uses. That is a token, and a direction owns it, so a
   future decision to replace a family is a change to one token rather than a
   change to any component.

   -----------------------------------------------------------------------------
   Why every family is declared twice
   -----------------------------------------------------------------------------

   Each face ships as two files, split exactly as the upstream projects publish
   them: `latin` for the ordinary case, and `latin-ext` for everything beyond
   it. The split is not a size optimisation for its own sake. **A browser
   downloads a subset only when the page actually contains a character in its
   range**, so an English page pays for `latin` alone and a page carrying a
   Ghanaian trading name pays for the second file as well.

   That second file is the one that matters here. Asiam3's own name exists
   because the Ewe vowel ɛ is awkward to type, and merchant and product names
   will carry ɛ ɔ ŋ ɖ ʋ. Those live in Latin Extended-B and the IPA extensions,
   which the `latin` subset does not contain: verified against the binaries
   rather than assumed, and guarded so it stays verified.

   -----------------------------------------------------------------------------
   font-display: swap
   -----------------------------------------------------------------------------

   Text is readable immediately in the fallback and is replaced when the face
   arrives. On an uneven connection that is the right trade: the alternative
   hides the words for up to three seconds while a merchant's customer waits.
   The reflow that follows is the accepted cost, and the fallback stacks in
   `tokens.css` are chosen to keep it small.
============================================================================= */

/* -----------------------------------------------------------------------------
   EB Garamond. The display face: shop names, product names, headings.

   It is here because the face the direction originally named could not set a
   Ghanaian name. Newsreader carries only ŋ of the five characters, and so do
   Instrument Serif, Source Serif, Literata, Spectral, Playfair Display, Lora,
   Alegreya, Crimson Pro, Fraunces and every other editorial serif tested. All
   of them were read from their own character maps rather than from their
   descriptions.

   Four faces cover all five: EB Garamond, Charis SIL, Noto Serif and Libre
   Baskerville. This one was chosen because it fills the role the direction
   actually specifies, which is an editorial display serif with character, and
   the alternatives either flatten that (Noto Serif is a workhorse) or move
   away from it (Charis SIL is a sturdy text serif).

   **Charis SIL remains the strongest alternative** and is one token away: it
   was drawn by SIL specifically for Latin-script African orthographies, so its
   coverage of these letters is deliberate rather than incidental. Swapping is a
   change to `--a3-dir-font-display` and nothing else.

   Covers ɛ ɔ ŋ ɖ ʋ in its extended subset. Verified, 799 glyphs.
----------------------------------------------------------------------------- */
@font-face {
  font-family: "EB Garamond";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/assets/eb-garamond-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: "EB Garamond";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/assets/eb-garamond-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;
}

/* -----------------------------------------------------------------------------
   Inter. The interface and body face.
   Covers ɛ ɔ ŋ ɖ ʋ in its extended subset.
----------------------------------------------------------------------------- */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/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 700;
  font-display: swap;
  src: url("/assets/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-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* -----------------------------------------------------------------------------
   Inter Tight. The display face the current direction uses for names.
   Covers ɛ ɔ ŋ ɖ ʋ in its extended subset.
----------------------------------------------------------------------------- */
@font-face {
  font-family: "Inter Tight";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/inter-tight-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 Tight";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/inter-tight-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;
}

/* -----------------------------------------------------------------------------
   IBM Plex Mono. Money and short labels, for its tabular figures.

   **Its extended subset does not carry ɛ ɔ ɖ ʋ**, and only ŋ of the five.
   Verified against the binary. So its declared range stops short of the IPA
   extensions at U+0250-02AF rather than claiming coverage it does not have:
   a character in that block falls to the next family in the stack **because
   this file says so**, not because a browser quietly discovered a hole.

   That is a real limitation and it is bounded by what this role does. The
   numeric face carries amounts, currency codes and short labels, where those
   letters do not appear. If it is ever asked to set a merchant's name, this
   declaration is where the problem will be visible.
----------------------------------------------------------------------------- */
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/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("/assets/ibm-plex-mono-500-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-024F, U+02B0-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/ibm-plex-mono-600-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: 600;
  font-display: swap;
  src: url("/assets/ibm-plex-mono-600-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-024F, U+02B0-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;
}

/* -----------------------------------------------------------------------------
   Onest. The face Direction C is set in, both display and body.

   -----------------------------------------------------------------------------
   Why this face and not the one the mockups used
   -----------------------------------------------------------------------------

   Direction C was designed as twenty screens in Bricolage Grotesque and
   Manrope, and the Founder approved that look. **Neither face can set a
   Ghanaian name.** Read from the binaries with the same parser
   `fontDelivery.test.ts` uses: of ɛ ɔ ŋ ɖ ʋ, both carry ŋ alone.

   That is not a new discovery so much as the same one. EB Garamond is in this
   file because Newsreader, Instrument Serif, Source Serif, Literata, Spectral,
   Playfair Display, Lora, Alegreya, Crimson Pro and Fraunces all failed the
   identical test. Forty faces were measured for Direction C. **Six passed**:
   Andika, Charis SIL, Fira Sans, Gentium Plus, Noto Sans and Onest. Two of the
   six are serifs, which is the language Direction A already speaks, and two are
   humanist workhorses. Onest is the only contemporary geometric sans among
   them, which is the shape Direction C's whole argument rests on -- rounded,
   modern, pitched at a merchant arriving from WhatsApp rather than from a
   market hall.

   So the direction keeps its look and changes its face, which is the trade the
   Founder authorised: *"if you have to rebuild it in Asiam3 way but still
   having the same ui too no problem."*

   **Fira Sans is the strongest alternative** and is one token away, exactly as
   Charis SIL is for EB Garamond. It is warmer and narrower, drawn for low-end
   phones, and it would move Direction C a little toward Direction A's humanism.

   -----------------------------------------------------------------------------
   Licence, and one discrepancy worth recording rather than smoothing over
   -----------------------------------------------------------------------------

   SIL Open Font License 1.1. `LICENSE-Onest.txt` is taken from the project's
   own repository, `github.com/simpals/onest`, and is byte-identical to the copy
   in the Google Fonts collection.

   **The licence's copyright line names `github.com/googlefonts/onest`, which
   does not resolve. The binary's own name table names `github.com/simpals/onest`,
   which does.** Both were checked rather than assumed. The licence TERMS are
   identical in all three copies, so nothing about the permission is in doubt;
   what is stale is a URL inside the upstream file, and the binary is the more
   current of the two. Recorded here because the next person to check will find
   the same 404 and should not have to re-derive why it is not a problem.

   One variable file per subset, 400-800, so every weight this direction uses
   arrives in one download rather than four.

   -----------------------------------------------------------------------------
   What the swap costs, measured
   -----------------------------------------------------------------------------

   `font-display: swap` means the fallback is rendered first and replaced, and
   the reflow that follows is the accepted cost. How large it is depends on how
   close the fallback's letterforms are, so it was measured from the binaries
   rather than assumed:

       Onest      x-height 0.527 em   cap-height 0.707 em
       Arial      x-height 0.519 em   cap-height 0.716 em

   Under two per cent on both, so the text barely moves when the face arrives.
   For comparison, EB Garamond sits at 0.400 against a Georgia fallback near
   0.484, a seventeen per cent mismatch -- Direction C is the gentler of the two
   on a slow connection, which is the connection Asiam3 is built for.

   Vertically there is nothing to shift: `components.css` sets a unitless
   line-height, which overrides the face's own line box, so Onest being eleven
   per cent taller than Arial in its metrics changes no layout.

   Covers ɛ ɔ ŋ ɖ ʋ in its extended subset. Verified, 279 glyphs.
----------------------------------------------------------------------------- */
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/assets/onest-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: "Onest";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/assets/onest-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;
}

/* directions.css */
/* =============================================================================
   Asiam3 visual directions
   =============================================================================

   The first of four layers:

       direction tokens  ->  semantic tokens  ->  components  ->  surface shells

   A direction owns the values a visual language decides: ground, ink, rule,
   action, the semantic state colours, the typeface roles and the shape
   language. It owns nothing else. **Switching direction changes presentation
   and nothing about the product**: the same routes render the same state, the
   same copy, the same controls, under the same rules.

   -----------------------------------------------------------------------------
   Why the names here are not the names components use
   -----------------------------------------------------------------------------

   Every token in this file is `--a3-dir-*`, and `tokens.css` consumes it as the
   *source* of the matching `--a3-*` semantic token:

       --a3-bg: var(--a3-dir-bg, <the value Asiam3 had before directions>);

   Two things follow, and both are the reason for the indirection.

   A page with no direction attribute still renders, because every semantic
   token keeps its original value as the fallback. Nothing depends on this file
   being loaded.

   And the **dark theme still wins**. It redefines the semantic tokens directly,
   so it overrides the whole `var()` expression rather than competing with it on
   specificity. That is deliberate: the dark theme is direction-agnostic today,
   both directions were delivered light-only, and whether A and B carry their
   own dark grounds is an open design question rather than something to answer
   by accident of the cascade.

   -----------------------------------------------------------------------------
   The rule this file exists to keep
   -----------------------------------------------------------------------------

   **A direction selector appears in this file and nowhere else.** A component
   that reaches for `[data-a3-direction]` has stopped consuming a token and
   started knowing which direction it is in, and from that point the backup
   direction is a second layout system rather than a set of values. A guard
   fails if one appears outside this file.

   **Both directions define the same token names.** A name defined in A and
   missing in B is a broken fallback that nobody notices until they switch, so
   a guard compares the two sets rather than trusting review.
============================================================================= */

/* -----------------------------------------------------------------------------
   Direction A, "Market Hall". The active production direction.

   A warm bone ground so photographs of food, cloth and craft sit on the page
   rather than float above it. Deep blue for chrome, headings and ink. Teal for
   anything you can act on. Kola red for identity and editorial emphasis only.

   **Three rules the colours here exist to keep**, each of which was a measured
   collision before it was a rule:

   The chrome and the action may never be the same hue. One colour doing both
   jobs makes every surface look equally pressable, which is the defect this
   direction was corrected for once already.

   The identity red never carries state. It sits 26 units in RGB from the danger
   red, so a mark drawn in it beside a failure notice is two different meanings
   in one colour. Kola appears on the wordmark and on editorial emphasis, and on
   no button, badge or notice.

   Settled money is achromatic. It was nine units from the action teal, which
   would have made "press this" and "the money has cleared" indistinguishable.

   Every pair below is checked against WCAG 2.2 AA by a guard that computes the
   contrast from these values rather than trusting them.
----------------------------------------------------------------------------- */
:root[data-a3-direction="a"] {
  /* Ground and ink. Bone paper, deep blue ink. */
  --a3-dir-bg:             #F7F2E9;
  --a3-dir-bg-raised:      #FFFDF8;
  --a3-dir-bg-sunken:      #EFE7D8;
  --a3-dir-bg-inverse:     #0D2537;
  --a3-dir-border:         #DFD5C4;
  --a3-dir-border-strong:  #857A6B;
  --a3-dir-text:           #0D2537;
  --a3-dir-text-secondary: #2C4657;
  --a3-dir-text-muted:     #6F665C;
  --a3-dir-text-inverse:   #FFFDF8;

  /* Action, and the focus ring that follows it. Teal, and teal alone. */
  --a3-dir-primary-700:    #084C58;
  --a3-dir-primary-600:    #0B6270;
  --a3-dir-primary-100:    #E4EFF1;
  --a3-dir-link:           #0B6270;
  --a3-dir-focus:          #0B6270;
  /* The ink that sits on the action colour. A distinct meaning from the ink on
     the inverse ground, which is a different surface, and it belongs to the
     direction: a direction that chose a pale action would need dark ink here,
     and until this existed the primary button's label was a palette white that
     no direction could reach and no contrast check measured. */
  --a3-dir-text-on-action: #FFFDF8;

  /* Identity. The mark, an editorial emphasis, and nothing that carries state. */
  --a3-dir-identity:       #B33420;
  /* The mark on a rating pill. Gold in the Founder's mockups, measured off
     them at #FDCB5C; the other directions carry their own warm equivalent.
     A token rather than a literal because a component may not hard-code a
     colour, and because a star that is gold in one direction and not in
     another is a direction doing something other than declaring values. */
  --a3-dir-rating-mark:    #C9922E;
  /* The surfaces. Added 2026-08-30 with Direction D. These two values were
     literals inside `tokens.css` and therefore identical in every direction --
     a merchant surface looked the same whichever visual language was active,
     which is the one thing a direction is supposed to be able to change. The
     values here are the literals they replace, so A, B and C are untouched. */
  --a3-dir-accent-merchant: #4a6b2f;
  --a3-dir-accent-control:  #6b3f7a;

  /* Semantic states. Three claims get three treatments, and a direction may
     restate them but may never collapse them into one. */
  --a3-dir-success-600:    #1D7A4A;
  --a3-dir-success-100:    #E5F2EA;
  --a3-dir-settled-600:    #3F4448;
  --a3-dir-settled-100:    #EDEAE4;
  --a3-dir-verified-600:   #4A5FA8;
  --a3-dir-verified-100:   #EAECF7;
  --a3-dir-pending-600:    #8A6216;
  --a3-dir-pending-100:    #F8F0DD;
  --a3-dir-warning-600:    #A35A12;
  --a3-dir-warning-100:    #FAEDDF;
  --a3-dir-danger-600:     #A32020;
  --a3-dir-danger-100:     #F8E7E5;
  --a3-dir-denied-600:     #5F4470;
  --a3-dir-denied-100:     #F0ECF3;

  /* Typeface roles, not faces. The families are a token so a direction can
     change them without a component knowing, and so replacing the display face
     is one line rather than a search through the components. */
  --a3-dir-font-body:      "Inter", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --a3-dir-font-display:   "EB Garamond", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --a3-dir-font-numeric:   "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  /* Shape. Nearly square, because a market hall is printed rather than app-like. */
  --a3-dir-radius-sm:      2px;
  --a3-dir-radius-md:      2px;
  --a3-dir-radius-lg:      3px;

  /* Materials, for a shop or a product with no photograph.

     A direction owns these for the same reason it owns everything else here:
     they are a visual decision, and a component must not know which one it is
     in. They are woven fields rather than pictures, and each is named for
     something a Ghanaian merchant actually sells. */
  --a3-dir-material-cocoa:    linear-gradient(155deg, #8A5029, #3D2110);
  --a3-dir-material-kente:    linear-gradient(150deg, #D2A02C 8%, #A8471F 55%, #5E1F16);
  --a3-dir-material-shea:     linear-gradient(155deg, #E8CF9A, #AB7C36);
  --a3-dir-material-pepper:   linear-gradient(155deg, #9E6238, #38200F);
  --a3-dir-material-wood:     linear-gradient(155deg, #B5813F, #4D2F16);
  --a3-dir-material-market:   linear-gradient(150deg, #D07B34 5%, #9A3F21 48%, #2C1A10);
  --a3-dir-material-hands:    linear-gradient(150deg, #C08347, #4D2A14);
  --a3-dir-material-greenery: linear-gradient(155deg, #61895A, #1F3320);

  /* The ink on a material. Deliberately not the ink on the inverse ground: a
     material is dark in every theme, so this must stay light when the inverse
     ground flips to near-black in the dark theme. */
  --a3-dir-text-on-material: #FFFDF8;

  /* The dark theme, unchanged. Market Hall has never had a night palette of its own and this restates what it already renders rather than inventing one. */
  --a3-dir-dark-bg: #0b1319;
  --a3-dir-dark-bg-raised: #131f27;
  --a3-dir-dark-bg-sunken: #091015;
  --a3-dir-dark-border: #24343f;
  --a3-dir-dark-border-strong: #5A7382;
  --a3-dir-dark-text: #e8edf0;
  --a3-dir-dark-text-secondary: #a9b6c0;
  --a3-dir-dark-text-muted: #7e8d99;
  --a3-dir-dark-link: #63b8cc;
  --a3-dir-dark-focus: #4fc3e0;
  --a3-dir-dark-primary-600: #2b93ab;
  --a3-dir-dark-primary-100: #10303a;
  --a3-dir-dark-success-600: #46b07a;
  --a3-dir-dark-success-100: #0f2b1f;
  --a3-dir-dark-settled-600: #4aa7c4;
  --a3-dir-dark-settled-100: #0d2731;
  --a3-dir-dark-verified-600: #8f9fd9;
  --a3-dir-dark-verified-100: #1a1f38;
  --a3-dir-dark-pending-600: #d0a047;
  --a3-dir-dark-pending-100: #2c2313;
  --a3-dir-dark-warning-600: #d38a45;
  --a3-dir-dark-warning-100: #2e1e11;
  --a3-dir-dark-danger-600: #e06666;
  --a3-dir-dark-danger-100: #2e1414;
  --a3-dir-dark-denied-600: #a48cb8;
  --a3-dir-dark-denied-100: #221a29;
  --a3-dir-dark-identity: #D9694F;
  --a3-dir-dark-accent-merchant: #6d9648;
  --a3-dir-dark-accent-control: #AC80BD;

}

/* -----------------------------------------------------------------------------
   Direction B. The backup template.

   It exists so the presentation boundary is proved rather than asserted: a
   direction that is only a name proves nothing, and an architecture with one
   direction in it is not an architecture.

   **These values are provisional.** They are drawn from the Field direction's
   own language - a chalk ground, hard near-black rules, an ultramarine action
   and a burnt sienna identity - so that switching is visibly a different visual
   world. They are not B's finished styling, which arrives with its own
   milestone alongside its type and composition.

   What is deliberately identical to A: the token names, the typeface roles
   (B's own faces wait for font delivery), and every product behaviour. What
   differs: colour and shape. That is the whole of what a direction may own.
----------------------------------------------------------------------------- */
:root[data-a3-direction="b"] {
  --a3-dir-bg:             #FBFAF7;
  --a3-dir-bg-raised:      #FFFFFF;
  --a3-dir-bg-sunken:      #EFEDE6;
  --a3-dir-bg-inverse:     #111111;
  --a3-dir-border:         #C9C7BE;
  --a3-dir-border-strong:  #111111;
  --a3-dir-text:           #111111;
  --a3-dir-text-secondary: #3D3C37;
  --a3-dir-text-muted:     #5C5B55;
  --a3-dir-text-inverse:   #FFFFFF;

  --a3-dir-primary-700:    #132a52;
  --a3-dir-primary-600:    #1D3A6B;
  --a3-dir-primary-100:    #E9EDF4;
  --a3-dir-link:           #1D3A6B;
  --a3-dir-focus:          #1D3A6B;
  /* Chalk white rather than A's bone, because B's ground is cooler. */
  --a3-dir-text-on-action: #FFFFFF;

  /* B's identity is a burnt sienna, held to the same rule: identity only. */
  --a3-dir-identity:       #8A3B12;
  /* The mark on a rating pill. Gold in the Founder's mockups, measured off
     them at #FDCB5C; the other directions carry their own warm equivalent.
     A token rather than a literal because a component may not hard-code a
     colour, and because a star that is gold in one direction and not in
     another is a direction doing something other than declaring values. */
  --a3-dir-rating-mark:    #C9922E;
  /* The surfaces. Added 2026-08-30 with Direction D. These two values were
     literals inside `tokens.css` and therefore identical in every direction --
     a merchant surface looked the same whichever visual language was active,
     which is the one thing a direction is supposed to be able to change. The
     values here are the literals they replace, so A, B and C are untouched. */
  --a3-dir-accent-merchant: #4a6b2f;
  --a3-dir-accent-control:  #6b3f7a;

  --a3-dir-success-600:    #186a41;
  --a3-dir-success-100:    #E4F1EA;
  --a3-dir-settled-600:    #3F4448;
  --a3-dir-settled-100:    #EDEDEA;
  --a3-dir-verified-600:   #3f5296;
  --a3-dir-verified-100:   #E9ECF8;
  --a3-dir-pending-600:    #7c5713;
  --a3-dir-pending-100:    #F8F1DE;
  --a3-dir-warning-600:    #94500f;
  --a3-dir-warning-100:    #F9EDE1;
  --a3-dir-danger-600:     #98191a;
  --a3-dir-danger-100:     #F9E7E7;
  --a3-dir-denied-600:     #553c66;
  --a3-dir-denied-100:     #F0ECF4;

  --a3-dir-font-body:      "Inter", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --a3-dir-font-display:   "Inter Tight", "Inter", "Segoe UI", system-ui, sans-serif;
  --a3-dir-font-numeric:   "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  /* Square rather than rounded. A shape decision a direction is entitled to
     make, and the one place B's difference is structural rather than chromatic
     - expressed as a token, so no component learns which direction it is in. */
  --a3-dir-radius-sm:      0;
  --a3-dir-radius-md:      0;
  --a3-dir-radius-lg:      0;

  /* B's materials are flatter and cooler than A's, in keeping with a chalk
     ground and hard rules. The same eight names, so a component asking for
     cocoa gets cocoa in either direction. */
  --a3-dir-material-cocoa:    linear-gradient(160deg, #7A5433, #2E1C10);
  --a3-dir-material-kente:    linear-gradient(160deg, #C9A33F 10%, #8E4B25 60%, #3A1B14);
  --a3-dir-material-shea:     linear-gradient(160deg, #E3CFA6, #9A7743);
  --a3-dir-material-pepper:   linear-gradient(160deg, #8E5F3D, #2C1B10);
  --a3-dir-material-wood:     linear-gradient(160deg, #A67C46, #402C17);
  --a3-dir-material-market:   linear-gradient(160deg, #BE7639, #7E3A20 50%, #241610);
  --a3-dir-material-hands:    linear-gradient(160deg, #AF7A4A, #402516);
  --a3-dir-material-greenery: linear-gradient(160deg, #557A52, #1B2C1D);

  --a3-dir-text-on-material: #FFFFFF;

  /* The dark theme, unchanged. Field has never had a night palette of its own either. */
  --a3-dir-dark-bg: #0b1319;
  --a3-dir-dark-bg-raised: #131f27;
  --a3-dir-dark-bg-sunken: #091015;
  --a3-dir-dark-border: #24343f;
  --a3-dir-dark-border-strong: #5A7382;
  --a3-dir-dark-text: #e8edf0;
  --a3-dir-dark-text-secondary: #a9b6c0;
  --a3-dir-dark-text-muted: #7e8d99;
  --a3-dir-dark-link: #63b8cc;
  --a3-dir-dark-focus: #4fc3e0;
  --a3-dir-dark-primary-600: #2b93ab;
  --a3-dir-dark-primary-100: #10303a;
  --a3-dir-dark-success-600: #46b07a;
  --a3-dir-dark-success-100: #0f2b1f;
  --a3-dir-dark-settled-600: #4aa7c4;
  --a3-dir-dark-settled-100: #0d2731;
  --a3-dir-dark-verified-600: #8f9fd9;
  --a3-dir-dark-verified-100: #1a1f38;
  --a3-dir-dark-pending-600: #d0a047;
  --a3-dir-dark-pending-100: #2c2313;
  --a3-dir-dark-warning-600: #d38a45;
  --a3-dir-dark-warning-100: #2e1e11;
  --a3-dir-dark-danger-600: #e06666;
  --a3-dir-dark-danger-100: #2e1414;
  --a3-dir-dark-denied-600: #a48cb8;
  --a3-dir-dark-denied-100: #221a29;
  --a3-dir-dark-identity: #D9694F;
  --a3-dir-dark-accent-merchant: #6d9648;
  --a3-dir-dark-accent-control: #AC80BD;

}

/* -----------------------------------------------------------------------------
   Direction C, "Cloth & Signal". A candidate skin, not the active direction.

   Founder direction, 26 August 2026, after reviewing twenty mockup screens
   produced in a Cowork session: *"I like the visual so make it work... If you
   have to rebuild it in Asiam3 way but still having the same ui too no
   problem."* So this is the mockups' argument expressed as values, and only as
   values -- `design/prototypes/cloth-and-signal/` remains the picture, this is
   the direction.

   Warm cream rather than bone paper. Plum-black ink instead of deep blue. Green
   for anything you can act on, rose for identity, and rounded shape throughout:
   8, 12 and 16 pixels where Market Hall is near-square at 2 and Field is at 0.
   It is pitched at a merchant arriving from WhatsApp or TikTok rather than at a
   market-hall metaphor, which is the whole of its case.

   -----------------------------------------------------------------------------
   The one substantive redesign, and why it was forced
   -----------------------------------------------------------------------------

   **The mockups rendered "Open", "Published", "Delivered" and "Paid" in the same
   green as the primary buttons.** That is Collision 1 in
   `design/context/COLOUR_DECISION.md` reappearing in a new pair: "press this"
   and "this succeeded" sharing a hue, which is the defect the Founder personally
   corrected once already and the reason the accent was split three ways.

   In Market Hall the action is teal and success is green, so the two have room.
   Here the action IS green, so success had to move, and there is nowhere
   comfortable for it to go: it sits between the green it cannot be and the blue
   that `verified` already speaks. It is a teal-blue, 63 RGB units from the
   action and 48 from verified.

   Measured against both shipped directions rather than asserted: Direction A has
   seven role pairs inside the separation target and Direction B has twelve.
   **This has five, and its tightest pair is wider than theirs.** That is the
   honest claim -- not that the problem is solved, but that this is the most
   separated of the three.

   `pending` and `warning` stay close on purpose, exactly as they do in Market
   Hall: two warm hues kept apart by hue and by the word beside them, so
   "awaiting review" and "needs attention" are not the same claim.

   The rose never carries state. It is the wordmark, the woven rule and editorial
   emphasis, and it appears on no button, badge or notice -- the rule Market
   Hall's kola red is held to, for the same reason.

   Every pair below is computed by `contrast.test.ts` from these values.
----------------------------------------------------------------------------- */
:root[data-a3-direction="c"] {
  /* Ground and ink. Warm cream, plum-black ink. */
  --a3-dir-bg:             #F8F1E3;
  --a3-dir-bg-raised:      #FFFBF4;
  --a3-dir-bg-sunken:      #EDE3CE;
  --a3-dir-bg-inverse:     #2A0F30;
  --a3-dir-border:         #DFD2C0;
  --a3-dir-border-strong:  #8A7481;
  --a3-dir-text:           #321239;
  --a3-dir-text-secondary: #4E2A54;
  --a3-dir-text-muted:     #6B5570;
  --a3-dir-text-inverse:   #FFFBF4;

  /* Action. Green, and 111 RGB units from the ink, so chrome is never
     mistakable for something pressable. */
  --a3-dir-primary-700:    #005C3B;
  --a3-dir-primary-600:    #00734B;
  --a3-dir-primary-100:    #DCEEE4;
  --a3-dir-link:           #006944;
  --a3-dir-focus:          #006944;
  --a3-dir-text-on-action: #FFFBF4;

  /* Identity. Rose, on the mark and on editorial emphasis. Never on state:
     134 units from danger, which is the separation Market Hall's kola red
     does not have. */
  --a3-dir-identity:       #C4327F;
  /* The mark on a rating pill. Gold in the Founder's mockups, measured off
     them at #FDCB5C; the other directions carry their own warm equivalent.
     A token rather than a literal because a component may not hard-code a
     colour, and because a star that is gold in one direction and not in
     another is a direction doing something other than declaring values. */
  --a3-dir-rating-mark:    #C98A2E;
  /* The surfaces. Added 2026-08-30 with Direction D. These two values were
     literals inside `tokens.css` and therefore identical in every direction --
     a merchant surface looked the same whichever visual language was active,
     which is the one thing a direction is supposed to be able to change. The
     values here are the literals they replace, so A, B and C are untouched. */
  --a3-dir-accent-merchant: #4a6b2f;
  --a3-dir-accent-control:  #6b3f7a;

  /* The seven states. See the note above for why success is not green. */
  --a3-dir-success-600:    #0C5F86;
  --a3-dir-success-100:    #E0EDF6;
  --a3-dir-settled-600:    #514D4A;
  --a3-dir-settled-100:    #EDEAE3;
  --a3-dir-verified-600:   #2F57A6;
  --a3-dir-verified-100:   #E6ECF8;
  --a3-dir-pending-600:    #8A5A0A;
  --a3-dir-pending-100:    #F8EEDA;
  --a3-dir-warning-600:    #A8500F;
  --a3-dir-warning-100:    #FAE9DC;
  --a3-dir-danger-600:     #A81F2E;
  --a3-dir-danger-100:     #F9E6E6;
  --a3-dir-denied-600:     #6B4788;
  --a3-dir-denied-100:     #F0EAF6;

  /* Type. Onest in both roles, separated by weight, the way Field separates
     Inter Tight from Inter. The mockups used Bricolage Grotesque and Manrope
     and neither can set a Ghanaian name; `fonts.css` records what was measured
     and what replaced them. */
  --a3-dir-font-body:      "Onest", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --a3-dir-font-display:   "Onest", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --a3-dir-font-numeric:   "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  /* Shape. The direction's loudest single decision. */
  --a3-dir-radius-sm:      8px;
  --a3-dir-radius-md:      12px;
  --a3-dir-radius-lg:      16px;

  /* Materials, for a shop with no photographs. Warmer and lighter than Market
     Hall's, to sit on cream rather than on bone. Kente is woven from this
     direction's own rose and green, which is what the mockups drew. */
  --a3-dir-material-cocoa:    linear-gradient(155deg, #A7694C, #4E2413);
  --a3-dir-material-kente:    linear-gradient(150deg, #C4327F 10%, #B07A2A 55%, #00734B);
  --a3-dir-material-shea:     linear-gradient(155deg, #E9D8B4, #B08F55);
  --a3-dir-material-pepper:   linear-gradient(155deg, #C05340, #571712);
  --a3-dir-material-wood:     linear-gradient(155deg, #B08355, #4F3320);
  --a3-dir-material-market:   linear-gradient(150deg, #D18E35 6%, #A2492A 50%, #331D17);
  --a3-dir-material-hands:    linear-gradient(150deg, #B47E5C, #56301F);
  --a3-dir-material-greenery: linear-gradient(155deg, #5A8C5F, #1B3A24);
  --a3-dir-text-on-material: #FFFBF4;

  /* Cloth & Signal after dark. The same plum, green and rose, lifted for a deep plum ground. Measured by contrast.test.ts against the same pairs the light directions are held to. The rose moves furthest: lifted for a dark ground it crowded the same pink corner as danger, so it was pushed toward magenta until it clears the failure colour it must never be mistaken for. */
  --a3-dir-dark-bg: #150A18;
  --a3-dir-dark-bg-raised: #231029;
  --a3-dir-dark-bg-sunken: #0D050F;
  --a3-dir-dark-border: #3A2142;
  --a3-dir-dark-border-strong: #8C6E95;
  --a3-dir-dark-text: #F3E7EE;
  --a3-dir-dark-text-secondary: #CDB6D4;
  --a3-dir-dark-text-muted: #A88FB0;
  --a3-dir-dark-link: #4FD69C;
  --a3-dir-dark-focus: #4FD69C;
  --a3-dir-dark-primary-600: #3FC98F;
  --a3-dir-dark-primary-100: #0E2E22;
  --a3-dir-dark-success-600: #5CBCDC;
  --a3-dir-dark-success-100: #0B2733;
  --a3-dir-dark-settled-600: #B3A7B0;
  --a3-dir-dark-settled-100: #241F23;
  --a3-dir-dark-verified-600: #93A6EA;
  --a3-dir-dark-verified-100: #161B33;
  --a3-dir-dark-pending-600: #D6A64E;
  --a3-dir-dark-pending-100: #2C2213;
  --a3-dir-dark-warning-600: #E0904F;
  --a3-dir-dark-warning-100: #2F1E12;
  --a3-dir-dark-danger-600: #EE6F7E;
  --a3-dir-dark-danger-100: #31151A;
  --a3-dir-dark-denied-600: #BC9ED3;
  --a3-dir-dark-denied-100: #241B2B;
  --a3-dir-dark-identity: #EE7ACB;
  /* REPAIRED 2026-08-30. Both were the literal token `undefined`, which is a
     valid token stream, so the `var()` fallbacks in `tokens.css` never fired
     -- a fallback applies when a property is MISSING, not when it holds
     nonsense. The merchant and control authority bands lost their accent
     after dark, on the direction production actually renders. Lifted for
     this direction's plum ground rather than copied from A and B, whose
     values were measured against a different one. */
  --a3-dir-dark-accent-merchant: #8FBF6E;
  --a3-dir-dark-accent-control:  #C79BD8;

}

/* -----------------------------------------------------------------------------
   Direction D — "Storefront"
   Added 2026-08-30, from the Founder's mockups of the same week.

   The pitch is a merchant's own shop front rather than a market metaphor. Where
   Cloth & Signal grounds the page in warm cream, this one puts white cards on a
   quiet warm ground so a merchant's photograph is the brightest thing on the
   screen -- which is the point of the storefront hero the same mockups
   introduce, and of the Vision's "a business can have a place in the market".

   Three decisions worth naming, because a later reader will otherwise change
   them back:

   GREEN IS STILL ONLY FOR ACTION, and success is still not green. The mockups
   draw both the buttons and the reassurance in the same green; that is
   Collision 1 in `context/COLOUR_DECISION.md` and it is why success here is a
   blue-teal. A customer cannot be asked to tell "you may press this" from "this
   worked" by hue alone.

   MAGENTA IS THE MERCHANT'S SURFACE, not a second brand colour. The Founder's
   merchant mockups render the wordmark magenta and the customer ones green, and
   that reads as deliberate: knowing which surface you are standing on is an
   authorization fact, which is exactly what `--a3-surface-accent` already
   exists to carry. So it is set as an accent, not as a second identity.

   ROUNDER THAN ANY DIRECTION SO FAR -- 10, 16 and 24 against Cloth & Signal's
   8, 12 and 16, and Field's 0. It is the loudest single value here and the one
   that makes the mockups read as themselves.
----------------------------------------------------------------------------- */
:root[data-a3-direction="d"] {
  /* Ground and ink. A warm off-white ground with white cards on it, so a
     photograph is brighter than the page it sits on. */
  --a3-dir-bg:#FFFFFF;
  --a3-dir-bg-raised:#FFFFFF;
  --a3-dir-bg-sunken:#F4F4F2;
  --a3-dir-bg-inverse:#0E2A1C;
  --a3-dir-border:#E6E6E4;
  --a3-dir-border-strong:#6E7A72;
  --a3-dir-text:#131612;
  --a3-dir-text-secondary:#3E4A43;
  --a3-dir-text-muted:#6B766F;
  --a3-dir-text-inverse:   #FFFFFF;

  /* Action. The mockups' button green. */
  --a3-dir-primary-700:#01502B;
  --a3-dir-primary-600:#046738;
  --a3-dir-primary-100:#E8F2EB;
  --a3-dir-link:#046738;
  --a3-dir-focus:#046738;
  --a3-dir-text-on-action: #FFFFFF;

  /* Identity. Magenta, on the mark and on editorial emphasis. Never on state. */
  --a3-dir-identity:#A90E50;

  /* The surfaces.
     CORRECTED 2026-08-30. This said `#A90E50` -- the identity magenta -- three
     lines under a comment reserving that value for the mark and forbidding it
     on state. It was doing two jobs, which is the one thing the line above says
     it must not do, and it put a merchant's active tab in the brand colour so
     that "which tab am I on" and "whose product is this" were the same signal.

     The Founder's mockups settle it independently: every merchant page draws
     the active tab and the MERCHANT label in the working green, and uses the
     magenta only on the wordmark. So merchant takes the green.

     The cost, stated rather than discovered: the customer and merchant bands
     are now the same hue, and colour alone no longer says which surface you are
     on. It never carried that alone -- the surface is named in words beside it,
     which is what a person reads and what a screen reader announces. The
     control plane keeps its own violet, because that is the band where being
     somewhere different matters. */
  --a3-dir-accent-merchant:#046738;
  --a3-dir-accent-control:  #6B3F7A;

  /* The seven states. Success is blue-teal, for the reason in the header. */
  --a3-dir-success-600:    #0B6E8C;
  --a3-dir-success-100:    #E1EFF5;
  --a3-dir-settled-600:    #4E524F;
  --a3-dir-settled-100:    #EBEBE7;
  --a3-dir-verified-600:   #2A4FA8;
  --a3-dir-verified-100:   #E7EBF8;
  --a3-dir-pending-600:#A15C08;
  --a3-dir-pending-100:#FFF1CB;
  --a3-dir-warning-600:#9C4709;
  --a3-dir-warning-100:#FBE9D8;
  --a3-dir-danger-600:#8E1B12;
  --a3-dir-danger-100:#F7E4E1;
  --a3-dir-denied-600:     #6B4788;
  --a3-dir-denied-100:     #F0EAF6;

  /* Type. Onest and IBM Plex Mono, unchanged: `fonts.css` records that of forty
     faces measured only six can set a Ghanaian name, and a direction is not
     allowed to undo that by preferring a shape. */
  --a3-dir-font-body:      "Onest", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  /* A SERIF, changed 2026-08-31. This was Onest, the body face, so display and
     body were the same typeface at different sizes -- which is a direction
     declining to have a display face rather than choosing one. The Founder's
     storefront mockups set every shop name and product name in a serif, and it
     is the one thing that makes a shop page read as a shop rather than as an
     admin screen. EB Garamond is already delivered and licensed; nothing new
     is fetched. Body stays Onest. */
  --a3-dir-font-display:   "EB Garamond", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --a3-dir-font-numeric:   "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  /* Shape. The loudest value in the direction. */
  --a3-dir-radius-sm:      10px;
  --a3-dir-radius-md:      16px;
  --a3-dir-radius-lg:      24px;

  /* Materials, for a shop with no photograph. Deeper than Cloth & Signal's,
     because here they sit against white rather than cream and would otherwise
     wash out. Kente is woven from this direction's own magenta and green. */
  --a3-dir-material-cocoa:    linear-gradient(155deg, #A9694A, #4A2211);
  --a3-dir-material-kente:    linear-gradient(150deg, #A31E86 10%, #C8912F 55%, #0C6238);
  --a3-dir-material-shea:     linear-gradient(155deg, #EBDCBA, #B3925A);
  --a3-dir-material-pepper:   linear-gradient(155deg, #C4553F, #551611);
  --a3-dir-material-wood:     linear-gradient(155deg, #B48757, #4E3220);
  --a3-dir-material-market:   linear-gradient(150deg, #D6923A 6%, #A54C2B 50%, #2F1C16);
  --a3-dir-material-hands:    linear-gradient(150deg, #B8815E, #543020);
  --a3-dir-material-greenery: linear-gradient(155deg, #5C9061, #1A3A23);
  --a3-dir-text-on-material:  #FFFFFF;

  /* Storefront after dark. A near-black with a green cast rather than a plum
     one, so the same photographs sit on it. Every pair below is measured by
     `contrast.test.ts` against the thresholds the light directions are held to;
     the magenta is lifted furthest, because a dark ground pulls it toward the
     danger red it must never be mistaken for. */
  --a3-dir-dark-bg:             #0E1512;
  --a3-dir-dark-bg-raised:      #16211B;
  --a3-dir-dark-bg-sunken:      #080D0A;
  --a3-dir-dark-border:         #263329;
  --a3-dir-dark-border-strong:  #7A9084;
  --a3-dir-dark-text:           #E9F1EB;
  --a3-dir-dark-text-secondary: #BCCCC1;
  --a3-dir-dark-text-muted:     #93A69B;
  --a3-dir-dark-link:           #4FD69C;
  --a3-dir-dark-focus:          #4FD69C;
  --a3-dir-dark-primary-600:    #3FC98F;
  --a3-dir-dark-primary-100:    #0C2A1E;
  --a3-dir-dark-success-600:    #5CBCDC;
  --a3-dir-dark-success-100:    #0B2733;
  --a3-dir-dark-settled-600:    #B0B5B1;
  --a3-dir-dark-settled-100:    #1F2320;
  --a3-dir-dark-verified-600:   #93A6EA;
  --a3-dir-dark-verified-100:   #161B33;
  --a3-dir-dark-pending-600:    #D6A64E;
  --a3-dir-dark-pending-100:    #2C2213;
  --a3-dir-dark-warning-600:    #E0904F;
  --a3-dir-dark-warning-100:    #2F1E12;
  --a3-dir-dark-danger-600:     #EE7A70;
  --a3-dir-dark-danger-100:     #311714;
  --a3-dir-dark-denied-600:     #BC9ED3;
  --a3-dir-dark-denied-100:     #241B2B;
  --a3-dir-dark-identity:       #F286CD;
  /* Follows the light correction above: the merchant band is the working
     green, not the identity magenta. */
  --a3-dir-dark-accent-merchant: #3FC98F;
  --a3-dir-dark-accent-control:  #C79BD8;

  /* The mark on a rating pill. Gold in the Founder's mockups, measured off
     them at #FDCB5C; the other directions carry their own warm equivalent. */
  --a3-dir-rating-mark:    #FDCB5C;
}

/* -----------------------------------------------------------------------------
   Direction E. The adopted brand.

   Founder decision G37, 4 September 2026, on a complete design run:
   "I'm already seeing it as my unique brand for Asiam3... I like, don't change
   anything except where it conflicts with Vision. I really want exact."

   It supersedes COLOUR_DECISION.md of 13 August, which is kept and marked
   superseded rather than deleted.

   THE RULE THAT KEEPS IT SAFE, and it is a treatment rather than a hue.

     The action signal is the FILLED SHAPE, not the colour. A solid green fill
     with white text means "you may press this", and nothing else in Asiam3 may
     use that treatment. The wordmark is green text. A masthead is a green area.
     An active tab is a green underline. None of them is a filled control, so a
     person separates them by form before they separate them by colour.

   That is why success is a TINT with a mark rather than a fifth hue: a green
   panel carrying a check cannot be mistaken for a button, and giving success
   its own colour instead forces "refused" and "not permitted" to collapse
   together, which are genuinely different states.

   G40: row titles are brown, "Earth's colour". G44: black carries content and
   blue carries links, and blue does nothing else -- no colour may appear in two
   rows of the role table.

   -----------------------------------------------------------------------------
   PROVENANCE. Every value is either GIVEN by the adopted design or DERIVED to
   complete a pair the design did not supply, and each derived value says so.
   Nothing here is a preference.

   The given values are all from one scale, which is how the derivations are
   possible without inventing: #2563EB, #F59E0B, #DC2626, #7C3AED, #6B7280,
   #111827, #374151, #E5E7EB are a single published ramp, so a missing
   companion is read from the same ramp rather than mixed by eye.
   ----------------------------------------------------------------------------- */
:root[data-a3-direction="e"] {
  /* Grounds. GIVEN: the design is white-ground with a pale tinted page. */
  --a3-dir-bg:              #FFFFFF;
  --a3-dir-bg-raised:       #FFFFFF;
  --a3-dir-bg-sunken:       #F9FAFB;
  --a3-dir-bg-inverse:      #0B0F14;

  /* Rules. border GIVEN. border-strong DERIVED one step up the same ramp:
     it carries empty-state borders, neutral status and card hover, so it is
     a stronger GREY and never the brand green. */
  --a3-dir-border:          #E5E7EB;
  --a3-dir-border-strong:   #6B7280;

  /* Ink. All GIVEN, and this is G44: black carries content. */
  --a3-dir-text:            #111827;
  --a3-dir-text-secondary:  #374151;
  --a3-dir-text-muted:      #6B7280;
  --a3-dir-text-inverse:    #FFFFFF;

  /* Action. 600 GIVEN. 700 is HOVER in components.css, DERIVED darker along
     the same hue; the pressed state is brightness(0.94) on top of it, which is
     why pressed reads darkest without a token of its own. 100 GIVEN as the
     soft brand ground. */
  --a3-dir-primary-600:     #0A7B4F;
  --a3-dir-primary-700:     #076039;
  --a3-dir-primary-100:     #E8F5ED;
  --a3-dir-text-on-action:  #FFFFFF;

  /* Links. GIVEN, and G44: blue carries links and nothing else. */
  --a3-dir-link:            #2563EB;
  --a3-dir-focus:           #0A7B4F;

  /* Identity is the same green, in text rather than in a fill. G37. */
  --a3-dir-identity:        #0A7B4F;

  /* Surface accents. accent-merchant GIVEN by the design, which renders the
     merchant surface in the same green as the customer one. accent-control
     DERIVED, and kept clear of the denied violet so a control-plane page is
     never mistaken for a refusal.

     WORTH KNOWING: Direction D used magenta for the merchant surface
     deliberately, because knowing which surface you stand on is an
     authorisation fact. The adopted design does not make that distinction.
     Recorded here rather than lost. */
  --a3-dir-accent-merchant: #0A7B4F;
  --a3-dir-accent-control:  #4338CA;

  /* Success is a TINT with a mark. 100 GIVEN as #DCF7EA. 600 is the mark and
     the words on it, and it is the brand green -- which is safe precisely
     because a tinted panel is not a filled control. */
  --a3-dir-success-600:     #0A7B4F;
  --a3-dir-success-100:     #DCF7EA;

  /* Unavailable. 600 GIVEN. 100 DERIVED from the same ramp. */
  --a3-dir-settled-600:     #4B5563;
  --a3-dir-settled-100:     #F3F4F6;

  /* Information. 600 GIVEN. 100 DERIVED. */
  --a3-dir-verified-600:    #1D4ED8;
  --a3-dir-verified-100:    #DBEAFE;

  /* Pending. 600 GIVEN. 100 DERIVED. */
  --a3-dir-pending-600:     #92400E;
  --a3-dir-pending-100:     #FEF3C7;

  /* Warning. BOTH DERIVED: the light sheet carries no warning swatch, and the
     dark one does. Taken from the same ramp, one step warmer than pending so
     the two are separable. */
  --a3-dir-warning-600:     #9A3412;
  --a3-dir-warning-100:     #FFEDD5;

  /* Refused. 600 GIVEN. 100 DERIVED. */
  --a3-dir-danger-600:      #B91C1C;
  --a3-dir-danger-100:      #FEE2E2;

  /* Not permitted, which is never drawn as an error. 600 GIVEN. 100 DERIVED. */
  --a3-dir-denied-600:      #7C3AED;
  --a3-dir-denied-100:      #EDE9FE;

  /* Type. The design sets everything in one sans and uses no serif, so display
     and body are the same family.

     ONEST RATHER THAN THE FACE THE DESIGN WAS DRAWN IN, and this is not a
     preference either. Forty faces were measured against the Ghanaian
     characters and six passed; fonts.css records which and why. A typeface
     that cannot set a merchant's name is not a candidate whatever it looks
     like. */
  --a3-dir-font-body:       "Onest", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --a3-dir-font-display:    "Onest", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --a3-dir-font-numeric:    "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  /* Shape. DERIVED by measuring the design's cards, inputs and buttons. */
  --a3-dir-radius-sm:       8px;
  --a3-dir-radius-md:       12px;
  --a3-dir-radius-lg:       16px;

  /* Materials stand where a merchant has uploaded no photograph. Carried from
     Direction D unchanged: the adopted design shows placeholder tiles and does
     not specify these, and inventing eight gradients would be a preference
     rather than a reading. */
  --a3-dir-material-cocoa:    linear-gradient(155deg, #F1E7E0, #DCC9BC);
  --a3-dir-material-kente:    linear-gradient(150deg, #EDE9FE 10%, #FEF3C7 55%, #DCF7EA);
  --a3-dir-material-shea:     linear-gradient(155deg, #FBF6EA, #EFE2C6);
  --a3-dir-material-pepper:   linear-gradient(155deg, #FBE7E3, #F3CFC7);
  --a3-dir-material-wood:     linear-gradient(155deg, #F3EAE0, #E0CDB6);
  --a3-dir-material-market:   linear-gradient(150deg, #FEF3C7 6%, #FBE7E3 50%, #E8F5ED);
  --a3-dir-material-hands:    linear-gradient(150deg, #F4EAE4, #E3CDC0);
  --a3-dir-material-greenery: linear-gradient(155deg, #E8F5ED, #C9E9D6);
  --a3-dir-text-on-material:  #111827;

  /* The rating star. GIVEN by the design's amber mark. */
  --a3-dir-rating-mark:     #F59E0B;

  /* -----------------------------------------------------------------------
     DARK.

     Every value below is GIVEN by the adopted dark palette except where it
     says otherwise, and it is NOT an inversion: the action green LIFTS from
     #0A7B4F to #10B981 and the link from #2563EB to #38BDF8, because a colour
     chosen against white does not survive a dark ground.

     The dark sheet supplies the PANELS, which are the 100s. The 600s are the
     mark and the words on them and are DERIVED one ramp step lighter, since a
     panel colour cannot also be the text on itself.
     ----------------------------------------------------------------------- */
  --a3-dir-dark-bg:             #0B0F14;
  --a3-dir-dark-bg-raised:      #111827;
  --a3-dir-dark-bg-sunken:      #0F172A;
  --a3-dir-dark-border:         #1F2937;
  --a3-dir-dark-border-strong:  #4B5563;
  --a3-dir-dark-text:           #F9FAFB;
  --a3-dir-dark-text-secondary: #D1D5DB;
  --a3-dir-dark-text-muted:     #94A3B8;
  --a3-dir-dark-link:           #38BDF8;
  --a3-dir-dark-focus:          #10B981;
  --a3-dir-dark-primary-600:    #10B981;
  --a3-dir-dark-primary-100:    #064E3B;
  --a3-dir-dark-success-600:    #34D399;
  --a3-dir-dark-success-100:    #065F46;
  --a3-dir-dark-settled-600:    #9CA3AF;
  --a3-dir-dark-settled-100:    #374151;
  --a3-dir-dark-verified-600:   #60A5FA;
  --a3-dir-dark-verified-100:   #1E3A8A;
  --a3-dir-dark-pending-600:    #FBBF24;
  --a3-dir-dark-pending-100:    #78350F;
  --a3-dir-dark-warning-600:    #FB923C;
  --a3-dir-dark-warning-100:    #92400E;
  --a3-dir-dark-danger-600:     #F87171;
  --a3-dir-dark-danger-100:     #7F1D1D;
  --a3-dir-dark-denied-600:     #A78BFA;
  --a3-dir-dark-denied-100:     #4C1D95;
  --a3-dir-dark-identity:       #10B981;
  --a3-dir-dark-accent-merchant: #10B981;
  --a3-dir-dark-accent-control:  #818CF8;
}

/* tokens.css */
/* =============================================================================
   Asiam3 design tokens
   =============================================================================

   Plain CSS custom properties. No framework, no build step, no vendor.

   This is deliberate. The frontend framework is an open decision, so the design
   language is expressed in the one layer every candidate can consume. A token
   file that required a specific toolchain would decide D4 by accident, and the
   design language belongs to Asiam3 rather than to whatever renders it.

   -----------------------------------------------------------------------------
   The point of view
   -----------------------------------------------------------------------------

   Asiam3 sells trust to people spending real money with small businesses, often
   on inexpensive phones over uneven connections. The memorable quality of this
   interface should not be an aesthetic flourish. It should be that **you always
   know exactly what is true**: whose data you are looking at, by what right, and
   what state the money is in.

   So the signature element is structural rather than decorative: a persistent
   authority band that states whose data is on screen and under what authorisation.
   It is a security control expressed as design, and it is the one thing that
   would be missing if someone copied this system without understanding it.

   -----------------------------------------------------------------------------
   The colour decision that matters most
   -----------------------------------------------------------------------------

   Most design systems collapse every positive state into one green. Asiam3 cannot.

   The Constitution says verification claims something narrow: that credentials
   were checked, in a stated scope, at a stated time. It does not claim the
   business is good, safe or guaranteed. The Vision is blunter: a badge that
   promises more than it can support is worse than no badge.

   If "verified" is rendered in the same celebratory green as "payment received"
   and "saved successfully", the interface promises more than the platform can
   support, and it does so in a way no legal disclaimer undoes. So verification
   gets its own restrained, cooler mark, distinct from success and distinct from
   settled money. Three meanings, three treatments.

   The same reasoning separates denied from error from conflict. To a user those
   are different situations with different next actions, and to an attacker the
   difference between them is information. They are never rendered alike.
============================================================================= */

/* -----------------------------------------------------------------------------
   Where the values come from
   -----------------------------------------------------------------------------

   The tokens a visual direction owns read `var(--a3-dir-X, <original value>)`.
   `directions.css` supplies `--a3-dir-X`; the fallback is what Asiam3 rendered
   before directions existed, so a page with no direction attribute is unchanged
   and nothing depends on that file being loaded.

   The dark theme below redefines the semantic tokens outright rather than
   supplying a direction value, so it overrides the whole expression. That keeps
   the theme direction-agnostic, which is the current deliberate position.

   Components consume the semantic names only. None of them mentions a
   direction, and a guard fails if one starts to.
----------------------------------------------------------------------------- */

:root {
  /* ---------------------------------------------------------------------------
     Palette
     Ink-and-instrument. A cool near-neutral ground that reads as a working tool
     rather than a brochure, with a single deep primary carrying authority.
     Deliberately not the warm-cream-and-terracotta or near-black-and-acid-accent
     that generated interfaces default to, and deliberately not a literal
     national palette, which would be a costume rather than a decision.
  --------------------------------------------------------------------------- */
  --a3-ink-900: #0e1a24;
  --a3-ink-800: #16242f;
  --a3-ink-700: #26333d;
  --a3-ink-600: #43525e;
  --a3-ink-500: #64737f;
  --a3-ink-400: #8e9aa4;
  --a3-ink-300: #b9c2c9;
  --a3-ink-200: #dbe1e5;
  --a3-ink-100: #eef1f3;
  --a3-ink-050: #f7f9fa;
  --a3-white:   #ffffff;

  /* Primary: deep teal-blue. Authority without corporate navy blandness. */
  --a3-primary-700: var(--a3-dir-primary-700, #0a4f5c);
  --a3-primary-600: var(--a3-dir-primary-600, #0d6274);
  --a3-primary-500: #10788d;
  --a3-primary-300: #7fb9c5;
  --a3-primary-100: var(--a3-dir-primary-100, #e0eff2);

  /* Semantic states. Each one means a different thing and looks different.
     A direction may restate these; it may never collapse two of them. */
  --a3-success-600: var(--a3-dir-success-600, #1d7a4a);   /* an action completed */
  --a3-success-100: var(--a3-dir-success-100, #e3f4ea);
  --a3-settled-600: var(--a3-dir-settled-600, #145c73);   /* money has settled. Sober, not celebratory */
  --a3-settled-100: var(--a3-dir-settled-100, #e2eff4);
  --a3-verified-600: var(--a3-dir-verified-600, #4a5fa8);  /* credentials checked, in scope, at a time */
  --a3-verified-100: var(--a3-dir-verified-100, #e9ecf8);
  --a3-pending-600: var(--a3-dir-pending-600, #8a6216);   /* awaiting something outside our control */
  --a3-pending-100: var(--a3-dir-pending-100, #fbf1dc);
  --a3-warning-600: var(--a3-dir-warning-600, #a35a12);
  --a3-warning-100: var(--a3-dir-warning-100, #fdeee0);
  --a3-danger-600:  var(--a3-dir-danger-600, #a32020);   /* destructive or failed */
  --a3-danger-100:  var(--a3-dir-danger-100, #fbe6e6);
  --a3-denied-600:  var(--a3-dir-denied-600, #5f4470);   /* not permitted. Never confused with an error */
  --a3-denied-100:  var(--a3-dir-denied-100, #f1ecf5);

  /* ---------------------------------------------------------------------------
     Semantic surfaces
     Components reference these, never raw palette values, so a theme change is
     a token change rather than an audit of every component.
  --------------------------------------------------------------------------- */
  --a3-bg:              var(--a3-dir-bg, var(--a3-ink-050));
  --a3-bg-raised:       var(--a3-dir-bg-raised, var(--a3-white));
  --a3-bg-sunken:       var(--a3-dir-bg-sunken, var(--a3-ink-100));
  --a3-bg-inverse:      var(--a3-dir-bg-inverse, var(--a3-ink-900));
  --a3-border:          var(--a3-dir-border, var(--a3-ink-200));
  --a3-border-strong:   var(--a3-dir-border-strong, var(--a3-ink-300));
  --a3-text:            var(--a3-dir-text, var(--a3-ink-900));
  --a3-text-secondary:  var(--a3-dir-text-secondary, var(--a3-ink-600));
  --a3-text-muted:      var(--a3-dir-text-muted, var(--a3-ink-500));
  --a3-text-inverse:    var(--a3-dir-text-inverse, var(--a3-white));
  --a3-link:            var(--a3-dir-link, var(--a3-primary-600));
  --a3-focus:           var(--a3-dir-focus, #1a8fb0);
  /* The ink on the action colour. Separate from --a3-text-inverse, which is the
     ink on the inverse *ground*: the two happen to be near-white in both
     directions today, and merging them would mean a direction cannot darken one
     without darkening the other. Measured against the action colour by the
     contrast guard in both directions. */
  --a3-text-on-action:  var(--a3-dir-text-on-action, var(--a3-white));
  /* The ink on a material placeholder. A material is a dark woven field in
     every theme, so this one is **not** redefined by the dark theme: doing so
     would put near-black lettering on a dark cocoa ground the moment a
     visitor's phone is set to dark. */
  --a3-text-on-material: var(--a3-dir-text-on-material, var(--a3-white));
  /* Asiam3's own mark and editorial emphasis. Never a state: it sits close
     enough to the danger red that a badge drawn in it would read as a failure,
     so it appears on the wordmark and on emphasis and nowhere else. */
  --a3-identity:        var(--a3-dir-identity, var(--a3-primary-600));
  /* The surface accents, now read from the direction rather than hardcoded at
     the point of use. Added 2026-08-30: the dark theme had taken these from a
     direction since 14 August, and the light one never had -- so a merchant
     surface was the same olive in every visual language by day, and correctly
     themed by night. The fallbacks are the literals that used to sit on the
     selectors below, so a direction that declares nothing is unchanged. */
  --a3-rating-mark:     var(--a3-dir-rating-mark, #C9922E);
  --a3-accent-merchant: var(--a3-dir-accent-merchant, #4a6b2f);
  --a3-accent-control:  var(--a3-dir-accent-control, #6b3f7a);

  /* ---------------------------------------------------------------------------
     Type
     Roles, not faces. The specific families are a token so they can change
     without touching a component.

     Body is a humanist sans at a size that survives a cheap Android screen in
     daylight. Numeric is a separate role with tabular figures, because money in
     a column that does not align is money a merchant cannot scan, and
     misreading an amount is the most expensive typographic failure here.
  --------------------------------------------------------------------------- */
  --a3-font-body: var(--a3-dir-font-body, "Inter", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif);
  --a3-font-display: var(--a3-dir-font-display, "Inter Tight", "Inter", "Segoe UI", system-ui, sans-serif);
  --a3-font-numeric: var(--a3-dir-font-numeric, "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace);

  --a3-text-2xs: 0.6875rem; /* 11px. Metadata only. Never an instruction */
  --a3-text-xs:  0.75rem;
  --a3-text-sm:  0.8125rem;
  --a3-text-md:  0.9375rem; /* 15px body. 16px on touch, see responsive rules */
  --a3-text-lg:  1.0625rem;
  --a3-text-xl:  1.3125rem;
  --a3-text-2xl: 1.625rem;
  --a3-text-3xl: 2.125rem;

  --a3-weight-regular: 400;
  --a3-weight-medium: 500;
  --a3-weight-semibold: 600;

  --a3-leading-tight: 1.25;
  --a3-leading-normal: 1.5;
  --a3-leading-relaxed: 1.65;

  /* Measure. Prose beyond this is measurably harder to read. */
  --a3-measure: 68ch;

  /* ---------------------------------------------------------------------------
     Space. A 4px base, because a commerce back office needs a denser rhythm
     than an 8px-only scale allows without everything becoming a compromise.
  --------------------------------------------------------------------------- */
  --a3-space-1: 0.25rem;
  --a3-space-2: 0.5rem;
  --a3-space-3: 0.75rem;
  --a3-space-4: 1rem;
  --a3-space-5: 1.5rem;
  --a3-space-6: 2rem;
  --a3-space-7: 3rem;
  --a3-space-8: 4rem;

  /* ---------------------------------------------------------------------------
     Density
     One system, two densities. A customer reading one order and a merchant
     scanning two hundred are not the same task, and forcing both into a single
     rhythm makes one of them worse. Set --a3-density-row on a container.
  --------------------------------------------------------------------------- */
  --a3-density-row: 2.75rem;        /* comfortable, default */
  --a3-density-row-compact: 2.125rem;

  /* Shape. Restrained. A commerce console is not a consumer app. */
  --a3-radius-sm: var(--a3-dir-radius-sm, 3px);
  --a3-radius-md: var(--a3-dir-radius-md, 5px);
  --a3-radius-lg: var(--a3-dir-radius-lg, 8px);

  /* Materials, for anything without a photograph. The fallback is the sunken
     ground, so a page with no direction shows a plain box rather than nothing. */
  --a3-material-cocoa:    var(--a3-dir-material-cocoa, var(--a3-bg-sunken));
  --a3-material-kente:    var(--a3-dir-material-kente, var(--a3-bg-sunken));
  --a3-material-shea:     var(--a3-dir-material-shea, var(--a3-bg-sunken));
  --a3-material-pepper:   var(--a3-dir-material-pepper, var(--a3-bg-sunken));
  --a3-material-wood:     var(--a3-dir-material-wood, var(--a3-bg-sunken));
  --a3-material-market:   var(--a3-dir-material-market, var(--a3-bg-sunken));
  --a3-material-hands:    var(--a3-dir-material-hands, var(--a3-bg-sunken));
  --a3-material-greenery: var(--a3-dir-material-greenery, var(--a3-bg-sunken));

  /* Elevation, used sparingly. Depth should mean "this is above the page",
     not "this is important". */
  /* A SCRIM, for words over a photograph.

     Declared rather than written inline because a component may hold no
     colour of its own, and because the strength is a decision: a photograph
     guarantees no contrast, so the darkness under a caption is drawn rather
     than borrowed from whichever image is in the slot. */
  --a3-scrim-strong: rgba(14, 26, 36, 0.62);
  --a3-scrim-soft:   rgba(14, 26, 36, 0.28);
  --a3-scrim-clear:  rgba(14, 26, 36, 0);

  --a3-shadow-raised: 0 1px 2px rgba(14, 26, 36, 0.06), 0 1px 3px rgba(14, 26, 36, 0.08);
  --a3-shadow-overlay: 0 8px 24px rgba(14, 26, 36, 0.16), 0 2px 6px rgba(14, 26, 36, 0.08);

  /* Focus. Never removed, never subtle. Two-tone so it survives any background. */
  --a3-focus-ring: 0 0 0 2px var(--a3-bg-raised), 0 0 0 4px var(--a3-focus);

  /* Touch, and there are TWO floors rather than one.

     ACCESSIBILITY.md section 5 states them separately: "Buttons, inputs,
     selects and the skip link meet 44px. The wordmark and links inside a
     sentence are 24px or a little more, which is WCAG 2.2 AA's minimum and its
     inline exception."

     Only the first was ever a token, so the second was a sentence in a document
     with nothing holding it. On 2026-09-02 the footer links measured 20px --
     under the 24 the document claimed, and not covered by the inline exception
     either, because that exempts a target inside a SENTENCE and the footer is a
     row of separate links. Naming the second floor is what stops it drifting
     again. */
  --a3-touch-min: 44px;
  --a3-target-inline-min: 24px;

  --a3-duration-fast: 120ms;
  --a3-duration-normal: 200ms;
  --a3-ease: cubic-bezier(0.2, 0, 0.2, 1);

  --a3-z-sticky: 100;
  --a3-z-overlay: 500;
  --a3-z-toast: 900;
}

/* -----------------------------------------------------------------------------
   Dark theme
   Redefines tokens only. No component knows a theme exists.
   Both an explicit choice and the system default are honoured.
----------------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-a3-theme="light"]) {
    --a3-bg:             var(--a3-dir-dark-bg, #0b1319);
    --a3-bg-raised:      var(--a3-dir-dark-bg-raised, #131f27);
    --a3-bg-sunken:      var(--a3-dir-dark-bg-sunken, #091015);
    --a3-bg-inverse:     var(--a3-ink-100);
    --a3-border:         var(--a3-dir-dark-border, #24343f);
    --a3-border-strong:  var(--a3-dir-dark-border-strong, #5A7382);
    --a3-text:           var(--a3-dir-dark-text, #e8edf0);
    --a3-text-secondary: var(--a3-dir-dark-text-secondary, #a9b6c0);
    --a3-text-muted:     var(--a3-dir-dark-text-muted, #7e8d99);
    --a3-text-inverse:   var(--a3-ink-900);
    --a3-link:           var(--a3-dir-dark-link, #63b8cc);
    --a3-focus:          var(--a3-dir-dark-focus, #4fc3e0);

    --a3-primary-600: var(--a3-dir-dark-primary-600, #2b93ab);

    /* Dark ink on the lighter dark-theme action colour. The near-white this

       replaced measured 3.52 against it and needed 4.5. */

    --a3-text-on-action: var(--a3-ink-900);
    --a3-primary-100: var(--a3-dir-dark-primary-100, #10303a);
    --a3-success-600: var(--a3-dir-dark-success-600, #46b07a); --a3-success-100: var(--a3-dir-dark-success-100, #0f2b1f);
    --a3-settled-600: var(--a3-dir-dark-settled-600, #4aa7c4); --a3-settled-100: var(--a3-dir-dark-settled-100, #0d2731);
    --a3-verified-600: var(--a3-dir-dark-verified-600, #8f9fd9); --a3-verified-100: var(--a3-dir-dark-verified-100, #1a1f38);
    --a3-pending-600: var(--a3-dir-dark-pending-600, #d0a047); --a3-pending-100: var(--a3-dir-dark-pending-100, #2c2313);
    --a3-warning-600: var(--a3-dir-dark-warning-600, #d38a45); --a3-warning-100: var(--a3-dir-dark-warning-100, #2e1e11);
    --a3-danger-600:  var(--a3-dir-dark-danger-600, #e06666); --a3-danger-100:  var(--a3-dir-dark-danger-100, #2e1414);
    --a3-denied-600:  var(--a3-dir-dark-denied-600, #a48cb8); --a3-denied-100:  var(--a3-dir-dark-denied-100, #221a29);
    /* Identity and the surface accents, corrected 2026-08-14 after rendering
       them. Both were defined once, for the light ground, and inherited
       straight into the dark theme, where the wordmark measured 2.74 and the
       control plane's own label measured 2.09. The hue is kept and the
       lightness lifted until each clears 4.5 on the raised dark ground. */
    --a3-identity: var(--a3-dir-dark-identity, #D9694F);
    --a3-accent-merchant: var(--a3-dir-dark-accent-merchant, #6d9648);
    --a3-accent-control: var(--a3-dir-dark-accent-control, #AC80BD);
  }
}

/* -----------------------------------------------------------------------------
   How a direction reaches the dark theme
   -----------------------------------------------------------------------------

   Added 2026-08-26, and the first version of it was wrong in a way worth
   recording. Cloth & Signal's dark ground was written here as
   a selector naming the direction and the theme together, which renders correctly
   and breaks the rule the whole arrangement rests on: **a direction selector
   appears in directions.css and nowhere else.** A guard caught it. A component
   -- or a theme -- that knows which direction it is in is no longer swappable,
   and "it was only a colour scheme" is exactly how that rule would erode.

   So the indirection runs the same way everything else does. Each dark value
   reads `var(--a3-dir-dark-*, <the value the theme had before directions carried
   one>)`. A direction that declares nothing gets the original dark theme, which
   is what Market Hall and Field should have until somebody designs theirs.
   Nothing here knows which direction is active, and the fallback means the dark
   theme still works on a page with no direction attribute at all.
----------------------------------------------------------------------------- */
:root[data-a3-theme="dark"] {
  --a3-bg:             var(--a3-dir-dark-bg, #0b1319);
  --a3-bg-raised:      var(--a3-dir-dark-bg-raised, #131f27);
  --a3-bg-sunken:      var(--a3-dir-dark-bg-sunken, #091015);
  --a3-bg-inverse:     var(--a3-ink-100);
  --a3-border:         var(--a3-dir-dark-border, #24343f);
  --a3-border-strong:  var(--a3-dir-dark-border-strong, #5A7382);
  --a3-text:           var(--a3-dir-dark-text, #e8edf0);
  --a3-text-secondary: var(--a3-dir-dark-text-secondary, #a9b6c0);
  --a3-text-muted:     var(--a3-dir-dark-text-muted, #7e8d99);
  --a3-text-inverse:   var(--a3-ink-900);
  --a3-link:           var(--a3-dir-dark-link, #63b8cc);
  --a3-focus:          var(--a3-dir-dark-focus, #4fc3e0);

  --a3-primary-600: var(--a3-dir-dark-primary-600, #2b93ab);

  /* Dark ink on the lighter dark-theme action colour. The near-white this

     replaced measured 3.52 against it and needed 4.5. */

  --a3-text-on-action: var(--a3-ink-900);
  --a3-primary-100: var(--a3-dir-dark-primary-100, #10303a);
  --a3-success-600: var(--a3-dir-dark-success-600, #46b07a); --a3-success-100: var(--a3-dir-dark-success-100, #0f2b1f);
  --a3-settled-600: var(--a3-dir-dark-settled-600, #4aa7c4); --a3-settled-100: var(--a3-dir-dark-settled-100, #0d2731);
  --a3-verified-600: var(--a3-dir-dark-verified-600, #8f9fd9); --a3-verified-100: var(--a3-dir-dark-verified-100, #1a1f38);
  --a3-pending-600: var(--a3-dir-dark-pending-600, #d0a047); --a3-pending-100: var(--a3-dir-dark-pending-100, #2c2313);
  --a3-warning-600: var(--a3-dir-dark-warning-600, #d38a45); --a3-warning-100: var(--a3-dir-dark-warning-100, #2e1e11);
  --a3-danger-600:  var(--a3-dir-dark-danger-600, #e06666); --a3-danger-100:  var(--a3-dir-dark-danger-100, #2e1414);
  --a3-denied-600:  var(--a3-dir-dark-denied-600, #a48cb8); --a3-denied-100:  var(--a3-dir-dark-denied-100, #221a29);
  /* Identity and the surface accents, corrected 2026-08-14 after rendering
     them. Both were defined once, for the light ground, and inherited
     straight into the dark theme, where the wordmark measured 2.74 and the
     control plane's own label measured 2.09. The hue is kept and the
     lightness lifted until each clears 4.5 on the raised dark ground. */
  --a3-identity: var(--a3-dir-dark-identity, #D9694F);
  --a3-accent-merchant: var(--a3-dir-dark-accent-merchant, #6d9648);
  --a3-accent-control: var(--a3-dir-dark-accent-control, #AC80BD);
}

/* -----------------------------------------------------------------------------
   Surface accents
   The three surfaces are visually distinguishable at a glance, because knowing
   which one you are in is an authorization fact, not a preference. Customer,
   merchant and control plane carry different authority; an operator who thinks
   they are in one while acting in another is the mistake this prevents.
   Applied by the authority band, never by restyling components.
----------------------------------------------------------------------------- */
[data-a3-surface="customer"] { --a3-surface-accent: var(--a3-primary-600); }
[data-a3-surface="merchant"] { --a3-surface-accent: var(--a3-accent-merchant, #4a6b2f); }
[data-a3-surface="control"]  { --a3-surface-accent: var(--a3-accent-control, #6b3f7a); }

/* -----------------------------------------------------------------------------
   Motion
   Honoured, not decorated. Someone who has asked their operating system to
   reduce motion has asked for a reason.
----------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  :root {
    --a3-duration-fast: 1ms;
    --a3-duration-normal: 1ms;
  }
}

/* -----------------------------------------------------------------------------
   Icon sizing
   Added 2026-08-30 with the icon set. Sizes are tokens rather than numbers on
   each glyph, because a set drawn at 20, 24 and 28 by whoever added the last one
   reads as three sets. `icons.ts` draws every glyph on one 24 grid and scales it
   with these.
----------------------------------------------------------------------------- */
:root {
  --a3-icon-sm: 1rem;
  --a3-icon-md: 1.25rem;
  --a3-icon-lg: 1.5rem;
}

/* components.css */
/* =============================================================================
   Asiam3 components
   =============================================================================
   Semantic HTML plus classes. No framework. Every component is a contract that
   any renderer can satisfy, because the frontend framework is still open.

   Rules that hold throughout:
   - Every colour, size and space comes from a token. No literals below.
   - Focus is always visible. Never `outline: none` without a replacement.
   - Every control a finger presses is at least --a3-touch-min: buttons, inputs,
     selects, the skip link. **Not** the wordmark and not links inside a
     sentence, which are 24px or a little more. Measured on a rendered page in
     Milestone 9, where this line used to read "anything a finger touches" and
     was not true of either. WCAG 2.2 AA asks for 24 by 24 and exempts a link
     in a line of text, so both are compliant; the sentence was wrong, not the
     components. Whether the wordmark should be a 44px target anyway is a
     design question and is recorded as one.
   - State is conveyed by more than colour. Colour vision is not a requirement
     for using a commerce platform.
============================================================================= */

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

/* ---------------------------------------------------------------------------
   HIDDEN MEANS HIDDEN.

   The browser hides `[hidden]` with `display: none` from its own stylesheet,
   which any class here beats simply by stating a display of its own. So a
   `<p class="a3-field__error" hidden>` was NOT hidden: `.a3-field__error` says
   `display: flex`, and flex wins.

   FOUND ON A RENDERED PAGE, 9 September 2026, and it had been live. The shop
   settings page drew, on load, with nothing chosen and nothing failed: an
   empty progress bar, "Sending your picture...", "Your picture did not finish
   uploading" and "Choose a picture under 4 MB" -- twice over, once for the
   logo and once for the cover. The product photograph page did the same. A
   merchant met a form already reporting two failures before touching it.

   Nothing could have caught it by reading. Every one of those elements has
   the `hidden` attribute in the markup, so a guard over the source sees a
   hidden element, and so did every reviewer. `crossDevice.test.ts` renders
   pages and would not have caught it either: it reads markup, and the markup
   was right. It took a photograph.

   `!important` deliberately, and it is the narrowest fix rather than the
   laziest. The alternative is `:not([hidden])` on all 191 class rules that
   state a display, which is a rule in 191 places -- Article 18 -- and would
   be wrong again the first time somebody adds the 192nd. `hidden` is not a
   style. It is the platform saying the element is not in the document for
   the user, and nothing in a component stylesheet has standing to disagree.
   --------------------------------------------------------------------------- */
[hidden] { display: none !important; }

body.a3 {
  margin: 0;
  background: var(--a3-bg);
  color: var(--a3-text);
  font-family: var(--a3-font-body);
  font-size: var(--a3-text-md);
  line-height: var(--a3-leading-normal);
  -webkit-text-size-adjust: 100%;
}

/* Touch devices get a larger base. 15px is comfortable with a mouse and
   marginal with a thumb, and iOS zooms any input below 16px on focus. */
@media (pointer: coarse) {
  body.a3 { font-size: 1rem; }
}

.a3-prose { max-width: var(--a3-measure); }

/* A LINK ALONE IN A PARAGRAPH IS NOT A LINK IN A SENTENCE.

   WCAG 2.2 SC 2.5.8 excepts a target "in a sentence or block of text", because
   the line height there belongs to the prose around it. Nothing runs through
   this one: the terms page carries `<p class="a3-prose"><a>Read the Privacy
   Notice</a></p>`, a standalone destination on a line of its own, and it
   rendered at 20px against the 24px floor.

   The same reading corrected the footer on 2026-09-02, which had the same shape
   and the same excuse. ACCESSIBILITY.md section on target sizes states the rule;
   this applies it where the exception genuinely does not reach. */
.a3-prose > a:only-child {
  display: inline-flex;
  align-items: center;
  min-height: var(--a3-target-inline-min);
}

:where(a) { color: var(--a3-link); text-underline-offset: 0.2em; }

:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--a3-focus-ring);
  border-radius: var(--a3-radius-sm);
}

/* Reachable only by keyboard, which is exactly who needs it. */
.a3-skip-link {
  position: absolute; left: var(--a3-space-2); top: -100vh;
  z-index: var(--a3-z-toast);
  background: var(--a3-bg-raised); color: var(--a3-text);
  padding: var(--a3-space-2) var(--a3-space-4);
  border: 1px solid var(--a3-border-strong); border-radius: var(--a3-radius-md);
}
.a3-skip-link:focus { top: var(--a3-space-2); }
/* Measured at 42px against a stated minimum of 44. Invisible until focused, so
   there is no visual cost to keeping the rule the file claims to keep. */
.a3-skip-link { min-height: var(--a3-touch-min); display: inline-flex; align-items: center; }

.a3-visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* -----------------------------------------------------------------------------
   Authority band: the signature element
   -----------------------------------------------------------------------------
   Persistent. States which surface you are in, whose data is on screen, and by
   what right. It is the answer to "am I about to act on the wrong business?"
   before the action rather than after it.

   The three surfaces are distinguishable at a glance because confusing them is
   an authorization error, not a cosmetic one. When authority is borrowed rather
   than owned, the band says so and shows the recorded reason, so a support
   operator can never forget they are inside someone else's business.
----------------------------------------------------------------------------- */
.a3-authority {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--a3-space-3);
  padding: var(--a3-space-2) var(--a3-space-4);
  background: var(--a3-bg-raised);
  border-bottom: 1px solid var(--a3-border);
  border-top: 3px solid var(--a3-surface-accent, var(--a3-primary-600));
  font-size: var(--a3-text-sm);
  position: sticky; top: 0; z-index: var(--a3-z-sticky);
}
.a3-authority__surface {
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-surface-accent, var(--a3-primary-600));
  text-transform: uppercase; letter-spacing: 0.06em;
  font-size: var(--a3-text-2xs);
}
.a3-authority__scope {
  color: var(--a3-text); font-weight: var(--a3-weight-medium);
  /* The scope is the page's title, and on a search that title contains the
     customer's own words. The band is on every page and is sticky, so when it
     grew wider than the screen it took every page with it. */
  min-width: 0; overflow-wrap: anywhere;
}
.a3-authority__basis { color: var(--a3-text-muted); }

/* -----------------------------------------------------------------------------
   THE MERCHANT'S SECOND BAR

   The mockups draw the merchant chrome as two rows: the mark and the surface
   on top, then the section a merchant is in on the left and how they are
   signed in on the right. Every other surface keeps the single row it has.

   Done by MOVING the existing parts, not by rendering a second band. The
   scope already IS the section and the basis already IS the sign-in state,
   and both are already correct -- so this decides where they sit, not what
   they say. A second band would have been two places to keep in step.

   A grid, placed explicitly by row and column. Two earlier attempts used a
   wrapping flex row and put the section in the wrong place twice: a layout
   whose correctness depends on how a browser resolves a zero-height
   full-width flex item, or on counting children in document order, is one
   nobody can reason about later and one that breaks when somebody adds a
   child. These four rules say where each part goes and can be read off.
   -------------------------------------------------------------------------- */
[data-a3-surface="merchant"].a3-authority {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "mark  surface"
    "title basis";
  row-gap: var(--a3-space-1);
  column-gap: var(--a3-space-3);
  align-items: center;
}
[data-a3-surface="merchant"] .a3-brand { grid-row: 1; grid-column: 1; }
[data-a3-surface="merchant"] .a3-authority__surface { grid-row: 1; grid-column: 2; justify-self: start; }
[data-a3-surface="merchant"] .a3-authority__scope { grid-row: 2; grid-column: 1; justify-self: start; }
[data-a3-surface="merchant"] .a3-authority__basis { grid-row: 2; grid-column: 2; justify-self: end; }
/* The viewer's monogram, top right, beside the surface label. Two letters in a
   circle: text rather than an image, so it holds at whatever text size the
   person has set and needs nothing to load. */
[data-a3-surface="merchant"] .a3-authority__viewer {
  grid-row: 1;
  grid-column: 2;
  justify-self: end;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 999px;
  background: var(--a3-primary-100);
  color: var(--a3-primary-700);
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-2xs);
  letter-spacing: 0.02em;
}
/* The dot is decoration beside words that already say it, which is the rule
   `componentSystem.test.ts` holds: no state is carried by colour alone. */
[data-a3-surface="merchant"] .a3-authority__basis::before {
  content: "";
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  margin-right: var(--a3-space-2);
  border-radius: 999px;
  background: var(--a3-primary-600);
  vertical-align: baseline;
}

/* Cross-boundary access is never quiet. If an operator is inside a business
   they are not a member of, the interface says so continuously and shows the
   reason that was recorded, because that reason is now part of an audit record
   they cannot alter. */
.a3-authority--borrowed {
  background: var(--a3-denied-100);
  border-top-color: var(--a3-denied-600);
}
.a3-authority--borrowed .a3-authority__basis { color: var(--a3-denied-600); font-weight: var(--a3-weight-medium); }

/* -----------------------------------------------------------------------------
   Brand slot: deliberately empty of brand
   -----------------------------------------------------------------------------
   The Asiam3 logo does not exist yet, and inventing one now would mean a
   temporary mark quietly becoming the brand because it shipped first.

   So this is a reserved box, not a logo. Its dimensions are fixed by token, and
   nothing around it measures the mark itself. When the real logo arrives it is
   dropped into a box that already occupies the right space, so navigation,
   spacing and responsive behaviour do not move.

   The placeholder is intentionally plain and visibly provisional. It should look
   like something waiting to be replaced, because it is. `data-a3-brand="pending"`
   is the marker to search for when substituting the real asset.

   Constraints the real logo must satisfy, so the swap stays a swap:
     - fits within --a3-brand-height without changing it
     - readable at --a3-brand-height-compact on small screens
     - works on --a3-bg-raised in both light and dark themes
     - has an accessible name; if it is an image, it carries meaningful alt text
----------------------------------------------------------------------------- */
:root {
  --a3-brand-height: 28px;
  --a3-brand-height-compact: 24px;
  --a3-brand-max-width: 160px;
}

.a3-brand {
  display: inline-flex; align-items: center;
  /* A reserved box, not a cage.
     This was a fixed `height` with `overflow: hidden`, which is right for the
     image logo the box is being kept for and wrong for the text standing in for
     it: at 150% text the wordmark was cut off, and at 200% a merchant's own
     shop name was cut off on their own page. That is WCAG 1.4.4, measured in a
     browser at the size a visitor can actually choose.
     The reservation is kept as a minimum, and the image is still contained by
     the `max-height` on the child below. */
  min-height: var(--a3-brand-height);
  max-width: var(--a3-brand-max-width);
  flex: 0 0 auto;
}
.a3-brand > * { max-height: 100%; max-width: 100%; }

@media (max-width: 640px) {
  .a3-brand { min-height: var(--a3-brand-height-compact); }
}

/* The provisional treatment. Wordmark in the display face, no device, no
   colour beyond the ink already in the system. Neutral on purpose: a neutral
   placeholder is easy to abandon, and a good-looking one is not. */
.a3-brand[data-a3-brand="pending"] {
  font-family: var(--a3-font-display);
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-lg);
  letter-spacing: -0.01em;
  color: var(--a3-text);
  align-items: center;
  gap: var(--a3-space-2);
}

/* Asiam3's mark carries Asiam3's identity colour. **A shop's does not.**
   The brand slot holds the market's name on the market and the merchant's name
   inside their shop, so colouring it unconditionally would paint a merchant's
   own name in Asiam3's brand colour, which is the platform putting itself
   where the merchant belongs. Keyed on the surface's context, never on the
   direction: a component does not know which direction it is in. */
[data-a3-context="marketplace"] .a3-brand[data-a3-brand="pending"] {
  color: var(--a3-identity);
}
/* A quiet, unmistakable "not final" marker. Visible to the team, meaningless
   to a customer, and impossible to mistake for a designed device. */
.a3-brand[data-a3-brand="pending"]::after {
  content: "";
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--a3-border-strong);
  flex: 0 0 auto;
}

/* -----------------------------------------------------------------------------
   Buttons
----------------------------------------------------------------------------- */
.a3-btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--a3-space-2);
  min-height: var(--a3-touch-min);
  padding: 0 var(--a3-space-4);
  font: inherit; font-weight: var(--a3-weight-medium);
  border-radius: var(--a3-radius-md);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  transition: background var(--a3-duration-fast) var(--a3-ease);
}
.a3-btn--primary { background: var(--a3-primary-600); color: var(--a3-text-on-action); }
.a3-btn--primary:hover:not(:disabled) { background: var(--a3-primary-700); }
.a3-btn--secondary {
  background: var(--a3-bg-raised); color: var(--a3-text);
  border-color: var(--a3-border-strong);
}
.a3-btn--secondary:hover:not(:disabled) { background: var(--a3-bg-sunken); }
.a3-btn--quiet { background: transparent; color: var(--a3-link); }

/* Destructive actions are never the primary button and never sit adjacent to
   the safe one without separation. Colour alone does not carry the weight. */
.a3-btn--destructive {
  background: var(--a3-bg-raised); color: var(--a3-danger-600);
  border-color: var(--a3-danger-600);
}
.a3-btn--destructive:hover:not(:disabled) { background: var(--a3-danger-100); }

/*
  PRESSED. Added 2026-09-04, and it was missing from the whole stylesheet:
  there was not one `:active` rule anywhere in the design system.

  The Founder pressed an upload button, nothing changed, nothing happened for
  forty seconds, and he pressed it again -- which sent his photograph a second
  way that could not work and produced the error page he had been meeting for
  three days.

  He then sent a talk describing the same thing from the outside: "if there was
  zero feedback at this point, this is where people start pressing the button
  again and again because they assume the site is frozen."

  A press is the one moment a person is asking the interface a direct question.
  This is the answer, it costs nothing, and it applies to every button in the
  product rather than to the one that failed.

  `:active` rather than a class, so it is the browser's own notion of pressed --
  it covers a finger, a mouse and the space bar, and it cannot get stuck on.
*/
.a3-btn:active:not(:disabled) {
  transform: translateY(1px);
  filter: brightness(0.94);
}
@media (prefers-reduced-motion: reduce) {
  /* The movement goes; the answer stays. */
  .a3-btn:active:not(:disabled) { transform: none; }
}

.a3-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* Busy, not disabled. A disabled button loses its accessible name to some
   screen readers and tells the user nothing about why. */
.a3-btn[aria-busy="true"] { cursor: progress; }
.a3-btn[aria-busy="true"]::before {
  content: ""; width: 0.85em; height: 0.85em; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent;
  animation: a3-spin 0.7s linear infinite;
}
@keyframes a3-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .a3-btn[aria-busy="true"]::before { animation-duration: 2s; }
}

/* -----------------------------------------------------------------------------
   Status
   -----------------------------------------------------------------------------
   Each status carries a shape or mark as well as a colour, so the distinction
   survives colour blindness and a bad screen in sunlight.

   Verified is deliberately not celebratory. It records a narrow claim.
----------------------------------------------------------------------------- */
.a3-status {
  display: inline-flex; align-items: center; gap: var(--a3-space-2);
  padding: var(--a3-space-1) var(--a3-space-3);
  border-radius: 999px;
  font-size: var(--a3-text-xs); font-weight: var(--a3-weight-medium);
  border: 1px solid transparent; white-space: nowrap;
}
.a3-status::before { content: attr(data-a3-mark); font-weight: var(--a3-weight-semibold); }

.a3-status--success  { background: var(--a3-success-100);  color: var(--a3-success-600);  border-color: var(--a3-success-600); }
.a3-status--settled  { background: var(--a3-settled-100);  color: var(--a3-settled-600);  border-color: var(--a3-settled-600); }
.a3-status--verified { background: var(--a3-verified-100); color: var(--a3-verified-600); border-color: var(--a3-verified-600); }
.a3-status--pending  { background: var(--a3-pending-100);  color: var(--a3-pending-600);  border-color: var(--a3-pending-600); }
.a3-status--warning  { background: var(--a3-warning-100);  color: var(--a3-warning-600);  border-color: var(--a3-warning-600); }
.a3-status--danger   { background: var(--a3-danger-100);   color: var(--a3-danger-600);   border-color: var(--a3-danger-600); }
.a3-status--denied   { background: var(--a3-denied-100);   color: var(--a3-denied-600);   border-color: var(--a3-denied-600); }
.a3-status--neutral  { background: var(--a3-bg-sunken);    color: var(--a3-text-secondary); border-color: var(--a3-border-strong); }

/* The scope of a verification claim travels with the claim. A badge without
   its scope and date is the badge that promises more than it can support. */
.a3-status__scope { color: var(--a3-text-muted); font-weight: var(--a3-weight-regular); font-size: var(--a3-text-2xs); }

/* -----------------------------------------------------------------------------
   Money
   Tabular figures, currency always explicit, never a bare number.

   NOT the monospace face, and that is the whole of this note. Until 2026-09-06
   a price was set in IBM Plex Mono, which is why every amount on the site read
   as a machine's output rather than a shop's price. Sheet `05-product` draws
   `GH<cedi> 120` in the same sans as the product name, and the Founder's rule
   for this direction is exact.

   `tabular-nums` is what the heading above was actually asking for -- columns
   of amounts that line up -- and Inter carries it, so the alignment the mono
   face was there to buy is kept without the typewriter.

   The currency is NOT set smaller or greyer than its amount. `GH<cedi>` is part
   of the price, not an annotation on it, and the sheet draws the two as one.
----------------------------------------------------------------------------- */
.a3-money {
  font-family: var(--a3-font-body);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.a3-money__currency { margin-right: 0.3em; }
.a3-money--negative { color: var(--a3-danger-600); }
td.a3-money, th.a3-money { text-align: right; }

/* -----------------------------------------------------------------------------
   Cards and layout
----------------------------------------------------------------------------- */
/* The head of a form that sits in its own card: a glyph in the shared round
   tile, the form's own name, and one line about what it expects. The mockups
   draw it on registration, above the fields rather than beside them, so the
   first thing read is what this form is for. */
.a3-formhead {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--a3-space-3);
  align-items: center;
}
.a3-formhead__title {
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-md);
  margin: 0;
}
.a3-formhead__text { margin: 0; color: var(--a3-text-secondary); font-size: var(--a3-text-sm); }
/* The one line that says what submitting a form commits you to. Small and
   centred and quiet: it is a statement rather than an explanation, and the two
   documents it names are where the explanation lives. */
/* -----------------------------------------------------------------------------
   ASIAM3 IS NOT OPEN FOR BUSINESS YET
   The first thing on the page, above the chrome, on every surface including a
   merchant's own storefront. Quiet rather than alarming: it is a statement of
   fact a person should read once, not a warning that makes the product look
   broken. Full width, because a notice that scrolls off at the side of a phone
   is a notice nobody read.
   -------------------------------------------------------------------------- */
.a3-openness {
  padding: var(--a3-space-3) var(--a3-space-4);
  background: var(--a3-primary-100);
  color: var(--a3-text);
  border-bottom: 1px solid var(--a3-border);
  display: grid;
  gap: 2px;
}
.a3-openness__title { margin: 0; font-weight: var(--a3-weight-semibold); font-size: var(--a3-text-sm); }
.a3-openness__text { margin: 0; font-size: var(--a3-text-sm); color: var(--a3-text-secondary); max-width: 70ch; }

.a3-formnote {
  margin: 0;

  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
  text-wrap: balance;
}

.a3-card {
  background: var(--a3-bg-raised);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  padding: var(--a3-space-5);
}
.a3-stack > * + * { margin-top: var(--a3-space-4); }
.a3-row { display: flex; flex-wrap: wrap; gap: var(--a3-space-3); align-items: center; }

/* The gutter is published as a token so a full-bleed child -- the storefront
   hero -- can cancel exactly this value instead of guessing at it, and stays
   correct when the gutter changes at the breakpoint below. */
.a3-page {
  --a3-page-gutter: var(--a3-space-5);
  padding: var(--a3-page-gutter);
  max-width: 1200px;
  margin: 0 auto;
}
@media (max-width: 640px) {
  .a3-page {
    --a3-page-gutter: var(--a3-space-3);
    padding: var(--a3-space-4) var(--a3-page-gutter);
  }
}

/* -----------------------------------------------------------------------------
   Forms and validation
   -----------------------------------------------------------------------------
   Every field is labelled. Errors are announced, described, and tied to the
   field with aria-describedby, so a screen reader user learns what is wrong
   without hunting for it. Error text says what to do, never just what failed.
----------------------------------------------------------------------------- */
.a3-field { display: block; }
.a3-field__label {
  display: block; margin-bottom: var(--a3-space-1);
  font-size: var(--a3-text-sm); font-weight: var(--a3-weight-medium);
}
/* A hint is its own line.
   It was inline, and a hint following a button is the case that shows why that
   is wrong: the button is inline-flex, so the sentence began to the right of
   "Place order" and wrapped around underneath it, which reads as a broken page
   on the one screen where a customer is deciding whether to buy something.
   Found by looking at it; no assertion in the suite could see it. */
.a3-field__hint {
  display: block;
  font-size: var(--a3-text-xs); color: var(--a3-text-muted); margin-bottom: var(--a3-space-1);
}
.a3-input, .a3-select, .a3-textarea {
  width: 100%; min-height: var(--a3-touch-min);
  padding: var(--a3-space-2) var(--a3-space-3);
  font: inherit; color: var(--a3-text);
  background: var(--a3-bg-raised);
  border: 1px solid var(--a3-border-strong);
  border-radius: var(--a3-radius-md);
}
.a3-textarea { min-height: 6rem; resize: vertical; }
.a3-input[aria-invalid="true"], .a3-select[aria-invalid="true"], .a3-textarea[aria-invalid="true"] {
  border-color: var(--a3-danger-600); border-width: 2px;
}
.a3-field__error {
  display: flex; gap: var(--a3-space-2); align-items: flex-start;
  margin-top: var(--a3-space-1);
  font-size: var(--a3-text-sm); color: var(--a3-danger-600);
}
.a3-field__error::before { content: "!"; font-weight: var(--a3-weight-semibold); }
.a3-input:disabled, .a3-select:disabled, .a3-textarea:disabled {
  background: var(--a3-bg-sunken); color: var(--a3-text-muted); cursor: not-allowed;
}

/* -----------------------------------------------------------------------------
   Notices
   -----------------------------------------------------------------------------
   Four kinds, deliberately distinct, because they mean different things and
   need different next actions:

     error     something broke. It may work if retried.
     denied    you are not permitted. Retrying will not help.
     conflict  someone or something else changed this first.
     info      context, no action required.

   Denied never explains what it is protecting. Telling an unauthorised caller
   which business a record belongs to, or that it exists at all, is the
   enumeration leak the API refuses; the interface must not reintroduce it.
----------------------------------------------------------------------------- */
.a3-notice {
  display: flex; gap: var(--a3-space-3);
  padding: var(--a3-space-4);
  border-radius: var(--a3-radius-md);
  border: 1px solid;
  border-left-width: 4px;
}
.a3-notice__body { flex: 1; }



/* The way out of a refusal. See `basketProblem`: a notice a person cannot

   act on is a dead end, whatever its words say. */

.a3-notice__actions { margin: var(--a3-space-3) 0 0; }
.a3-notice__title { font-weight: var(--a3-weight-semibold); margin: 0 0 var(--a3-space-1); font-size: var(--a3-text-md); }
.a3-notice__text  { margin: 0; font-size: var(--a3-text-sm); color: var(--a3-text-secondary); }

/* What kind of thing this is, in words, above the sentence. The colour is
   supporting information and never the message: a reader who cannot see the
   difference between the amber and the purple still reads "Not permitted"
   rather than "Something went wrong", and those need different next actions. */
.a3-notice__kind {
  margin: 0 0 var(--a3-space-1);
  font-family: var(--a3-font-numeric);
  font-size: var(--a3-text-2xs);
  font-weight: var(--a3-weight-semibold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
/* The mark. A second non-colour signal, and the reason each variant sets its
   own: a shape carries meaning where a hue alone does not. */
.a3-notice__mark {
  flex: 0 0 auto;
  font-weight: var(--a3-weight-semibold);
  line-height: var(--a3-leading-tight);
}

.a3-notice--error    { background: var(--a3-danger-100);  border-color: var(--a3-danger-600); }
.a3-notice--error .a3-notice__kind, .a3-notice--error .a3-notice__mark { color: var(--a3-danger-600); }
.a3-notice--denied   { background: var(--a3-denied-100);  border-color: var(--a3-denied-600); }
.a3-notice--denied .a3-notice__kind, .a3-notice--denied .a3-notice__mark { color: var(--a3-denied-600); }
/* Conflict was the same treatment as warning until 2026-08-14, which meant two
   different situations with two different next actions were one thing on
   screen. It now carries the pending palette and a dashed edge: somebody
   changed the record first, so the reader has to look again before acting,
   which is not the same as a requirement being unmet. */
.a3-notice--conflict {
  background: var(--a3-pending-100); border-color: var(--a3-pending-600);
  border-left-style: dashed;
}
.a3-notice--conflict .a3-notice__kind, .a3-notice--conflict .a3-notice__mark { color: var(--a3-pending-600); }
.a3-notice--info     { background: var(--a3-primary-100); border-color: var(--a3-primary-600); }
.a3-notice--info .a3-notice__kind, .a3-notice--info .a3-notice__mark { color: var(--a3-primary-600); }
.a3-notice--success  { background: var(--a3-success-100); border-color: var(--a3-success-600); }
.a3-notice--success .a3-notice__kind, .a3-notice--success .a3-notice__mark { color: var(--a3-success-600); }
/* Present, priced and explained, and not a failure: the thing exists and
   cannot be acted on right now. Neutral rather than alarming, because nothing
   went wrong and nobody is being refused. */
.a3-notice--unavailable {
  background: var(--a3-bg-sunken); border-color: var(--a3-border-strong);
}
.a3-notice--unavailable .a3-notice__kind, .a3-notice--unavailable .a3-notice__mark { color: var(--a3-text-secondary); }
/* Something stands between the reader and what they wanted, and it is neither a
   refusal nor a failure. A shop that cannot publish yet is not being denied and
   nothing has broken; a requirement has not been met. Distinct from conflict,
   which now carries its own palette and a dashed edge. */
.a3-notice--warning  { background: var(--a3-warning-100); border-color: var(--a3-warning-600); }
.a3-notice--warning .a3-notice__kind, .a3-notice--warning .a3-notice__mark { color: var(--a3-warning-600); }

/* A NOTICE THE MERCHANT MAY CLOSE (G96). The Founder: "the merchant should be
   able to tap on close on the banner." The cross is a full touch target in the
   top corner, and the notice keeps its own anatomy around it. */
.a3-notice--closable {
  position: relative;
  padding-inline-end: calc(var(--a3-touch-min) + var(--a3-space-2));
  /* Room before whatever follows: the phone screenshot showed the dashboard's
     figures touching its lower edge. */
  margin-block-end: var(--a3-space-4);
}
.a3-notice__close {
  position: absolute;
  inset-block-start: var(--a3-space-1);
  inset-inline-end: var(--a3-space-1);
  margin: 0;
}
.a3-notice__closer {
  display: grid;
  place-items: center;
  width: var(--a3-touch-min);
  height: var(--a3-touch-min);
  border: 0;
  border-radius: var(--a3-radius-md);
  background: transparent;
  color: var(--a3-text-secondary);
  cursor: pointer;
}
.a3-notice__closer:hover { background: var(--a3-surface-accent); }
.a3-notice__closer .a3-icon { width: 1.25rem; height: 1.25rem; }
.a3-notice__body > .a3-notice__text + .a3-notice__text { margin-block-start: var(--a3-space-2); }
.a3-notice__sign { margin: var(--a3-space-1) 0 0; font-size: var(--a3-text-sm); font-weight: var(--a3-weight-semibold); }

/* THE UNREAD COUNT BESIDE A NAVIGATION LABEL (G98), red like the cart's: the
   Founder, "notification number should be in red just as carts number is in
   red on the homepage". Beside the label rather than over the glyph, because a
   menu row has room and a number over an icon is harder to read. */
.a3-nav__count {
  display: inline-grid;
  place-items: center;
  min-width: 1.25rem;
  height: 1.25rem;
  margin-inline-start: var(--a3-space-2);
  padding-inline: 5px;
  border-radius: 999px;
  background: var(--a3-danger-600);
  color: var(--a3-text-inverse);
  font-size: var(--a3-text-2xs);
  font-weight: 700;
  line-height: 1;
}

/* -----------------------------------------------------------------------------
   Tables
   Dense by default on merchant and control surfaces. Headers stay put, because
   a merchant scrolling two hundred orders should not have to remember which
   column is which.
----------------------------------------------------------------------------- */
.a3-table-wrap { overflow-x: auto; border: 1px solid var(--a3-border); border-radius: var(--a3-radius-lg); background: var(--a3-bg-raised); }
.a3-table { width: 100%; border-collapse: collapse; font-size: var(--a3-text-sm); }
.a3-table caption { text-align: left; padding: var(--a3-space-3) var(--a3-space-4); color: var(--a3-text-secondary); font-size: var(--a3-text-sm); }
.a3-table th, .a3-table td {
  padding: 0 var(--a3-space-4); height: var(--a3-density-row);
  text-align: left; border-bottom: 1px solid var(--a3-border);
}
.a3-table thead th {
  position: sticky; top: 0; background: var(--a3-bg-sunken);
  font-size: var(--a3-text-xs); text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--a3-text-secondary); font-weight: var(--a3-weight-semibold);
}
.a3-table--compact { --a3-density-row: var(--a3-density-row-compact); }
/* The row header too. Written for `td` alone, it left every table with a
   `th[scope="row"]` drawing a line under its FIRST CELL ONLY and stopping
   partway across -- seen at 1440px on the merchant dashboard, which is the
   first table to put a row header in a last row with no foot under it. */
.a3-table tbody tr:last-child td,
.a3-table tbody tr:last-child th { border-bottom: none; }

/* -----------------------------------------------------------------------------
   Empty, loading, and skeletons
   -----------------------------------------------------------------------------
   An empty screen is an invitation to act, never an apology. A loading state
   reserves the space the content will occupy so nothing jumps when it arrives.
----------------------------------------------------------------------------- */
.a3-empty {
  text-align: center; padding: var(--a3-space-7) var(--a3-space-4);
  border: 1px dashed var(--a3-border-strong); border-radius: var(--a3-radius-lg);
}
.a3-empty__title {
  font-size: var(--a3-text-lg); font-weight: var(--a3-weight-semibold); margin: 0 0 var(--a3-space-2);
  /* This line quotes what the customer typed. Anything a customer can type,
     a customer can type sixty characters of with no spaces in it, and unbroken
     it pushed the whole page sideways on every phone width. Found by rendering
     it: no stylesheet reading could have shown it. */
  overflow-wrap: anywhere;
}
.a3-empty__text { margin: 0 auto var(--a3-space-4); max-width: 44ch; color: var(--a3-text-secondary); font-size: var(--a3-text-sm); }

.a3-skeleton {
  background: linear-gradient(90deg, var(--a3-bg-sunken) 25%, var(--a3-bg-raised) 37%, var(--a3-bg-sunken) 63%);
  background-size: 400% 100%;
  animation: a3-shimmer 1.4s ease infinite;
  border-radius: var(--a3-radius-sm);
  height: 1em;
}
@keyframes a3-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
@media (prefers-reduced-motion: reduce) {
  .a3-skeleton { animation: none; background: var(--a3-bg-sunken); }
}

/* -----------------------------------------------------------------------------
   Destructive confirmation
   -----------------------------------------------------------------------------
   Irreversible actions state the consequence in specific terms and require an
   intentional act rather than a reflex. What is retained and for how long is
   shown here, not in a policy page nobody opens, because a deletion the user
   did not understand is a deletion they did not consent to.
----------------------------------------------------------------------------- */
.a3-confirm { border: 1px solid var(--a3-danger-600); border-radius: var(--a3-radius-lg); padding: var(--a3-space-5); background: var(--a3-bg-raised); }
.a3-confirm__title { margin: 0 0 var(--a3-space-3); font-size: var(--a3-text-xl); }
.a3-confirm__consequences { margin: 0 0 var(--a3-space-4); padding-left: var(--a3-space-5); font-size: var(--a3-text-sm); color: var(--a3-text-secondary); }
.a3-confirm__consequences li + li { margin-top: var(--a3-space-1); }
.a3-confirm__actions { display: flex; gap: var(--a3-space-3); flex-wrap: wrap; }
/* The safe action comes first in the DOM and is the default. */
.a3-confirm__actions .a3-btn--destructive { margin-left: auto; }
@media (max-width: 480px) {
  .a3-confirm__actions .a3-btn { width: 100%; }
  .a3-confirm__actions .a3-btn--destructive { margin-left: 0; order: 2; }
}

/* -----------------------------------------------------------------------------
   Discovery: shops and products
   -----------------------------------------------------------------------------
   The customer's mental model is shops and products, not merchants. A shop is a
   destination with an identity; a merchant is the business operating it and does
   not appear here at all.

   Both cards are links to a place, not tiles with buttons in them. A card with
   an action inside it makes the whole card ambiguous to a keyboard user and to
   anyone using a screen reader.
----------------------------------------------------------------------------- */
.a3-grid {
  display: grid;
  gap: var(--a3-space-4);
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
/* One column on a narrow phone. Two cramped columns is worse than one clear one. */
@media (max-width: 400px) { .a3-grid { grid-template-columns: 1fr; } }

.a3-card-link {
  display: flex; flex-direction: column; gap: var(--a3-space-3);
  padding: var(--a3-space-4);
  background: var(--a3-bg-raised);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  text-decoration: none; color: inherit;
  min-height: var(--a3-touch-min);
  /* A shop name is merchant text and can be 120 characters with nothing in it
     to break on. Unbroken, it widens the card, the card widens the grid, and
     the whole page scrolls sideways on a phone because of one merchant. Broken
     here rather than left to push everything else. */
  min-width: 0; overflow-wrap: anywhere;
}
.a3-card-link:hover { border-color: var(--a3-border-strong); }

/* The shop's mark. Reserved space, so a shop without a logo does not reflow the
   grid, and a shop with one cannot make its card taller than its neighbours. */
.a3-shop-mark {
  /* Fixed so a shop without a logo does not reflow the grid. The letters inside
     it are text and scale with the visitor's setting, so the box grows rather
     than clipping them. */
  width: 40px; min-height: 40px; flex: 0 0 auto;
  border-radius: var(--a3-radius-md);
  background: var(--a3-bg-sunken);
  display: grid; place-items: center;
  font-family: var(--a3-font-display); font-weight: var(--a3-weight-semibold);
  color: var(--a3-text-secondary);
  overflow: hidden;
}
.a3-shop-mark img { width: 100%; height: 100%; object-fit: cover; }

/* A monogram over the shop's own material, so two shops beginning with the same
   letter are not the same mark. The letters sit on the material rather than
   beside it, so the mark is one object at 40px. */
.a3-shop-mark[data-a3-material] {
  background: var(--a3-material);
  /* White on the scrim rather than the material ink, because the material is
     no longer what is underneath. Stacked above the gradient. */
  color: var(--a3-text-inverse);
  z-index: 1;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
  letter-spacing: 0.02em;
}

/* -----------------------------------------------------------------------------
   Materials: what a record looks like before anyone has photographed it
   -----------------------------------------------------------------------------
   Asiam3 has no image handling and will open with no photographs, so this is
   the ordinary case rather than a fallback. The material is chosen from the
   record's own identifier, which is what makes a shop look the same on every
   page rather than changing as a customer moves around it.

   It is a woven field and never a picture of the thing. A placeholder that
   looked like a photograph of the actual product would be a claim about goods
   Asiam3 has never seen, and the label says so in the open.

   Every value here is a token, so a direction may restate the whole palette and
   no component learns which direction it is in.
----------------------------------------------------------------------------- */
[data-a3-material="cocoa"]    { --a3-material: var(--a3-material-cocoa); }
[data-a3-material="kente"]    { --a3-material: var(--a3-material-kente); }
[data-a3-material="shea"]     { --a3-material: var(--a3-material-shea); }
[data-a3-material="pepper"]   { --a3-material: var(--a3-material-pepper); }
[data-a3-material="wood"]     { --a3-material: var(--a3-material-wood); }
[data-a3-material="market"]   { --a3-material: var(--a3-material-market); }
[data-a3-material="hands"]    { --a3-material: var(--a3-material-hands); }
[data-a3-material="greenery"] { --a3-material: var(--a3-material-greenery); }

/* The weave. A single repeating gradient over the material, which is what stops
   it reading as a flat swatch, and it is one paint rather than an image. */
.a3-placeholder {
  position: relative;
  display: block;
  background: var(--a3-material, var(--a3-bg-sunken));
  overflow: hidden;
}
.a3-placeholder::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(112deg, rgba(255, 255, 255, 0.3) 0 1px, transparent 1px 9px);
  mix-blend-mode: soft-light;
}

/* Said out loud rather than left to be inferred. Marked decorative in the
   markup, so a screen reader announces the product's name instead of this. */
.a3-placeholder__label {
  position: absolute;
  left: var(--a3-space-2); bottom: var(--a3-space-2);
  padding: 2px var(--a3-space-2);
  background: rgba(20, 14, 10, 0.55);
  color: var(--a3-text-on-material);
  font-family: var(--a3-font-numeric);
  font-size: var(--a3-text-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border-radius: var(--a3-radius-sm);
}

.a3-card-link__title { font-weight: var(--a3-weight-semibold); font-size: var(--a3-text-md); }
.a3-card-link__meta {
  color: var(--a3-text-muted); font-size: var(--a3-text-xs);
  /* A shop address is one unbroken string and can be longer than a 320px
     screen. Broken here rather than allowed to push the page sideways. */
  overflow-wrap: anywhere;
}

/* A guest's private order link: one unbroken string far wider than a phone.
   Unbroken, it pushed the whole order page sideways (screenshots, 27 Sep). */
.a3-order-link { overflow-wrap: anywhere; }

/* Product image. A fixed aspect box, so a merchant's tall photograph cannot
   stretch a row and a missing one leaves a placeholder rather than a collapse. */
.a3-product-image {
  aspect-ratio: 4 / 3; width: 100%;
  background: var(--a3-bg-sunken);
  border-radius: var(--a3-radius-md);
  display: grid; place-items: center;
  color: var(--a3-text-muted); font-size: var(--a3-text-xs);
  overflow: hidden;
}
.a3-product-image img { width: 100%; height: 100%; object-fit: cover; }

/* The material wins over the plain ground above.
   Both rules are one class deep, `.a3-product-image` is written later, and
   later wins, so every product placeholder painted the sunken ground and no
   product ever showed its material. The shop mark did, which is why this
   survived review: the system looked applied because half of it was. Stated as
   a combined selector rather than fixed by moving a block, so a future reorder
   cannot undo it. */
.a3-product-image.a3-placeholder { background: var(--a3-material, var(--a3-bg-sunken)); }

/* -----------------------------------------------------------------------------
   Reputation
   -----------------------------------------------------------------------------
   The first place where a design choice can undo a security guarantee.

   The domain publishes a band and a volume bucket, and refuses to publish a mean
   or an exact count, because a mean plus a count is solvable: watch the pair
   change and the newly added score falls out exactly.

   So this component renders a band and a bucket, and there is deliberately no
   element for a numeric average, no element for a review count, and no
   "most recent review" slot. Each of those would be a natural thing to add and
   each reintroduces the leak. A star row is also absent: five stars with one
   half-filled is a mean with extra steps.
----------------------------------------------------------------------------- */
.a3-reputation { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--a3-space-2); }
.a3-reputation__band {
  font-family: var(--a3-font-body);
  font-variant-numeric: tabular-nums;
  font-weight: var(--a3-weight-medium);
  font-size: var(--a3-text-md);
  /* INHERIT rather than name the ground ink. This said var(--a3-text), which is
     correct on a white page and wrong inside a storefront hero: the hero paints
     a dark material and sets the ink to match, and this line put the dark ink
     straight back, so a shop's rating band rendered near-black on near-black.
     The contrast guard computes token pairs and cannot see a component
     overriding an inherited colour. */
  color: inherit;
}
.a3-reputation__volume { color: var(--a3-text-muted); font-size: var(--a3-text-xs); }
/* Same rule, other half: on a material ground the muted grey is not readable
   either, so the volume takes the surrounding ink and steps back with opacity
   rather than with a hue that assumes a pale ground. */
.a3-hero .a3-reputation__volume,
.a3-shopcard .a3-reputation__volume { color: inherit; opacity: 0.75; }

/* Below the threshold. Says what is true without implying anything bad: a new
   shop has no ratings because it is new, not because it is poor. */
.a3-reputation--withheld { color: var(--a3-text-muted); font-size: var(--a3-text-sm); }

/* An individual published rating. No author, no date beyond the month, and no
   ordinal position, because "first" or "latest" is attribution. */
.a3-rating { padding: var(--a3-space-3) 0; border-bottom: 1px solid var(--a3-border); }
.a3-rating:last-child { border-bottom: none; }
.a3-rating__head { display: flex; align-items: baseline; gap: var(--a3-space-3); }
.a3-rating__score { font-family: var(--a3-font-body); font-weight: var(--a3-weight-semibold); }
.a3-rating__period { color: var(--a3-text-muted); font-size: var(--a3-text-xs); }
.a3-rating__body { margin: var(--a3-space-2) 0 0; font-size: var(--a3-text-sm); color: var(--a3-text-secondary); }

/* -----------------------------------------------------------------------------
   Shop header
   -----------------------------------------------------------------------------
   The shop's own identity: its mark, name, summary, where it trades, its trust
   signals and whoever it has chosen to name publicly. The operating business is
   not shown, because a customer came for the shop.
----------------------------------------------------------------------------- */
.a3-shop-header { display: flex; flex-wrap: wrap; gap: var(--a3-space-4); align-items: flex-start; }
.a3-shop-header .a3-shop-mark { width: 64px; height: 64px; font-size: var(--a3-text-xl); }
.a3-shop-header__body { flex: 1 1 16rem; min-width: 0; }
.a3-shop-header__name {
  margin: 0; font-family: var(--a3-font-display); font-size: var(--a3-text-2xl);
  /* The largest text on the page and the most likely to be a long name. */
  overflow-wrap: anywhere;
}
.a3-shop-header__summary { margin: var(--a3-space-1) 0 0; color: var(--a3-text-secondary); max-width: var(--a3-measure); }

/* Named by choice, never because they operate the shop. */
.a3-operator { display: flex; align-items: baseline; gap: var(--a3-space-2); font-size: var(--a3-text-sm); }
.a3-operator__role { color: var(--a3-text-muted); font-size: var(--a3-text-xs); }

/* -----------------------------------------------------------------------------
   Two-column detail, collapsing to one
   -----------------------------------------------------------------------------
   A product beside its purchase panel on a wide screen; stacked on a phone with
   the panel after the detail, because a thumb reaching the bottom of the screen
   should find the action.
----------------------------------------------------------------------------- */
.a3-detail { display: grid; gap: var(--a3-space-5); grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); align-items: start; }
@media (max-width: 720px) { .a3-detail { grid-template-columns: 1fr; } }

/* Sticky only where there is room for it to help. */
@media (min-width: 721px) {
  .a3-detail__aside { position: sticky; top: calc(var(--a3-space-6) + 2rem); }
}

.a3-breadcrumb { display: flex; flex-wrap: wrap; gap: var(--a3-space-2); font-size: var(--a3-text-sm); color: var(--a3-text-muted); }
/* A trail is a row of SEPARATE links, so each one is a target in its own
   right and WCAG 2.2's inline exception does not cover it -- that exception is
   for a link inside a sentence, with text running through it.

   Measured 2026-09-03 with the eye, on a merchant product page at 390px: these
   rendered at 20px, under the 24 the token declares. Exactly the footer's
   defect, in a component written after the footer was fixed, which is why the
   guard now reads every link rule in this file rather than that one selector.

   A minimum height with centring, rather than padding, so the target grows
   without moving the words or reflowing the trail. */
.a3-breadcrumb a {
  color: var(--a3-link);
  display: inline-flex;
  align-items: center;
  min-height: var(--a3-target-inline-min);
}

/* -----------------------------------------------------------------------------
   Layout utilities the surfaces actually need
   -----------------------------------------------------------------------------
   These exist because the content policy sets style-src 'self' with no
   unsafe-inline, so a style attribute is dropped by the browser and the layout
   it was holding up collapses. Silently, and only on the real product: markup
   tests still see the attribute in the string.

   So every layout decision a surface makes has to be nameable here. The list is
   short on purpose. When it stops being short, that is the signal that a
   component is missing rather than that another utility is needed.
----------------------------------------------------------------------------- */

/* A list of records. Semantically a list, without the marker and indent that
   turn a card list into a bulleted one. */
.a3-list { list-style: none; padding: 0; margin: 0; }

/* Label on one side, status on the other, stacking rather than overflowing when
   the viewport cannot hold both. This is the phone case, which is the common
   case, so it is the one the rule is written for. */
.a3-row--spread { justify-content: space-between; }
/* The catalogue controls sit at the right on the sheet, on both widths. Added
   2026-09-11 with the catalogue section; nothing else uses it yet. */
.a3-row--end { justify-content: flex-end; }

/* THE COUNT IS A LINE, NOT A SENTENCE. Sheet `05-phone-established` draws
   "48 products" on its own, and `10-laptop-setting-up` draws "0 products" the
   same way, so the number reads at a glance in both states. */
.a3-catalogue__count {
  margin: 0;
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text);
}

/* Secondary text that explains a control or its absence. Not a notice: a notice
   is something that happened, a hint is something to know beforehand. */
.a3-hint {
  margin: 0;
  color: var(--a3-text-secondary);
  font-size: var(--a3-text-sm);
}

/* Supporting line inside an empty state. Distinct from .a3-empty__text, which
   is the centred explanatory paragraph; this one sits under a title. */
.a3-empty__body {
  margin: 0;
  color: var(--a3-text-secondary);
  font-size: var(--a3-text-sm);
}

/* -----------------------------------------------------------------------------
   The shell: what sits around a page's content
   -----------------------------------------------------------------------------
   Three parts, none of which invents a value. A page shell composes components
   and reads the same semantic tokens everything else does; the moment one holds
   a colour of its own it becomes a second place a direction has to be applied.

   All three are laid out with flex and gap rather than per-element margins, and
   nothing here carries a fixed width, because a fixed width is the single most
   common cause of a page that scrolls sideways on a phone.
----------------------------------------------------------------------------- */

/* A surface's own destinations. Wraps rather than scrolls: two or three links
   fit at 320px, and a row that scrolls sideways hides the ones past the edge
   from everyone who does not think to drag it. */
.a3-nav {
  display: flex; flex-wrap: wrap; gap: var(--a3-space-2) var(--a3-space-4);
  padding: var(--a3-space-3) var(--a3-space-4);
  border-bottom: 1px solid var(--a3-border);
  background: var(--a3-bg-raised);
  font-size: var(--a3-text-sm);
}
.a3-nav__link {
  display: inline-flex; align-items: center;
  min-height: var(--a3-touch-min);
  color: var(--a3-text-secondary);
  text-decoration: none;
}
.a3-nav__link:hover { color: var(--a3-text); }
/* Signing out is a POST, so it is a button rather than a link, and it has to be
   indistinguishable from the links beside it. A person should not be able to
   tell which navigation items happen to change something -- that is an
   implementation detail of the route, not a fact about the market. */
.a3-nav__form { display: inline-flex; margin: 0; }
button.a3-nav__link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
/* Where you are, said with weight and a rule as well as a colour, so it is
   still legible to someone who cannot tell the two colours apart. */
.a3-nav__link[aria-current="page"] {
  color: var(--a3-text);
  font-weight: var(--a3-weight-semibold);
  box-shadow: inset 0 -2px 0 var(--a3-identity);
}

/* Where the market's search lives. Above the content, so the first thing in the
   page is the heading that says where you are. */
.a3-masthead {
  padding: var(--a3-space-4);
  max-width: 1200px; margin: 0 auto;
}
@media (max-width: 640px) { .a3-masthead { padding: var(--a3-space-3); } }

/* Named, with no destinations, because none of them exist yet. */
.a3-footer {
  display: flex; flex-wrap: wrap; gap: var(--a3-space-1) var(--a3-space-3);
  padding: var(--a3-space-6) var(--a3-space-4);
  margin-top: var(--a3-space-7);
  border-top: 1px solid var(--a3-border);
  color: var(--a3-text-muted);
  font-size: var(--a3-text-xs);
}

/* The one line of Asiam3 permitted inside a merchant's storefront, and the only
   route from it back to the market. Quiet on purpose: being listed makes a shop
   discoverable *from* the market and is not a licence to advertise the market
   inside the merchant's own shop. */
.a3-attribution {
  padding: var(--a3-space-5) var(--a3-space-4);
  border-top: 1px solid var(--a3-border);
  text-align: center;
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
}

/* An item in a record list. The list itself carries no marker, so the item
   carries the separation: a rule between rows rather than a border around each,
   because a list of five bordered boxes reads as five unrelated things. */
.a3-list__item {
  display: flex;
  flex-direction: column;
  gap: var(--a3-space-1);
  padding: var(--a3-space-3) 0;
  border-bottom: 1px solid var(--a3-border);
}
.a3-list__item:last-child { border-bottom: 0; }

/* A labelled list of facts about one record: address, visibility, and whatever
   a surface must state plainly rather than bury in a sentence. A definition
   list because that is what it is, and the label stays with its value at every
   width rather than becoming a two-column table that overflows a phone. */
.a3-facts { margin: 0; display: grid; gap: var(--a3-space-3); min-width: 0; }
.a3-facts dt {
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
  font-weight: var(--a3-weight-medium);
}
.a3-facts dd { margin: var(--a3-space-1) 0 0; font-size: var(--a3-text-sm); overflow-wrap: anywhere; }

/* Page and section headings, so a surface never reaches for an inline
   font-size. */
.a3-heading { margin: 0; font-size: var(--a3-text-xl); font-weight: var(--a3-weight-semibold); }
.a3-subheading { margin: 0; color: var(--a3-text-secondary); font-size: var(--a3-text-sm); }

/* The payment strip: a bounded band above and below, rather than a card. */
.a3-strip {
  padding: var(--a3-space-3) 0;
  border-top: 1px solid var(--a3-border);
  border-bottom: 1px solid var(--a3-border);
}

/* A quantity input is a number, and a number does not need to be as wide as a
   name. Capped in rem rather than px so it grows with the user's text size. */
.a3-input--quantity { max-width: 6rem; }

/* A short answer that is not a secret -- a month, an order number -- sized to
   what it holds rather than to the page. Plain spacing: the code style spreads its
   digits for checking a secret, which makes "October" read as letters. */
.a3-input--short { max-width: 12rem; }

/* A short secret typed digit by digit: the guest's Order PIN and the one-time
   code that replaces a forgotten one (G106, G107). Sized to what it holds, not
   to the page, so a six-digit box does not stretch across a laptop screen;
   even spacing and fixed-width digits make each one easy to check. */
.a3-input--code {
  max-width: 12rem;
  letter-spacing: 0.2em;
  font-variant-numeric: tabular-nums;
}

/* A skeleton row fills its container. */
.a3-skeleton--row { width: 100%; margin-top: var(--a3-space-3); }

/* -----------------------------------------------------------------------------
   Search
   -----------------------------------------------------------------------------
   The search field takes the space the button does not, on every width, rather
   than at a breakpoint. A search box that is narrower than its own placeholder
   on a phone is the most common way a marketplace loses the customer who was
   ready to type.
----------------------------------------------------------------------------- */
.a3-search__field { flex: 1 1 12rem; min-width: 0; }

/* A card's own supporting line. Clamped, because a merchant's summary is
   merchant-written text of unpredictable length, and one long summary must not
   make its card twice the height of every neighbour in the grid. */
.a3-card-link__summary {
  margin: 0;
  color: var(--a3-text-secondary);
  font-size: var(--a3-text-sm);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* -----------------------------------------------------------------------------
   Sharing a shop (`G82`)
   -----------------------------------------------------------------------------
   Direction D's card, which is the only drawing of sharing -- Direction E has
   none -- with the Founder's one change: the round glyph on the left is a copy
   control, and "Share shop" on the right opens the phone's own share list.

   ALWAYS VISIBLE. It replaced a closed disclosure the Founder found on the live
   site: a customer "won't even know there is a link that can be shared if they
   don't tap on it". And the address is TEXT on the card, which is what a phone
   with no script falls back to: press and hold to copy it.

   The two buttons are `hidden` in the markup and shown by share.js only where
   they can work, so with no script nothing here pretends to act
   (`SURFACE_INTEGRITY.md` section 1). The global `[hidden]` rule at the top of
   this file is what stops `.a3-btn` putting a hidden button back on screen. */
.a3-sharecard {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--a3-space-3) var(--a3-space-4);
  align-items: center;
  padding: var(--a3-space-4);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-bg-raised);
}
.a3-sharecard__body { display: grid; gap: 2px; min-width: 0; }
.a3-sharecard__title {
  margin: 0;
  font-size: var(--a3-text-md);
  font-weight: var(--a3-weight-semibold);
}
.a3-sharecard__line { margin: 0; font-size: var(--a3-text-sm); color: var(--a3-text-muted); }
/* The address a person can press and hold. Long ones wrap rather than push the
   page sideways. */
.a3-sharecard__address { margin: 0; font-size: var(--a3-text-sm); overflow-wrap: anywhere; }
.a3-sharecard__status { margin: 0; font-size: var(--a3-text-sm); font-weight: var(--a3-weight-medium); }
.a3-sharecard__status:empty { display: none; }
/* The tile as a control: the same round glyph, pressable. */
.a3-sharecard__copy {
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.a3-sharecard__copy:focus-visible { outline: 2px solid var(--a3-focus); outline-offset: 2px; }
/* On a phone the button takes its own line under the words, full width, the
   way D draws actions at that size. */
.a3-sharecard__share { grid-column: 1 / -1; width: 100%; }
@media (min-width: 40rem) {
  .a3-sharecard { grid-template-columns: auto minmax(0, 1fr) auto; }
  .a3-sharecard__share { grid-column: auto; width: auto; }
}

/* ---------------------------------------------------------------------------
   Explaining a charge, on demand
   --------------------------------------------------------------------------- */

/*
 * A quiet disclosure for a line the customer may want explained -- the service
 * fee on the payment summary being the one that exists today.
 *
 * Native `details`, like `.a3-share`, so it opens with no JavaScript and is
 * announced correctly by a screen reader. Deliberately LIGHTER than that
 * component: no border and no raised background, because this sits inside a
 * money table and a boxed panel there reads as another row of the bill.
 *
 * The summary is still a control and keeps the full touch target. A person
 * asking what a charge is for is often doing it on a phone, in a hurry, about
 * to spend money.
 */
.a3-explain > summary {
  cursor: pointer;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
  min-height: var(--a3-touch-min);
  display: flex;
  align-items: center;
  gap: var(--a3-space-2);
}
.a3-explain > summary:focus-visible { outline: 2px solid var(--a3-focus); outline-offset: 2px; }
.a3-explain[open] > summary { margin-bottom: var(--a3-space-2); }
.a3-explain__body {
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
  margin: 0;
}

/* -----------------------------------------------------------------------------
   The storefront hero
   Added 2026-08-30, from the Founder's mockups.

   VISION.md: "Asiam3 exists so that a business can have a place in the market."
   This is that place. Every other card on Asiam3 is Asiam3's presentation of a
   merchant; this is the merchant's own, and it is the one part of the product
   where their imagery is louder than ours.

   It carries a photograph when the merchant has given one and the shop's own
   material when they have not, so a shop with no pictures still arrives rather
   than showing a grey rectangle. `materialFor()` picks the material from the
   slug, so it is stable for the life of the address.

   Why this is a component and not a direction: it is structure, and a direction
   may declare values and nothing else. Every direction gets the hero, in its
   own colours.
----------------------------------------------------------------------------- */
.a3-hero {
  position: relative;
  /* FULL BLEED, changed 2026-08-31. This was an inset rounded card with the
     page ground showing around it. The Founder's storefront draws the hero
     edge to edge and square: a shop's own banner is the top of its page, not
     a tile placed on Asiam3's page, and the rounded inset was Asiam3's frame
     around somebody else's shop. The negative margin cancels the content
     gutter the shell applies, so nothing else on the page moves. */
  margin-inline: calc(var(--a3-page-gutter, var(--a3-space-4)) * -1);
  border-radius: 0;
  overflow: hidden;
  background: var(--a3-material, var(--a3-bg-sunken));
  color: var(--a3-text-on-material);
  isolation: isolate;

  /* A FIXED SHAPE, NOT A FIXED HEIGHT. Measured from the Founder's own
     mockup with `tools/mockupRegion.mjs`: the hero band in
     `06-storefront.png` is 904 x 600, which is 3:2.

     This is the whole of his invariant, kept. `min-height: 21rem` used to
     sit here, and a fixed HEIGHT on a full-width element is a box whose
     SHAPE changes with the window -- 1.2:1 on a phone, 3.6:1 on a laptop.
     Same picture, two different framings, and the merchant sees neither.
     A band fixed at 3:2 is 3:2 everywhere: it scales and never re-frames.

     3:2 is also what a phone camera produces, so an ordinary merchant
     photograph fills it with nothing lost. See DIRECTION_D_UNBUILT 1b. */
  aspect-ratio: 3 / 2;
  display: block;
}

/* The words, over the left of the picture. The mockup composes every hero
   this way -- subject right, words left -- and the shade below is what makes
   that readable over a photograph nobody vetted. */
.a3-hero__inner {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* LITERAL LENGTHS, not tokens. `--a3-space-9` does not exist, and one
     undefined custom property inside a clamp() invalidates the whole
     declaration -- so the padding silently became nothing and the shop name
     rendered hard against the edge of the band, clipped. Found on screen,
     2026-09-01, on a page that had been reported as ready. */
  padding-inline: clamp(1rem, 5vw, 3.4rem);
  gap: 0;
  /* A merchant writes their own summary and their own claims, and neither has
     a length Asiam3 controls. The band keeps its shape, so the overflow is
     clipped rather than allowed to break the page open. */
  overflow: hidden;
  /* The words never cover the merchant's subject, which sits right. */
  align-items: flex-start;
}

/* The merchant's photograph, drawn WHOLE inside the band.

   `contain` rather than `cover`, and the difference is the rule: a picture
   that is not 3:2 is fitted against the shop's own material rather than cut
   to fill. The material already fills the half the words sit on, so the
   margin reads as part of the banner instead of as a gap. A 3:2 photograph
   -- which is most of them -- fills the band exactly and no margin appears. */
.a3-hero__cover {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* FILLS THE BAND. `contain` was tried first and looked wrong: a 3:2
     photograph in a 2.6:1 card left grey bars down both sides, which is not
     what the mockup draws and not what a merchant wants their shop to look
     like. Seen in a browser, on 2026-09-01, rather than reasoned about.

     `cover` is safe HERE and was not safe before, and the difference is the
     band. Cropping to fill a box whose shape changes with the window shows a
     different part of the picture on every device -- the thing the Founder
     ruled out. Cropping to fill a band that is 2.6:1 at every width shows the
     SAME part everywhere. The rule was never about object-fit. */
  object-fit: cover;
  object-position: center;
  z-index: -2;
}
@media (min-width: 46rem) {
  /* The words keep to the left half on a wide screen, which is where the
     mockup puts them and what leaves the merchant's subject visible. The
     band's own proportions are set once, above, and are not touched here:
     a breakpoint that changed them would reintroduce the per-device
     re-framing this whole section exists to prevent. */
  .a3-hero__inner > * { max-width: 32rem; }
  .a3-hero__mark { max-width: none; }
}

/* `.a3-hero__photo` was here and is gone, 2026-09-01. It filled a
   fixed-HEIGHT box with `object-fit: cover`, which is the per-device
   re-framing the Founder ruled out. `.a3-hero__cover` above replaces it:
   the same full-bleed picture, in a band whose shape never changes.
   `coverFraming.test.ts` fails if either name comes back with a crop. */

/* The scrim is what makes the ink contract hold over an image nobody vetted.
   A merchant uploads their own photograph and it may be white sand or a black
   studio wall; without this the name is legible over one and invisible over
   the other, and no guard can measure a picture that does not exist yet. */
/* -----------------------------------------------------------------------------
   Two treatments, and the Founder switches between them in one line
   -----------------------------------------------------------------------------
   `coverTreatment.ts` carries the constant and the reasoning; the shell puts it
   on the document as `data-a3-cover`. Both draw the picture in the SAME band --
   the framing is his invariant and sits above this choice, never inside it.

   MOCKUP is his drawings. The shop's own material is blended through the
   photograph rather than laid over it: Fred's card is red throughout, Hana's
   pink, NewLife's olive. `multiply` is what makes it one surface rather than a
   tinted sheet on top -- the picture keeps its detail and takes the colour.

   SHADED is a dark gradient with the words on it. It reads over any photograph
   at all, and it dulls the one the merchant was proud of.
----------------------------------------------------------------------------- */
.a3-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* LEFT TO RIGHT, because that is where the words are. It was top to bottom
     while the words sat at the bottom edge. The ink contract has to hold over
     a white sand photograph and a black studio wall alike, and no guard can
     measure a picture that does not exist yet -- so the dark end is heavy and
     the clear end is where the merchant's subject is. */
  /* NO WASH BY DEFAULT. Each treatment declares its own below, so neither is
     merely "what is left when the other does not apply" -- a switch whose
     second position is an absence is one nobody can read from the source. */
}

/* SHADED: a dark gradient over the picture, with the words on the gradient.
   Reads over any photograph at all, including a white plate on a white cloth,
   and dulls the picture the merchant was proud of. */
[data-a3-cover="shaded"] .a3-hero::after {
  background: linear-gradient(90deg,
    rgba(0,0,0,.88) 0%, rgba(0,0,0,.70) 40%, rgba(0,0,0,.24) 72%, rgba(0,0,0,.04) 100%);
}

/* MOCKUP: the shop's colour through the picture, no shade. */
[data-a3-cover="mockup"] .a3-hero::after {
  background: var(--a3-material, transparent);
  mix-blend-mode: multiply;
  opacity: 0.82;
}
/* A second, very light pass so white type holds where a photograph is pale.
   It is not the old scrim: it darkens the left eighth and clears entirely by
   the middle, so the merchant's subject on the right is untouched. */
[data-a3-cover="mockup"] .a3-hero__inner {
  background: linear-gradient(90deg, rgba(0,0,0,.28) 0%, rgba(0,0,0,.10) 34%, rgba(0,0,0,0) 62%);
}

/* EVERY SIZE IN THIS BLOCK SCALES WITH THE VIEWPORT.

   The band is a fixed 3:2, so its height is decided by its width. Type at a
   fixed rem size therefore fits at one width and overflows at another, and on
   a phone the shop name ran off the edge and the claims panel was cut in
   half. Sizing the contents in the same units the band is measured in is what
   keeps the composition the Founder drew intact at every width. */
.a3-hero__eyebrow {
  display: flex;
  align-items: center;
  gap: clamp(0.4rem, 1.2vw, 0.7rem);
  font-size: clamp(0.7rem, 1.5vw, 0.82rem);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.9;
  margin: 0 0 clamp(0.35rem, 1.4vw, 0.75rem);
}
/* The short rule beside it, which the mockup draws on every hero. */
.a3-hero__eyebrow::after {
  content: "";
  height: 1px;
  width: clamp(1.5rem, 7vw, 4rem);
  background: rgba(255, 255, 255, 0.5);
}
.a3-hero__name {
  font-family: var(--a3-font-display);
  font-weight: var(--a3-weight-semibold);
  font-size: clamp(1.85rem, 9vw, 5.1rem);
  line-height: 0.98;
  letter-spacing: -0.02em;
  margin: 0;
  /* Left of the subject. Without this the name runs under the photograph's
     own content on a wide screen. */
  max-width: 62%;
}
.a3-hero__category {
  margin: clamp(0.25rem, 1vw, 0.6rem) 0 0;
  font-size: clamp(0.8rem, 2.9vw, 1.7rem);
  opacity: 0.95;
}
.a3-hero__rule {
  border: 0;
  border-top: 1px solid var(--a3-rating-mark, currentColor);
  width: clamp(1.75rem, 5.5vw, 3.1rem);
  margin: clamp(0.5rem, 1.8vw, 1.1rem) 0 0;
}
.a3-hero__summary {
  margin: clamp(0.5rem, 1.6vw, 1rem) 0 0;
  /* As wide as the name. At 42% this was 164px on a phone -- five lines of
     text in a band with room for three. */
  max-width: min(62%, 24rem);
  font-size: clamp(0.82rem, 1.95vw, 1.12rem);
  line-height: 1.45;
  opacity: 0.92;
}

/* The claims panel, as the mockup draws it: a bordered rounded box on the
   picture, the items in a ROW with hairlines between them. It stays a row at
   every width -- stacking it is what made it taller than the band on a phone
   and pushed half of it out of sight. */
.a3-hero .a3-claims__source {
  margin: clamp(0.35rem, 1.2vw, 0.7rem) 0 0;
  font-size: clamp(0.64rem, 1.3vw, 0.75rem);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.68;
}
.a3-hero__mark {
  width: 3.5rem;
  height: 3.5rem;
  border-radius: var(--a3-radius-md);
  object-fit: cover;
  background: var(--a3-bg-raised);
}

@media (min-width: 48rem) {
  .a3-hero {
    min-height: 20rem;
    padding: var(--a3-space-8) var(--a3-space-7) var(--a3-space-6);
  }
}

/* -----------------------------------------------------------------------------
   What the merchant says about their own shop
   Added 2026-08-30.

   The Founder's mockups draw a strip of three claims under a shop -- "Trusted
   Seller", "Fast Delivery", "High Quality". Asiam3 cannot make any of them:
   `metadata.test.ts` bans those exact phrases from customer pages, because
   Asiam3 does not check businesses and does not arrange delivery, and its own
   published Terms say so in as many words.

   So the strip stays and the VOICE changes. These are the merchant's words
   about their own shop, attributed to them on the page, and Asiam3 vouches for
   none of it. That is the rule the claimed name already follows under Founder
   decision G1: Asiam3 records what somebody said and does not endorse it.
----------------------------------------------------------------------------- */
.a3-claims {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--a3-space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 34rem) {
  .a3-claims {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--a3-space-4);
  }
}
.a3-claims__item {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.a3-claims__title {
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-sm);
  overflow-wrap: anywhere;
}
.a3-claims__text {
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
  overflow-wrap: anywhere;
}

/* The attribution. Not decoration: it is the sentence that makes the strip
   honest, and it is the reason the strip is allowed to exist at all. */
.a3-claims__source {
  font-size: var(--a3-text-2xs);
  color: var(--a3-text-muted);
  margin: var(--a3-space-2) 0 0;
}

/* Inside the hero the strip sits on the merchant's own ground, so it takes the
   material's ink rather than the page's. */
.a3-hero .a3-claims__text,
.a3-hero .a3-claims__source {
  color: inherit;
  opacity: 0.82;
}

/* -----------------------------------------------------------------------------
   Progressive disclosure
   Added 2026-08-30, for the Payments simplification.

   Native `details`, not a scripted accordion, because every page has to work
   with no JavaScript at all and this one carries the fee explanation a merchant
   is entitled to read before choosing. A control that hides required
   information behind script hides it from somebody.
----------------------------------------------------------------------------- */
.a3-disclose {
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-md);
  background: var(--a3-bg-raised);
  padding: 0;
}
.a3-disclose + .a3-disclose {
  margin-top: var(--a3-space-3);
}
.a3-disclose__summary {
  cursor: pointer;
  list-style: none;
  padding: var(--a3-space-4);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--a3-space-3);
  font-weight: var(--a3-weight-medium);
  min-height: var(--a3-touch-min);
}
.a3-disclose__summary::-webkit-details-marker {
  display: none;
}
.a3-disclose__summary::after {
  content: "+";
  font-family: var(--a3-font-numeric);
  color: var(--a3-text-muted);
  flex: 0 0 auto;
}
.a3-disclose[open] .a3-disclose__summary::after {
  content: "-";
}
.a3-disclose__summary:focus-visible {
  outline: var(--a3-focus-ring);
  outline-offset: -2px;
}
.a3-disclose__body {
  padding: 0 var(--a3-space-4) var(--a3-space-4);
  color: var(--a3-text-secondary);
}
.a3-disclose__hint {
  display: block;
  font-weight: var(--a3-weight-regular);
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
}

/* -----------------------------------------------------------------------------
   Steps
   Added 2026-08-30, for the Payments flow.

   An ordered list, because the order is real information and somebody using a
   screen reader should hear it. The current step carries `aria-current`, so the
   state is in the markup rather than only in the colour.
----------------------------------------------------------------------------- */
.a3-steps {
  display: flex;
  flex-wrap: wrap;
  gap: var(--a3-space-3);
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: a3-step;
}
.a3-steps__step {
  display: flex;
  align-items: center;
  gap: var(--a3-space-2);
  color: var(--a3-text-muted);
  font-size: var(--a3-text-sm);
  min-width: 0;
}
.a3-steps__step::before {
  counter-increment: a3-step;
  content: counter(a3-step);
  font-family: var(--a3-font-numeric);
  font-size: var(--a3-text-xs);
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--a3-border-strong);
  flex: 0 0 auto;
}
.a3-steps__step[aria-current="step"] {
  color: var(--a3-text);
  font-weight: var(--a3-weight-semibold);
}
.a3-steps__step[aria-current="step"]::before {
  background: var(--a3-primary-600);
  border-color: var(--a3-primary-600);
  color: var(--a3-text-on-action);
}
.a3-steps__step[data-a3-step="done"]::before {
  background: var(--a3-primary-100);
  border-color: var(--a3-primary-600);
  color: var(--a3-primary-700);
}

/* -----------------------------------------------------------------------------
   An empty state that does not look like a broken page
   Extended 2026-08-30. The figure is decorative and hidden from assistive
   technology; the words carry the meaning, exactly as they did before.
----------------------------------------------------------------------------- */
.a3-empty__figure {
  width: 100%;
  max-width: 12rem;
  height: auto;
  margin: 0 auto var(--a3-space-4);
  display: block;
  color: var(--a3-border-strong);
}


/* -----------------------------------------------------------------------------
   THE SHOP CARD, as the mockups actually draw it
   Rewritten 2026-08-30. The first build was an ADAPTATION -- a two-column card
   with a media panel beside the text and the claims on a grey strip below it.
   The Founder's drawing is a different thing: ONE photograph filling the whole
   card, everything overlaid on it, and the claims on a translucent band inside.

   The difference is not decorative. In the mockup the merchant's picture IS the
   card, which is what makes a row of shops read as a market rather than as a
   list of records with thumbnails.
----------------------------------------------------------------------------- */
.a3-shopcard {
  position: relative;
  display: block;

  /* A FIXED SHAPE, measured from the Founder's mockup with
     `tools/mockupRegion.mjs`: the cards in `01-customer-marketplace.png`
     are 897 x 342, which is 2.6:1.

     `min-height: 17.5rem` used to sit here and a fixed height is what
     re-frames a photograph between a phone and a laptop. The same argument
     as the hero, and the same fix. See DIRECTION_D_UNBUILT 1b. */
  /* THE WORDS ON THE PICTURE, AT EVERY WIDTH.

     This stacked below 46rem for a few hours on 2026-09-01 -- picture on top,
     words underneath -- and the Founder caught it immediately: "the phone own
     regressed". It was mine, from arithmetic about a 44px action in a 150px
     card, and the arithmetic was right about the space and wrong about what to
     do with it. His drawings put the words on the picture and that is the
     product; what gives way on a narrow screen is the description, not the
     composition.

     Kept for the record, because the reasoning will recur:

     his drawing is 972px wide, where a 2.6:1 card is 342px tall and everything
     fits. The same card on a 390px phone is 150px tall, and the action alone is
     44px -- the accessibility floor, which is not negotiable. So the
     description is dropped below 30rem and the rest stays exactly where he drew
     it. THE PICTURE KEEPS ITS 2.6:1 FRAMING EVERYWHERE, which is the part that
     is his invariant rather than a layout preference. */
  aspect-ratio: 2.6 / 1;
  display: flex;
  flex-direction: column;
  border-radius: var(--a3-radius-lg);
  overflow: hidden;
  background: var(--a3-material, var(--a3-bg-sunken));
  color: var(--a3-text-on-material);
  text-decoration: none;
  isolation: isolate;
}
.a3-shopcard:focus-visible { outline: var(--a3-focus-ring); outline-offset: 3px; }

/* `.a3-shopcard__photo` was here and is gone, 2026-09-01, for the reason
   `.a3-hero__photo` is: it cut the picture to fill a box whose shape changed
   with the window. `.a3-shopcard__cover` replaces it -- the same full-bleed
   photograph, in a card that is 2.6:1 everywhere, drawn whole. */
.a3-shopcard__cover {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
  /* FILLS THE BAND. `contain` was tried first and looked wrong: a 3:2
     photograph in a 2.6:1 card left grey bars down both sides, which is not
     what the mockup draws and not what a merchant wants their shop to look
     like. Seen in a browser, on 2026-09-01, rather than reasoned about.

     `cover` is safe HERE and was not safe before, and the difference is the
     band. Cropping to fill a box whose shape changes with the window shows a
     different part of the picture on every device -- the thing the Founder
     ruled out. Cropping to fill a band that is 2.6:1 at every width shows the
     SAME part everywhere. The rule was never about object-fit. */
  object-fit: cover;
  object-position: center;
  background: rgba(0, 0, 0, 0.18);
}

/* With no cover -- every shop today -- the card is its own material at exactly
   the size the photograph will take, so nothing moves when a real one arrives.
   The card's own background already does this; no extra element is needed. */

/* The mark and the name on one line, which is how the mockup opens every
   card. They were stacked while the card was a tall tile. */
.a3-shopcard__id {
  display: flex;
  align-items: center;
  gap: var(--a3-space-3);
}

/* The wash. In the mockups each card carries its own colour, pulled from the
   photograph -- deep red, dusty pink, near-black. Here it is a fixed gradient
   over whatever the merchant supplied, because a colour derived from an image
   nobody vetted is a contrast measurement nobody can make. The ink contract
   holds over a white sand photograph and a black studio wall alike. */
.a3-shopcard::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Heavier at the left than it was, because the words now sit there rather
     than across the whole card, and the merchant's subject sits right. */
  /* Declared per treatment below, for the reason the hero's is. */
}

[data-a3-cover="shaded"] .a3-shopcard::before {
  background: linear-gradient(90deg,
    rgba(0,0,0,.90) 0%, rgba(0,0,0,.74) 34%, rgba(0,0,0,.28) 64%, rgba(0,0,0,.04) 100%);
}

[data-a3-cover="mockup"] .a3-shopcard::before {
  background: var(--a3-material, transparent);
  mix-blend-mode: multiply;
  opacity: 0.82;
}
[data-a3-cover="mockup"] .a3-shopcard__body {
  background: linear-gradient(90deg, rgba(0,0,0,.30) 0%, rgba(0,0,0,.12) 40%, rgba(0,0,0,0) 72%);
}

.a3-shopcard__body {
  display: grid;
  gap: clamp(0.2rem, 1vw, 0.55rem);
  align-content: center;
  justify-items: start;
  /* FLOW, NOT ABSOLUTE. This was absolutely positioned on 2026-09-01 while
     the strip below stayed in normal flow, so both rendered at the top of the
     card and printed over each other -- the shop name, its description and
     the merchant's claims all in the same place. Caught on screen.

     The card is a flex column now: this takes the space that is left, the
     strip takes what it needs, and neither can reach the other. */
  flex: 1;
  min-height: 0;
  padding: clamp(0.7rem, 2.6vw, 1.5rem) clamp(0.75rem, 3.3vw, 2rem);
}

/* The logo tile. A square with the merchant's mark or their initials, which is
   the first thing in the mockup's reading order. */
.a3-shopcard__logo {
  /* SCALED, not fixed. A 4rem square in a 120px body left nothing for the
     name, the category and the action beneath it, and the card overflowed on
     every phone. Measured before it was changed rather than after. */
  width: clamp(1.9rem, 6.6vw, 3.75rem);
  height: clamp(1.9rem, 6.6vw, 3.75rem);
  flex: none;
  border-radius: var(--a3-radius-md);
  object-fit: cover;
  display: grid;
  place-items: center;
  font-family: var(--a3-font-display);
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-lg);
  letter-spacing: 0.02em;
  background: rgba(255,255,255,.16);
  color: inherit;
}

.a3-shopcard__name {
  font-family: var(--a3-font-display);
  font-weight: var(--a3-weight-semibold);
  font-size: clamp(1.05rem, 4.4vw, 2.5rem);
  line-height: 1.04;
  letter-spacing: -0.01em;
  margin: 0;
  overflow-wrap: anywhere;
}

/* The category chip: translucent, rounded, with the tag glyph. */
.a3-shopcard__tag {
  display: inline-flex;
  align-items: center;
  gap: var(--a3-space-1);
  font-size: clamp(0.68rem, 1.55vw, 0.88rem);
  padding: clamp(2px, 0.6vw, 6px) clamp(7px, 1.5vw, 14px);
  border-radius: 999px;
  background: rgba(255,255,255,.18);
  color: inherit;
}

.a3-shopcard__text {
  margin: 0;
  font-size: var(--a3-text-sm);
  max-width: 24ch;
  opacity: .92;
}

/* The white pill. In the mockup this is the loudest thing on the card after the
   name, and it is white on every card whatever colour the photograph is. */
.a3-shopcard__cta {
  display: inline-flex;
  align-items: center;
  gap: var(--a3-space-2);
  padding: 10px 20px;
  border-radius: 999px;
  background: var(--a3-white);
  color: var(--a3-text);
  font-weight: var(--a3-weight-medium);
  font-size: var(--a3-text-sm);
  min-height: var(--a3-touch-min);
  box-sizing: border-box;
}

/* The card's call to action as a FILLED action rather than a quiet chip, and
   full width of the card body.

   Sheet 07 draws the baskets list this way: the marketplace card invites
   ("Visit Shop >") and this one commits ("Continue this basket"). A person on
   the baskets page has already chosen the shop, so the card's action is the
   thing they came for rather than one option among a row of shops.

   The colour is the action green, which is what that green means and the only
   thing it means -- G37 records that the action signal is the filled shape. */
.a3-shopcard__cta--go {
  justify-content: center;
  width: 100%;
  background: var(--a3-primary-600);
  color: var(--a3-text-on-action);
  border-radius: var(--a3-radius-md);
}

/* An icon standing in for an illustration in an empty state.

   The pack has no basket, and drawing one is not a thing that can be done in a
   stylesheet. The icon carries the same meaning at the same size, and it stays
   decorative: `icon()` marks every glyph aria-hidden, so the words below still
   carry the whole message. */
.a3-empty__figure--icon { max-width: 7rem; }
.a3-empty__figure--icon > .a3-icon { width: 100%; height: auto; }

/* The claims band, INSIDE the card, on a darker translucent strip. Three
   columns with hairline dividers, exactly as drawn. */
.a3-shopcard__strip {
  flex: none;
  background: rgba(0,0,0,.46);
  padding: clamp(0.4rem, 1.4vw, 0.9rem) clamp(0.6rem, 2.2vw, 1.3rem);
}
/* THREE ACROSS, ALWAYS, because that is what the Founder draws and because
   stacked it is taller than the card it sits in. On a phone each gets about
   110px and the second line is dropped rather than the claim -- a title a
   customer can read beats three truncated ones. */
.a3-shopcard__strip .a3-claims {
  grid-template-columns: repeat(3, 1fr);
  gap: 0;
}
.a3-shopcard__strip .a3-claims__title { font-size: clamp(0.7rem, 1.45vw, 0.86rem); }
.a3-shopcard__strip .a3-claims__text { font-size: clamp(0.64rem, 1.3vw, 0.78rem); }
/* One line each, but only where the three sit side by side. On a phone they
   are stacked with the full width available, and truncating there loses words
   for no reason. */
@media (min-width: 30rem) {
  .a3-shopcard__strip .a3-claims__title,
  .a3-shopcard__strip .a3-claims__text {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}
.a3-shopcard__strip .a3-claims__text,
.a3-shopcard__strip .a3-claims__source { color: inherit; opacity: .78; }
.a3-shopcard__strip .a3-claims__item {
  grid-template-columns: auto 1fr;
  column-gap: var(--a3-space-2);
  align-items: center;
}
.a3-shopcard__strip .a3-icon { grid-row: span 2; opacity: .9; }

@media (min-width: 30rem) {
  .a3-shopcard__strip .a3-claims {
    grid-template-columns: repeat(3, 1fr);
    gap: 0;
  }
  /* The hairline dividers between the three. */
  .a3-shopcard__strip .a3-claims__item + .a3-claims__item {
    border-left: 1px solid rgba(255,255,255,.22);
    padding-left: var(--a3-space-4);
    margin-left: var(--a3-space-4);
  }
}

/* The rating pill sits on the card's own corner rather than inside a media box,
   because the media box is now the whole card. */
.a3-shopcard .a3-reputation--pill {
  position: absolute;
  top: var(--a3-space-4);
  right: var(--a3-space-4);
  z-index: 1;
  background: rgba(0,0,0,.55);
  color: var(--a3-white);
}
.a3-shopcard .a3-reputation__mark { color: var(--a3-rating-mark); }

/* -----------------------------------------------------------------------------
   THE PRODUCT ROW
   The storefront list in the mockups: picture on the left, everything else on
   the right, price large, one clear action.
----------------------------------------------------------------------------- */
.a3-productrow {
  display: grid;
  grid-template-columns: 7.5rem minmax(0, 1fr);
  gap: var(--a3-space-4);
  padding: var(--a3-space-4);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-md);
  background: var(--a3-bg-raised);
  color: inherit;
  text-decoration: none;
  align-items: center;
}
.a3-productrow:focus-visible {
  outline: var(--a3-focus-ring);
  outline-offset: 2px;
}
.a3-productrow__media {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: var(--a3-radius-sm);
  overflow: hidden;
  background: var(--a3-material, var(--a3-bg-sunken));
}
.a3-productrow__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.a3-productrow__body {
  display: grid;
  gap: var(--a3-space-1);
  min-width: 0;
}
.a3-productrow__name {
  font-family: var(--a3-font-display);
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-lg);
  margin: 0;
  overflow-wrap: anywhere;
}
.a3-productrow__shop {
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
}
.a3-productrow__price {
  font-family: var(--a3-font-body);
  font-size: var(--a3-text-xl);
  font-weight: var(--a3-weight-semibold);
  margin-top: var(--a3-space-2);
}
.a3-productrow__actions {
  display: flex;
  gap: var(--a3-space-2);
  margin-top: var(--a3-space-2);
  flex-wrap: wrap;
}

@media (min-width: 40rem) {
  .a3-productrow {
    grid-template-columns: 10rem minmax(0, 1fr);
    gap: var(--a3-space-5);
    padding: var(--a3-space-5);
  }
}

/* -----------------------------------------------------------------------------
   THE PAGE BANNER
   "Shops on Asiam3", "Your orders" -- a heading with room to breathe and a
   place for an illustration, which is what makes the mockups feel unhurried.
----------------------------------------------------------------------------- */
.a3-banner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--a3-space-4);
  align-items: center;
  padding: var(--a3-space-6) var(--a3-space-5);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-primary-100);
  margin-bottom: var(--a3-space-5);
}
.a3-banner__title {
  font-family: var(--a3-font-display);
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-2xl);
  letter-spacing: -0.02em;
  margin: 0 0 var(--a3-space-2);
  text-wrap: balance;
}
.a3-banner__text {
  margin: 0;
  color: var(--a3-text-secondary);
  max-width: 46ch;
}
.a3-banner__art {
  width: 100%;
  max-width: 9rem;
  height: auto;
  justify-self: center;
  color: var(--a3-primary-600);
}

@media (min-width: 40rem) {
  .a3-banner {
    grid-template-columns: minmax(0, 1fr) auto;
    padding: var(--a3-space-7) var(--a3-space-7);
  }
  .a3-banner__art { max-width: 11rem; justify-self: end; }
}

/* -----------------------------------------------------------------------------
   THE PANEL
   The signed-out pages in the mockups: one card, generous padding, the form
   inside it. It exists so authentication does not look like a settings screen.
----------------------------------------------------------------------------- */
.a3-panel {
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-bg-raised);
  padding: var(--a3-space-6) var(--a3-space-5);
  display: grid;
  gap: var(--a3-space-5);
  max-width: 34rem;
  margin: 0 auto;
}
.a3-panel__head {
  display: grid;
  gap: var(--a3-space-2);
}
.a3-panel__title {
  font-family: var(--a3-font-display);
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-2xl);
  letter-spacing: -0.02em;
  margin: 0;
  text-wrap: balance;
}
.a3-panel__sub {
  margin: 0;
  color: var(--a3-text-secondary);
}

@media (min-width: 40rem) {
  .a3-panel { padding: var(--a3-space-7); }
}

/* -----------------------------------------------------------------------------
   THE LIST ROW
   The three ways on from the sign-in page. A link that reads as a destination
   rather than as a sentence with an underline in it.
----------------------------------------------------------------------------- */
.a3-listrow {
  display: flex;
  align-items: center;
  gap: var(--a3-space-3);
  padding: var(--a3-space-4) var(--a3-space-2);
  border-top: 1px solid var(--a3-border);
  color: inherit;
  text-decoration: none;
  min-height: var(--a3-touch-min);
}
.a3-listrow:first-of-type { border-top: 0; }
.a3-listrow:focus-visible { outline: var(--a3-focus-ring); outline-offset: -2px; }
.a3-listrow__body { display: grid; gap: 2px; min-width: 0; flex: 1 1 auto; }
.a3-listrow__title { font-weight: var(--a3-weight-medium); }
.a3-listrow__text { font-size: var(--a3-text-sm); color: var(--a3-link); }
.a3-listrow__chev { color: var(--a3-text-muted); flex: 0 0 auto; }

/* -----------------------------------------------------------------------------
   THE RATING PILL
   Added 2026-08-30, from the Founder's mockups, which put a dark pill with a
   star and a figure on the corner of every shop card.

   ONE STAR, AS A MARK. Not a row of five with one half-filled -- that is the
   thing `design/README.md` calls "a mean with extra steps", and it is absent
   for the same reason the numeric average is: a mean plus a count is solvable,
   so a merchant watching the pair move can work out exactly what a new customer
   gave them, and on a shop with few customers, who gave it.

   So the pill carries the star and the BAND: a glance reads the same as the
   mockup, and the number behind it cannot be reversed into one person's
   opinion. The star is decorative and hidden from assistive technology; the
   band is the content and is announced.

   This is the distinction to hold if anyone revisits it: a star used as an ICON
   is presentation, and a star used as a SCALE is a mean. The first is welcome
   and the second is the leak.
----------------------------------------------------------------------------- */
.a3-reputation--pill {
  display: inline-flex;
  align-items: center;
  gap: var(--a3-space-1);
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--a3-bg-inverse);
  color: var(--a3-text-inverse);
  font-size: var(--a3-text-xs);
  white-space: nowrap;
}
.a3-reputation--pill .a3-reputation__band {
  color: inherit;
  font-size: var(--a3-text-xs);
}
.a3-reputation--pill .a3-reputation__volume {
  color: inherit;
  opacity: 0.75;
}
.a3-reputation__mark {
  flex: 0 0 auto;
  line-height: 1;
}



/* -----------------------------------------------------------------------------
   Icons
   Drawn in `surfaces/shared/icons.ts`. Every glyph is decorative and sits beside
   a real label -- see that file for why, and for the rule about when an icon is
   wrong rather than the label being missing.
----------------------------------------------------------------------------- */
.a3-icon {
  width: var(--a3-icon-md);
  height: var(--a3-icon-md);
  flex: 0 0 auto;
  /* Optical alignment with the text it sits beside. A glyph on the text
     baseline sits slightly high against lowercase, which reads as a wobble in a
     row of them. */
  vertical-align: -0.18em;
}
.a3-icon--sm { width: var(--a3-icon-sm); height: var(--a3-icon-sm); }
.a3-icon--lg { width: var(--a3-icon-lg); height: var(--a3-icon-lg); }

/* An icon inside a button or a row takes the ink of what it belongs to, which
   `currentColor` already does -- this is only the spacing. */
.a3-btn .a3-icon { margin-right: var(--a3-space-2); }
/* The chevron and any bare glyph stay muted. The one inside a tile does not:
   it takes the tile's own colour, which is what makes the tile read as a
   destination rather than as a grey box. */
.a3-listrow .a3-icon { color: var(--a3-text-muted); }
.a3-listrow__mark .a3-icon { color: inherit; }

/* -----------------------------------------------------------------------------
   THE WAVE AT THE FOOT OF AN AUTHENTICATION PANEL
   Decoration. It sits behind the content, spans the panel, and is clipped by
   the panel's own rounded corner. Sized in rem so it holds its proportion when
   a person has increased their text size, which is a setting this product is
   required to survive rather than to assume away.
   -------------------------------------------------------------------------- */
.a3-panel { position: relative; overflow: hidden; }
/* THE COMMITTING BUTTON SPANS THE PANEL, which is what the mockups draw and
   what a thumb needs. Scoped to the form inside an authentication panel rather
   than given a modifier class of its own, following `.a3-confirm__actions`
   above: this repository sizes a button by where it sits, and a second way of
   saying "full width" is a second rule to keep in step. */
.a3-panel > form .a3-btn--primary { width: 100%; }
/* Everything except the wave paints above it. Stated rather than left to
   source order, because an absolutely positioned child would otherwise paint
   over the last row of links. */
.a3-panel > * { position: relative; z-index: 1; }
.a3-panel__wave {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 5rem;
  z-index: 0;
  pointer-events: none;
}
.a3-panel__wave svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: var(--a3-primary-100);
  opacity: 0.55;
}

/* -----------------------------------------------------------------------------
   THE PAGE CHROME, as the mockups draw it
   Added 2026-08-30. Direction D only in appearance, shared in structure -- these
   are component rules consuming semantic tokens, so A, B and C get the same
   arrangement in their own colours.

   The band already carried exactly the right CONTENT: the mark, the surface
   name, the page title, and who you are signed in as. The mockups draw those
   four things in a particular arrangement, and that arrangement is all that
   changes here. No element was added to the shell, because a header control
   that leads nowhere is a dead control, and the shell says so about its own
   footer in as many words.
----------------------------------------------------------------------------- */
.a3-authority {
  display: grid;
  grid-template-columns: auto auto 1fr;
  grid-template-areas:
    "mark surface title"
    "basis basis basis";
  align-items: center;
  column-gap: var(--a3-space-3);
  row-gap: 2px;
  padding: var(--a3-space-3) var(--a3-space-4);
}
.a3-authority .a3-brand { grid-area: mark; }
.a3-authority__surface { grid-area: surface; }
.a3-authority__scope { grid-area: title; }
.a3-authority__basis { grid-area: basis; font-size: var(--a3-text-xs); }

/* The wordmark is the loudest thing in the row, in the surface's own colour --
   the market's green on a customer page, the operating environment's magenta on
   a merchant one, which is what the mockups draw and what `--a3-surface-accent`
   already exists to carry. */
.a3-authority .a3-brand[data-a3-brand="pending"] {
  color: var(--a3-surface-accent, var(--a3-primary-600));
  font-size: var(--a3-text-xl);
  letter-spacing: -0.02em;
}
.a3-authority .a3-brand[data-a3-brand="pending"]::after { display: none; }

.a3-authority__scope {
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-sm);
}

@media (min-width: 40rem) {
  .a3-authority { padding: var(--a3-space-3) var(--a3-space-6); }
}

/* -----------------------------------------------------------------------------
   The tab row
   Shops / Sign in / Create an account, with the current one underlined in the
   surface's colour. The underline is the mockups' own affordance and it does
   the job `aria-current` already does for a screen reader.
----------------------------------------------------------------------------- */
.a3-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--a3-space-5);
  padding: 0 var(--a3-space-4);
  background: var(--a3-bg-raised);
  border-bottom: 1px solid var(--a3-border);
  overflow-x: auto;
}
.a3-nav__link {
  display: inline-flex;
  align-items: center;
  gap: var(--a3-space-2);
  padding: var(--a3-space-4) 0;
  min-height: var(--a3-touch-min);
  border-bottom: 2px solid transparent;
  color: var(--a3-text-secondary);
  text-decoration: none;
  white-space: nowrap;
  background: none;
  border-left: 0; border-right: 0; border-top: 0;
  font: inherit;
  cursor: pointer;
}
.a3-nav__link:hover { color: var(--a3-text); }
.a3-nav__link[aria-current="page"] {
  color: var(--a3-surface-accent, var(--a3-primary-600));
  border-bottom-color: var(--a3-surface-accent, var(--a3-primary-600));
  font-weight: var(--a3-weight-medium);
}
.a3-nav__link:focus-visible { outline: var(--a3-focus-ring); outline-offset: -2px; }
.a3-nav__form { display: contents; }

@media (min-width: 40rem) {
  .a3-nav { padding: 0 var(--a3-space-6); }
}

/* -----------------------------------------------------------------------------
   The search band
   A soft green ground, a pill input with the glyph inside it, and a pill button.
----------------------------------------------------------------------------- */
.a3-masthead {
  background: var(--a3-primary-100);
  padding: var(--a3-space-5) var(--a3-space-4);
}
.a3-masthead .a3-row {
  display: flex;
  gap: var(--a3-space-3);
  align-items: center;
  max-width: 56rem;
  margin: 0 auto;
}
.a3-masthead .a3-search__field {
  flex: 1 1 auto;
  min-width: 0;
  position: relative;
  display: flex;
  align-items: center;
}
.a3-masthead .a3-input {
  border-radius: 999px;
  padding-left: var(--a3-space-5);
  min-height: var(--a3-touch-min);
  background: var(--a3-bg-raised);
  border-color: transparent;
  width: 100%;
}
.a3-masthead .a3-btn {
  border-radius: 999px;
  padding-left: var(--a3-space-6);
  padding-right: var(--a3-space-6);
  min-height: var(--a3-touch-min);
  white-space: nowrap;
}

@media (min-width: 40rem) {
  .a3-masthead { padding: var(--a3-space-6); }
}

/* -----------------------------------------------------------------------------
   The footer
   Already the mockups' content exactly -- Asiam3, the parent company, then the
   two legal links. This only sets the arrangement.
----------------------------------------------------------------------------- */
.a3-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--a3-space-2) var(--a3-space-4);
  padding: var(--a3-space-6) var(--a3-space-4);
  margin-top: var(--a3-space-8);
  background: var(--a3-bg-raised);
  border-top: 1px solid var(--a3-border);
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
}
.a3-footer > span:first-child {
  color: var(--a3-surface-accent, var(--a3-primary-600));
  font-weight: var(--a3-weight-semibold);
}
/* A FOOTER LINK IS A TARGET, NOT A WORD IN A SENTENCE.
   Added 2026-09-02, from a measurement rather than a review. The eye reported
   these at 45x20 and 36x20 on both a phone and a laptop, and ACCESSIBILITY.md
   section 5 claimed "the wordmark and links inside a sentence are 24px or a
   little more, which is WCAG 2.2 AA's minimum and its inline exception."

   Neither half held here. Twenty is under the 24 the sentence claims, and the
   inline exception does not cover these: WCAG 2.2 exempts a target inside a
   SENTENCE or a block of text, and this footer is a row of separate links with
   nothing running through it. The claim was accurate about buttons and inputs
   and wrong about this row, which nothing had ever measured.

   A minimum height with centring, rather than padding, so the target grows
   without moving the words or reflowing the row. */
.a3-footer a {
  color: var(--a3-link);
  display: inline-flex;
  align-items: center;
  min-height: var(--a3-target-inline-min);
}

@media (min-width: 40rem) {
  .a3-footer { padding: var(--a3-space-6); }
}

/* -----------------------------------------------------------------------------
   The account control in the authority band
   Added 2026-08-30. The mockups put a person glyph at the top right of every
   customer page. It is a real destination -- sign-in, or your orders -- rather
   than a decoration, which is the only reason it is here at all.

   The touch target is the whole box rather than the glyph, because a 20px icon
   is not a target anybody can hit on a phone.
----------------------------------------------------------------------------- */
.a3-authority__control {
  grid-area: control;
  justify-self: end;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--a3-touch-min);
  min-height: var(--a3-touch-min);
  border-radius: 999px;
  color: var(--a3-text-secondary);
  text-decoration: none;
}
.a3-authority__control:hover { color: var(--a3-text); }
.a3-authority__control:focus-visible { outline: var(--a3-focus-ring); outline-offset: -2px; }
.a3-authority__control .a3-icon { width: var(--a3-icon-lg); height: var(--a3-icon-lg); }

/* The band gains a fourth column for it. Stated here rather than in the band's
   own rule so the two stay together: a grid area without a column is an element
   that silently lands in the wrong cell. */
.a3-authority {
  grid-template-columns: auto auto 1fr auto;
  grid-template-areas:
    "mark surface title control"
    "basis basis basis basis";
}

/* -----------------------------------------------------------------------------
   The page header inside the content
   The mockups introduce a section head: a circular glyph tile, the heading, and
   one line saying what the page is for. It sits inside `main`, below the band,
   which is where the mockups put it.
----------------------------------------------------------------------------- */
.a3-pagehead {
  display: flex;
  align-items: center;
  gap: var(--a3-space-4);
  margin-bottom: var(--a3-space-5);
}
.a3-pagehead__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: 999px;
  background: var(--a3-primary-100);
  color: var(--a3-primary-700);
  flex: 0 0 auto;
}
.a3-pagehead__mark .a3-icon { width: var(--a3-icon-lg); height: var(--a3-icon-lg); }
.a3-pagehead h1 { margin: 0; }
.a3-pagehead__text {
  margin: 2px 0 0;
  color: var(--a3-text-muted);
  font-size: var(--a3-text-sm);
}

/* -----------------------------------------------------------------------------
   Tone: light ground, dark ink
   Added 2026-08-30, from the Founder's own reading of his mockups -- two cards
   carry a dark photograph and a white pill, and the pale one carries a black
   pill. A white pill on a pale card is a control nobody can see.

   Only the ground and the ink change. Nothing moves, nothing is hidden, and the
   card is the same markup either way, so the tone cannot become a second layout
   wearing a different colour.
----------------------------------------------------------------------------- */
[data-a3-tone="light"] {
  color: var(--a3-text);
}
/* The scrim lightens instead of darkening, so a pale material stays pale. */
.a3-shopcard[data-a3-tone="light"]::before,
.a3-hero[data-a3-tone="light"]::after {
  background: linear-gradient(100deg, rgba(255,255,255,.82) 0%, rgba(255,255,255,.58) 45%, rgba(255,255,255,.18) 100%);
}
.a3-hero[data-a3-tone="light"]::after {
  background: linear-gradient(180deg, rgba(255,255,255,.24) 0%, rgba(255,255,255,.62) 55%, rgba(255,255,255,.86) 100%);
}
/* The pill inverts: dark on a light card, which is what the mockup draws. */
[data-a3-tone="light"] .a3-shopcard__cta {
  background: var(--a3-bg-inverse);
  color: var(--a3-text-inverse);
}
[data-a3-tone="light"] .a3-shopcard__logo,
[data-a3-tone="light"] .a3-shopcard__tag {
  background: rgba(0,0,0,.10);
}
[data-a3-tone="light"] .a3-shopcard__strip {
  background: rgba(0,0,0,.06);
}
[data-a3-tone="light"] .a3-shopcard__strip .a3-claims__item + .a3-claims__item {
  border-left-color: rgba(0,0,0,.18);
}

/* -----------------------------------------------------------------------------
   The icon gallery
   A merchant choosing a glyph for one of their own highlights.

   Radios rather than a scripted picker: every page works with no JavaScript,
   and a merchant decorating their shop is not a reason to make an exception.
   The radio itself is hidden from sight but not from the keyboard or from a
   screen reader -- each choice carries the glyph's name in visually hidden text,
   because a person who cannot see the drawing still has to be able to pick one.
----------------------------------------------------------------------------- */
.a3-gallery {
  display: flex;
  flex-wrap: wrap;
  gap: var(--a3-space-2);
  border: 0;
  padding: 0;
  margin: var(--a3-space-2) 0 0;
}
.a3-gallery legend { padding: 0; }
.a3-gallery__choice {
  display: inline-flex;
  cursor: pointer;
}
/* Off-screen rather than display:none, which would take it out of the tab
   order and leave the gallery unusable from a keyboard. */
.a3-gallery__choice input {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap; border: 0;
}
.a3-gallery__glyph,
.a3-gallery__none {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--a3-touch-min);
  min-height: var(--a3-touch-min);
  padding: 0 var(--a3-space-2);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-sm);
  background: var(--a3-bg-raised);
  color: var(--a3-text-secondary);
  font-size: var(--a3-text-xs);
}
.a3-gallery__choice input:checked + .a3-gallery__glyph,
.a3-gallery__choice input:checked + .a3-gallery__none {
  border-color: var(--a3-primary-600);
  background: var(--a3-primary-100);
  color: var(--a3-primary-700);
}
.a3-gallery__choice input:focus-visible + .a3-gallery__glyph,
.a3-gallery__choice input:focus-visible + .a3-gallery__none {
  outline: var(--a3-focus-ring);
  outline-offset: 2px;
}

/* -----------------------------------------------------------------------------
   The shop grid on a wider screen
   Added 2026-08-30. The Founder's mockups are all phone-width, so the shop card
   was built to a phone. On a desktop the default grid packs it into 15rem
   tracks, which is narrower than the card's own content, and the general grid
   cannot be widened for everything -- a product tile is meant to be small.

   `min(26rem, 100%)` rather than a bare `26rem`: a fixed minimum wider than the
   viewport makes the track overflow the screen, which is the classic way an
   `auto-fill` grid breaks on the narrowest phone rather than on the desktop it
   was written for.

   One capability, adapted presentation -- `design/README.md`. A phone gets one
   card per row and a desktop gets two, and it is the same card.
----------------------------------------------------------------------------- */
/* ONE PER ROW. The Founder's marketplace draws wide banners stacked, not a
   grid of tiles -- `01-customer-marketplace.png`. A row of 2.6:1 banners
   side by side would be unreadable at any width a phone has. */
.a3-grid--shops {
  grid-template-columns: 1fr;
}

/* -----------------------------------------------------------------------------
   The card as the mockup draws it, once there is room for it
   -----------------------------------------------------------------------------
   From here the photograph fills the card and the words sit on its left, over
   the shade -- `01-customer-marketplace.png`. 46rem is where the arithmetic
   above turns: the card is then 2.6:1 and about 283px tall, which holds the
   mark, the name, the category, the description and a 44px action with room
   to spare. */
/* The description is what gives way on the narrowest screens. The mark, the
   name, the category and the action are the card; a sentence is the part a
   customer can do without when there is no room for it. */
/* WHAT GIVES WAY ON A PHONE, and it is not the composition.

   Measured at 390px: the card is 150px, the action alone is 44px, and the
   mark, name and category need 57 more. The claims strip needs 40 across or
   119 stacked. Something has to go, and the Founder was explicit that the
   words belong ON the picture -- so the description and the claims give way
   and his composition does not.

   The claims are the merchant's own voice and losing them is a real cost.
   The alternative is to let the strip sit BELOW the picture, which keeps them
   and keeps the picture at 2.6:1, at the price of a card that is a different
   height on a phone than in the drawing. Left as his call rather than taken. */
@media (max-width: 30rem) {
  /* THE WHOLE CARD, SMALLER. Nothing moves out and nothing is dropped.

     Every compromise before this one rested on a wrong reading: that the
     "Visit Shop" pill had to be 44px because a finger taps it. It does not.
     `.a3-shopcard` is the `<a>`; the pill is a `<span>` inside it. The touch
     target is the card -- 150px tall on a phone -- so the pill is decoration
     and may be any size that reads. `--a3-touch-min` on it was over-cautious.

     With that freed, everything the Founder drew fits inside a 2.6:1 card at
     390px: the mark and name, the category, one line of description, the
     action, and the claims strip along the bottom edge. Measured, not hoped:
     ~91px of content in the ~102px the body has after the strip.

     And the picture stays 2.6:1, so a phone and a laptop frame it identically,
     which is the Founder's own invariant. */
  .a3-shopcard__logo { width: 1.6rem; height: 1.6rem; border-radius: 0.4rem; font-size: 0.6rem; }
  .a3-shopcard__name { font-size: 1.05rem; }
  .a3-shopcard__tag { font-size: 0.56rem; padding: 1px 7px; }
  .a3-shopcard__body { gap: 0.18rem; padding: 0.5rem 34% 0.4rem 0.7rem; }
  /* One line, so a merchant who writes at length cannot push the action out. */
  .a3-shopcard__text {
    /* 11px, the smallest type anyone reads on a phone (tools/eye/look.mjs's
       floor). It was 0.62rem, about 10px, and the look tool flagged every card
       on 26 September 2026. One line, so it costs the card ~1.4px of the ~11px
       it has spare, and the 2.6:1 frame is unchanged. */
    font-size: var(--a3-text-2xs);
    line-height: 1.25;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
  }
  /* A span inside the link, not a control. See above. */
  .a3-shopcard__cta {
    min-height: 0;
    padding: 5px 12px;
    font-size: 0.62rem;
    gap: 0.25rem;
  }
  .a3-shopcard__cta .a3-icon { width: 0.7rem; height: 0.7rem; }
  .a3-shopcard__strip { padding: 0.3rem 0.6rem; }
  /* The second line of each claim goes; the title stays. Three readable
     titles beat three truncated pairs. */
  .a3-shopcard__strip .a3-claims__text { display: none; }
  .a3-shopcard__strip .a3-claims__title { font-size: 0.56rem; }
  .a3-shopcard__strip .a3-icon { width: 0.75rem; height: 0.75rem; }
}

/* -----------------------------------------------------------------------------
   The mark in the brand box
   The box above was reserved before there was a mark. This fills it without
   changing it: the image is bounded by the reserved height and keeps its own
   proportions, so nothing around it moves.
----------------------------------------------------------------------------- */
.a3-brand[data-a3-brand="mark"] { display: inline-flex; align-items: center; }
/* Larger than the reserved box, on the Founder's word, 25 September 2026: "the
   Asiam3 logo on the site is too tiny". The box still reserves its minimum. */
:root { --a3-brand-mark-height: 44px; --a3-brand-mark-height-compact: 34px; }
.a3-brand__mark {
  height: var(--a3-brand-mark-height);
  width: auto;
  max-width: var(--a3-brand-max-width);
  display: block;
}
@media (max-width: 640px) {
  .a3-brand__mark { height: var(--a3-brand-mark-height-compact); }
}

/* -----------------------------------------------------------------------------
   Media never overflows the page
   Added 2026-08-30, when the brand mark became a real image.

   `:where()` so it carries no specificity: any component that needs a different
   size simply states one and wins, without a fight or an `!important`.

   This is what makes it safe for an image to declare its intrinsic `width` and
   `height`. Those attributes are how a browser reserves the right space before
   the bytes arrive -- without them a page jumps as each picture lands -- and
   `crossDevice.test.ts` exempts them from its fixed-width rule ONLY because this
   rule exists. The two are a pair; removing this one re-arms that guard.
----------------------------------------------------------------------------- */
:where(img, picture, svg, video) {
  max-width: 100%;
}
:where(img, video) {
  height: auto;
}

/* -----------------------------------------------------------------------------
   Illustrations
   The rendered artwork from the brand pack. Decorative in every case -- the
   words beside them carry the meaning, which is why they are `aria-hidden` in
   the markup rather than given alt text describing a shopping bag.
----------------------------------------------------------------------------- */
.a3-illustration {
  display: block;
  max-width: 100%;
  height: auto;
}

/* Beside the heading on the signed-out pages, as the mockups draw it, and above
   it on a phone where there is no room for two columns. */
.a3-panel__head--art {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--a3-space-3);
  align-items: center;
}
.a3-panel__art {
  display: block;
  width: 100%;
  max-width: 9rem;
  height: auto;
  justify-self: center;
}
@media (min-width: 30rem) {
  .a3-panel__head--art { grid-template-columns: 1fr auto; }
  .a3-panel__art { max-width: 10rem; justify-self: end; }
}

/* -----------------------------------------------------------------------------
   A notification row
   The mockups draw each as a glyph tile, the words, and the state on the right.
   The state is a pill AND a word, never a colour alone -- somebody who cannot
   distinguish the two pills still reads "New" or "Seen".
----------------------------------------------------------------------------- */
.a3-notice-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--a3-space-3);
  align-items: start;
  padding: var(--a3-space-4);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-md);
  background: var(--a3-bg-raised);
}
.a3-notice-row + .a3-notice-row { margin-top: var(--a3-space-3); }
/* ONE TREATMENT, TWO COMPONENTS, and deliberately not a third name for it.
   A glyph in a round tile appears on the notice row and on the sign-in list
   row, and Article 22 forbids a second definition of a concept the repository
   already holds. The names stay component-scoped, because a class is where it
   is used; the DECLARATIONS are shared, because there is one idea here.
   Written after finding a3-notice-row__glyph invented for this exact role in
   businessRoutes.ts, which rendered unstyled because nothing defined it. */
.a3-notice-row__mark,
.a3-listrow__mark,
.a3-sharecard__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 999px;
  background: var(--a3-primary-100);
  color: var(--a3-primary-700);
  flex: 0 0 auto;
}
/* The list row's tile is the larger of the two. The mockups draw it holding a
   destination rather than a status, and it is the tap target on a phone. */
.a3-listrow__mark,
.a3-sharecard__mark { width: 3rem; height: 3rem; }
.a3-notice-row__body { display: grid; gap: 2px; min-width: 0; }
/* Green, as 10-merchant-notifications draws it -- not the link blue, which a
   merchant reads as "unread" on every row (the Founder, 2 October 2026). */
.a3-notice-row__title {
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-primary-700);
  text-decoration: none;
  overflow-wrap: anywhere;
}
/* Seen: plain text, so only what is new stands out. */
.a3-notice-row--seen .a3-notice-row__title {
  font-weight: var(--a3-weight-medium);
  color: var(--a3-text);
}
.a3-notice-row__title:hover { text-decoration: underline; }
.a3-notice-row__state {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--a3-space-3);
  flex-wrap: wrap;
}

@media (min-width: 40rem) {
  .a3-notice-row { grid-template-columns: auto minmax(0, 1fr) auto; }
  .a3-notice-row__state { grid-column: auto; justify-content: flex-end; }
}

/* -----------------------------------------------------------------------------
   THE NOTIFICATION LIST AT DESKTOP WIDTH.

   NO SHEET DRAWS THIS PAGE. All three merchant sheets put a bell in the top bar
   and none carries a notifications screen, which the handover already records as
   a `G68` KEEP -- Asiam3 has it and the brief never mentioned it. So the row
   below is not conformance to a drawing; it is the second half of `G68`, which
   asks what the page NEEDS that nobody drew.

   MEASURED SIGNED IN, ON A BUSINESS THAT HAS SOME: seventeen rows, each 1151px
   wide, each carrying about forty characters stacked at the left edge with the
   state and the control at the right and roughly seven hundred pixels of nothing
   between them. That is `ENGINEERING_FOUNDATION.md` section 2's "a desktop
   shrunk down" with the arrow pointing the other way -- a phone column stretched
   to a workspace.

   SO THE ROW BECOMES ONE LINE, which is what the merchant surface already does
   with an order row and a product row above 60rem. `display: contents` lifts the
   title and the time out of their stack and into the row's own grid, and the
   time takes a column of its own instead of a third line.

   AND THE INSTRUCTION GOES, BUT ONLY THE INSTRUCTION. Every `order.placed` body
   is the same sentence, so seventeen of them is sixteen repeats of something a
   merchant read once. `notification.ts` owns which bodies may be dropped and
   says why at length: one points at a record, the other is the only warning a
   merchant gets that their money has been redirected, and it is time-bounded.
   The class `a3-notice-row__where` is the surface repeating that decision, not
   making it -- a rule written against `.a3-card-link__meta` would take the alarm
   with the instruction, and the page would look right with the warning gone.
   --------------------------------------------------------------------------- */
@media (min-width: 60rem) {
  .a3-notice-row {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: center;
    column-gap: var(--a3-space-4);
  }

  /* The title and the time become cells of the row rather than a stack inside
     one cell. The essential body, where there is one, keeps its own line by
     spanning -- a warning is not a column. */
  .a3-notice-row__body { display: contents; }
  .a3-notice-row__title { grid-column: 2; }
  .a3-notice-row__when { grid-column: 3; justify-self: end; white-space: nowrap; }
  .a3-notice-row__state { grid-column: 4; }

  /* An essential body takes a LINE, not a column. It is a sentence a merchant
     has to read, and the columns beside it hold a timestamp and a button. */
  .a3-notice-row__body > .a3-card-link__meta:not(.a3-notice-row__when) {
    grid-column: 2 / -1;
    grid-row: 2;
  }

  /* Where to look, on a page showing seventeen rows at once. */
  .a3-notice-row__where { display: none; }
}

/* -----------------------------------------------------------------------------
   The search glyph inside the field
   The mockups put it there rather than on the button, which is where a person
   looks for it. The label stays visually hidden and still names the field, and
   the glyph is decoration beside it.

   `padding-inline-start` rather than a left padding, so the glyph moves with the
   direction the page reads in.
----------------------------------------------------------------------------- */
.a3-search__field { position: relative; display: flex; align-items: center; }
.a3-search__glyph {
  position: absolute;
  inset-inline-start: var(--a3-space-4);
  display: inline-flex;
  color: var(--a3-text-muted);
  pointer-events: none;
}
.a3-input--search { padding-inline-start: calc(var(--a3-space-4) * 2 + var(--a3-icon-md)); }
/* And in the masthead too, where `.a3-masthead .a3-input` sets its own left
   padding and, being more specific, used to win: the glyph sat on the first
   letter ("Qadepa") on every market page until 26 September 2026. */
.a3-masthead .a3-input--search { padding-left: calc(var(--a3-space-4) * 2 + var(--a3-icon-md)); }

/* -----------------------------------------------------------------------------
   The menu control
   A hamburger that genuinely opens something, on the screens where the tab row
   would otherwise scroll sideways and hide destinations past the edge.

   Above the breakpoint the tabs are simply visible and the control is gone. The
   list is revealed regardless of the disclosure's open state there, so a desktop
   never has navigation hidden behind a control it does not need -- which is the
   dishonest version of this pattern and the common one.
----------------------------------------------------------------------------- */
.a3-menu {
  background: var(--a3-bg-raised);
  border-bottom: 1px solid var(--a3-border);
}
.a3-menu__toggle {
  display: flex;
  align-items: center;
  gap: var(--a3-space-2);
  padding: var(--a3-space-3) var(--a3-space-4);
  min-height: var(--a3-touch-min);
  cursor: pointer;
  list-style: none;
  color: var(--a3-text-secondary);
}
.a3-menu__toggle::-webkit-details-marker { display: none; }
.a3-menu__toggle::after {
  content: "Menu";
  font-size: var(--a3-text-sm);
}
.a3-menu[open] .a3-menu__toggle::after { content: "Close"; }
.a3-menu__toggle:focus-visible { outline: var(--a3-focus-ring); outline-offset: -2px; }
.a3-menu .a3-nav { border-bottom: 0; }

@media (min-width: 48rem) {
  /* The tabs stand on their own. `display` is forced because a closed
     `details` hides its content in the user agent stylesheet, and above this
     width there is no control left to open it with. */
  .a3-menu__toggle { display: none; }
  /* A closed `details` hides its content on the ::details-content pseudo, not
     on the child, so setting `display` on the nav alone left a desktop with NO
     NAVIGATION AT ALL -- which is worse than the hamburger this exists to
     avoid, and is what a 1280px capture showed. Both are needed: the pseudo
     for browsers that have it, the child for those that do not. */
  .a3-menu::details-content {
    content-visibility: visible;
    block-size: auto;
  }
  .a3-menu .a3-nav { display: flex; }
}

/* -----------------------------------------------------------------------------
   Payments: the choice, the rail, and the flow
   From the Founder's Direction D mockup of /merchant/payments.

   The card is styling over a real radio, never a replacement for one. The input
   stays in the accessibility tree and keeps the group's arrow-key behaviour; it
   is moved out of sight rather than hidden, because display:none would take it
   out of the tab order and out of the posted form. Selection is expressed by the
   sibling selector on :checked, so what a person sees and what the browser will
   post cannot disagree.
----------------------------------------------------------------------------- */
.a3-choices {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}
.a3-choices__legend {
  display: flex;
  align-items: center;
  gap: var(--a3-space-3);
  flex-wrap: wrap;
  padding: 0;
  margin-bottom: var(--a3-space-2);
}
.a3-choices__heading {
  font-size: var(--a3-text-lg);
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text);
}
.a3-choices__grid {
  display: grid;
  gap: var(--a3-space-3);
  margin-top: var(--a3-space-4);
  grid-template-columns: 1fr;
}
@media (min-width: 40rem) {
  .a3-choices__grid { grid-template-columns: repeat(3, 1fr); }
}

.a3-choice { display: block; min-width: 0; cursor: pointer; }
/* Out of sight, still in the tab order and still posted. */
.a3-choice > input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.a3-choice__box {
  display: grid;
  gap: var(--a3-space-2);
  justify-items: start;
  height: 100%;
  padding: var(--a3-space-4);
  padding-right: calc(var(--a3-space-4) + 1.6rem);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-bg-raised);
  position: relative;
}
.a3-choice__glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 999px;
  background: var(--a3-primary-100);
  color: var(--a3-primary-700);
}
.a3-choice__glyph .a3-icon { width: var(--a3-icon-md); height: var(--a3-icon-md); }
.a3-choice__title {
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text);
}
.a3-choice__text {
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
}
.a3-choice__pill {
  margin-top: auto;
  font-size: var(--a3-text-xs);
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--a3-bg-sunken);
  color: var(--a3-text-secondary);
}
.a3-choice > input:checked + .a3-choice__box {
  border-color: var(--a3-primary-600);
  box-shadow: inset 0 0 0 1px var(--a3-primary-600);
}
.a3-choice > input:checked + .a3-choice__box .a3-choice__title { color: var(--a3-primary-700); }
.a3-choice > input:checked + .a3-choice__box .a3-choice__pill {
  background: var(--a3-primary-100);
  color: var(--a3-primary-700);
}
/* The dot, drawn on the box so the card reads as a radio and not as a button. */
.a3-choice__box::after {
  content: "";
  position: absolute;
  top: var(--a3-space-4);
  right: var(--a3-space-4);
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 999px;
  border: 2px solid var(--a3-border-strong);
}
.a3-choice > input:checked + .a3-choice__box::after {
  border-color: var(--a3-primary-600);
  background: radial-gradient(circle, var(--a3-primary-600) 0 45%, transparent 46%);
}
.a3-choice > input:focus-visible + .a3-choice__box {
  outline: var(--a3-focus-ring);
  outline-offset: 2px;
}

.a3-chip--required {
  font-size: var(--a3-text-xs);
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--a3-primary-100);
  color: var(--a3-primary-700);
}
.a3-chip--choice {
  font-size: var(--a3-text-sm);
  padding: 3px 11px;
  border-radius: 999px;
  background: var(--a3-bg-sunken);
  color: var(--a3-text);
}

/* The steps, now carrying a description under each label. */
.a3-steps__body { display: grid; min-width: 0; }
.a3-steps__label { font-weight: var(--a3-weight-medium); }
.a3-steps__hint {
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
  font-weight: var(--a3-weight-regular);
}
.a3-steps__step { align-items: flex-start; }
.a3-steps__step::before { margin-top: 1px; }

/* A page head with no mark: the heading and its sentence stack. */
.a3-pagehead--stacked {
  display: block;
}
.a3-pagehead--stacked .a3-pagehead__text {
  max-width: 46ch;
}

/* The rail. One column on a phone, beside the work on a wide screen. It sits
   below the main column in source order, so a small screen reads the choice
   before the summary of it -- a summary above the thing it summarises is a
   desktop habit that reads as noise on a phone. */
.a3-withrail {
  display: grid;
  gap: var(--a3-space-6);
  grid-template-columns: 1fr;
  align-items: start;
}
.a3-withrail__main { display: grid; gap: var(--a3-space-6); min-width: 0; align-content: start; }
.a3-withrail__side { display: grid; gap: var(--a3-space-4); min-width: 0; align-content: start; }
@media (min-width: 60rem) {
  .a3-withrail { grid-template-columns: minmax(0, 1fr) 20rem; }
}

.a3-rail {
  display: grid;
  gap: 0;
  align-content: start;
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-bg-raised);
  min-width: 0;
}
.a3-rail__block { padding: var(--a3-space-4); border-top: 1px solid var(--a3-border); }
.a3-rail__block:first-child { border-top: 0; }
.a3-rail__title {
  font-size: var(--a3-text-sm);
  font-weight: var(--a3-weight-semibold);
  margin: 0 0 var(--a3-space-2);
  color: var(--a3-text);
}
.a3-rail__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--a3-space-3);
  flex-wrap: wrap;
  margin: 0;
}
.a3-rail__note {
  display: flex;
  gap: var(--a3-space-3);
  margin: 0;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
}
.a3-rail__glyph { color: var(--a3-text-muted); flex: 0 0 auto; }
.a3-rail__figures {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--a3-space-2) var(--a3-space-3);
  margin: 0 0 var(--a3-space-3);
  font-size: var(--a3-text-sm);
}
.a3-rail__figures dt { color: var(--a3-text-muted); min-width: 0; }
.a3-rail__figures dd {
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--a3-text);
}

.a3-panel--assure {
  display: flex;
  gap: var(--a3-space-3);
  align-items: flex-start;
  /* The base panel is the centred auth card and caps itself at 34rem. This
     modifier is a full-width reassurance band under a page, so it takes the
     width it is given. */
  max-width: none;
  margin: 0;
  padding: var(--a3-space-4);
}
.a3-panel--assure .a3-panel__title { margin: 0 0 4px; font-size: var(--a3-text-md); }
.a3-panel--assure p { margin: 0; font-size: var(--a3-text-sm); color: var(--a3-text-secondary); }
.a3-panel__glyph { color: var(--a3-primary-700); flex: 0 0 auto; }

/* How a direct payment works. Four steps across a wide screen, stacked on a
   phone, and no connecting arrow is drawn: an arrow that wraps onto its own
   line points at nothing. */
.a3-flow__list {
  display: grid;
  gap: var(--a3-space-4);
  grid-template-columns: 1fr;
  list-style: none;
  margin: var(--a3-space-4) 0 0;
  padding: 0;
}
@media (min-width: 46rem) {
  .a3-flow__list { grid-template-columns: repeat(4, 1fr); }
}
.a3-flow__step {
  display: grid;
  gap: var(--a3-space-2);
  justify-items: start;
  align-content: start;
  min-width: 0;
}
.a3-flow__n {
  font-family: var(--a3-font-numeric);
  font-size: var(--a3-text-xs);
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--a3-primary-100);
  color: var(--a3-primary-700);
}
.a3-flow__glyph { color: var(--a3-primary-700); }
.a3-flow__glyph .a3-icon { width: var(--a3-icon-md); height: var(--a3-icon-md); }
.a3-flow__title { font-weight: var(--a3-weight-semibold); font-size: var(--a3-text-sm); }
.a3-flow__text { font-size: var(--a3-text-sm); color: var(--a3-text-muted); }

/* -----------------------------------------------------------------------------
   A merchant's own shop, on the list of them
   From the Direction D mockup of /merchant/shops.

   Three columns on anything but the narrowest phone: the mark, the shop, the
   way in. Below that the button drops under the text rather than squeezing the
   address into four characters and an ellipsis -- an address a merchant cannot
   read is the one thing this card exists to show them.
----------------------------------------------------------------------------- */
.a3-shoplist {
  display: grid;
  gap: var(--a3-space-3);
  list-style: none;
  margin: 0;
  padding: 0;
}
.a3-shopitem {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--a3-space-2) var(--a3-space-4);
  align-items: center;
  padding: var(--a3-space-4);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-bg-raised);
}
@media (min-width: 30rem) {
  .a3-shopitem { grid-template-columns: auto minmax(0, 1fr) auto; }
}
.a3-shopitem__mark {
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 999px;
  object-fit: cover;
  background: var(--a3-primary-100);
  flex: 0 0 auto;
}
.a3-shopitem__mark--glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--a3-primary-700);
}
.a3-shopitem__mark--glyph .a3-icon { width: var(--a3-icon-md); height: var(--a3-icon-md); }
.a3-shopitem__body { min-width: 0; }
.a3-shopitem__head {
  display: flex;
  align-items: center;
  gap: var(--a3-space-3);
  flex-wrap: wrap;
  margin: 0 0 4px;
}
.a3-shopitem__name {
  font-size: var(--a3-text-lg);
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-primary-700);
  text-decoration: none;
  /* A SHOP'S NAME IS THE WAY INTO IT, and it was a 101x26 target: tall enough
     for WCAG 2.2 AA at 24px, short of the 44px a thumb actually wants, which
     `ACCESSIBILITY.md` 5 asks for as "adequate for touch". Padding rather than
     a larger font, so the card's proportions are unchanged and only the thing
     a finger lands on grows. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-right: var(--a3-space-2);
}
.a3-shopitem__name:hover { text-decoration: underline; }
.a3-shopitem__address {
  margin: 0;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
  /* An address is one long token with no spaces to break at. It wraps here
     rather than running out of the card. */
  overflow-wrap: anywhere;
}
/* Manage shop, then View as a customer: under the shop's details on a phone,
   stacked at the card's end where there is room (the Founder, 2 October 2026). */
.a3-shopitem__actions {
  grid-column: 2;
  display: flex;
  flex-wrap: wrap;
  gap: var(--a3-space-2);
  justify-self: start;
}
/* The main way in: centred, so it reads as one button beside the other. */
.a3-shopitem__manage { justify-content: center; }
.a3-shopitem__open {
  gap: var(--a3-space-2);
  border: 1px solid var(--a3-border-strong);
}
@media (min-width: 30rem) {
  .a3-shopitem__actions { grid-column: 3; grid-row: 1; flex-direction: column; align-items: stretch; justify-self: end; }
}

/* -----------------------------------------------------------------------------
   The customer's own orders
   From the Direction D mockup of /orders.

   A row is one link, not a card with a link inside it. The mockup draws the
   whole row as pressable and the chevron says so; making only the title a link
   would leave most of a 48px-tall target inert, which is the version that feels
   broken on a phone.
----------------------------------------------------------------------------- */
/* -----------------------------------------------------------------------------
   THE ORDERS PAGE, and this block had no definition at all.

   `.a3-orders__head`, `__mark`, `__sub` and `__list` were each defined and the
   BLOCK they belong to was not. It went unnoticed because the class guard
   matched `.a3-orders` as a SUBSTRING of `.a3-orders__list`, so a wrapper with
   no design behind it looked defined -- found on 12 September when `M990`
   walked through the same hole and the guard was tightened to whole names.

   The page therefore fell back to whatever the surrounding stack gave it, which
   is why the heading, the sub-line and the list sat at one another's spacing
   rather than the sheet's.
   ----------------------------------------------------------------------------- */
/* -----------------------------------------------------------------------------
   THE TABS ABOVE A LIST. Sheet `04-orders-b` draws them across the top of the
   orders page, each with a count.

   SCROLLS SIDEWAYS RATHER THAN WRAPPING. Five tabs do not fit across 390px,
   and wrapping them turns a single row of chrome into two -- which is what
   pushed the orders themselves below the fold on the page this replaces. The
   scroll is the row's own, never the page's, which `crossDevice` asserts.

   The current tab is marked by weight and a rule, never by colour alone --
   the same rule the bottom bar follows.
   ----------------------------------------------------------------------------- */
.a3-filters {
  display: flex;
  gap: var(--a3-space-2);
  overflow-x: auto;
  /* The row scrolls; the page must not. */
  max-width: 100%;
  padding-bottom: var(--a3-space-1);
  scrollbar-width: thin;
}

.a3-filters__tab {
  display: inline-flex;
  align-items: center;
  gap: var(--a3-space-2);
  flex: 0 0 auto;
  padding: var(--a3-space-2) var(--a3-space-3);
  min-height: var(--a3-touch-min);
  /* A pill, and there is no pill token: the scale stops at `lg`. A large
     number rather than a new token, because one tab shape does not justify
     widening the scale every direction has to define. */
  border-radius: 999px;
  border: 1px solid var(--a3-border);
  background: var(--a3-bg-raised);
  color: var(--a3-text-muted);
  text-decoration: none;
  font-size: var(--a3-text-sm);
  white-space: nowrap;
}

.a3-filters__tab:hover { border-color: var(--a3-border-strong); }
.a3-filters__tab:focus-visible { outline: var(--a3-focus-ring); outline-offset: 2px; }

.a3-filters__tab[aria-current="page"] {
  color: var(--a3-primary-700);
  border-color: var(--a3-primary-600);
  font-weight: var(--a3-weight-semibold);
}

.a3-filters__count {
  font-size: var(--a3-text-2xs);
  color: var(--a3-text-muted);
}

.a3-orders {
  display: grid;
  gap: var(--a3-space-4);
}

.a3-orders__head {
  display: flex;
  align-items: center;
  gap: var(--a3-space-4);
  padding: var(--a3-space-5);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-bg-sunken);
  margin-bottom: var(--a3-space-5);
}
.a3-orders__head h1 { margin: 0; }
.a3-orders__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: var(--a3-radius-md);
  background: var(--a3-bg-raised);
  color: var(--a3-primary-700);
  flex: 0 0 auto;
}
.a3-orders__mark .a3-icon { width: var(--a3-icon-md); height: var(--a3-icon-md); }
.a3-orders__sub {
  margin: 2px 0 0;
  color: var(--a3-text-muted);
  font-size: var(--a3-text-sm);
}

.a3-orders__list {
  display: grid;
  gap: var(--a3-space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}
.a3-orderrow { min-width: 0; }
.a3-orderrow__link {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--a3-space-2) var(--a3-space-3);
  padding: var(--a3-space-3) var(--a3-space-4);
  min-height: var(--a3-touch-min);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-bg-raised);
  color: inherit;
  text-decoration: none;
}
.a3-orderrow__link:hover { border-color: var(--a3-border-strong); }
.a3-orderrow__link:focus-visible { outline: var(--a3-focus-ring); outline-offset: 2px; }
.a3-orderrow__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--a3-radius-md);
  background: var(--a3-primary-100);
  color: var(--a3-primary-700);
  flex: 0 0 auto;
}
.a3-orderrow__mark .a3-icon { width: var(--a3-icon-md); height: var(--a3-icon-md); }

/* The row's mark, when it is the photograph of what was bought rather than the
   generic box. Both orders sheets lead the row with one. Same square, so a
   list stays aligned where some rows have a picture and some do not. */
.a3-orderrow__mark--shot {
  object-fit: cover;
  background: var(--a3-bg-sunken);
  padding: 0;
}
.a3-orderrow__body { display: grid; min-width: 0; }
.a3-orderrow__title {
  font-weight: var(--a3-weight-semibold);
  /* An order identifier has no spaces to break at. */
  overflow-wrap: anywhere;
}
.a3-orderrow__meta {
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
}
.a3-orderrow__chev { color: var(--a3-text-muted); flex: 0 0 auto; }

/* -----------------------------------------------------------------------------
   HOW FAR AN ORDER HAS GOT.

   Sheets `04-orders-a`, `04-orders-b`, `05-paying-1` and `06-merchant-1` all
   draw it: a vertical list of steps, each with a marker, a label and the time
   it was reached, with a rule connecting them.

   FOUR STATES, not two. A step is done, is where the order is now, was skipped,
   or has not happened -- and `preparing` being optional under `G67` is why
   "skipped" exists at all. Colour alone does not carry any of them: `done` has
   a tick, `now` has a ring and a weight, and `skipped` says so in words.
   ACCESSIBILITY.md, and the same rule the tab bar follows for the current page.
   ----------------------------------------------------------------------------- */
/* -----------------------------------------------------------------------------
   AN ITEM ON AN ORDER, with its photograph.

   Every sheet that draws an order draws a thumbnail beside each line. The
   picture is the one recorded AT PURCHASE, so it is what the customer was
   looking at rather than what the product carries today.

   A fixed box whether or not there is a photograph, so a table of five items
   does not shift as pictures arrive -- the same reason the product cards
   reserve their space.
   ----------------------------------------------------------------------------- */
.a3-orderitem {
  display: inline-grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--a3-space-3);
  min-width: 0;
}

.a3-orderitem__shot {
  width: 3rem;
  height: 3rem;
  border-radius: var(--a3-radius-md);
  object-fit: cover;
  background: var(--a3-bg-sunken);
  flex: 0 0 auto;
}

/* No photograph: the same box, holding the mark the orders list uses, so an
   item without one still lines up with the items that have one. */
.a3-orderitem__shot--none {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--a3-text-muted);
}

.a3-orderitem__name { overflow-wrap: anywhere; font-weight: var(--a3-weight-regular); }

.a3-timeline { display: block; }

.a3-timeline__list {
  display: grid;
  gap: var(--a3-space-4);
  list-style: none;
  margin: 0;
  padding: 0;
}

.a3-timeline__step {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--a3-space-3);
  position: relative;
}

/* The rule between markers, drawn from each step down to the next. Not on the
   last, which would trail into nothing. */
.a3-timeline__step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: calc(var(--a3-icon-md) / 2);
  top: var(--a3-icon-md);
  bottom: calc(var(--a3-space-4) * -1);
  width: 2px;
  background: var(--a3-border);
  transform: translateX(-1px);
}

.a3-timeline__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--a3-icon-md);
  height: var(--a3-icon-md);
  border-radius: 50%;
  border: 2px solid var(--a3-border);
  background: var(--a3-bg-raised);
  color: var(--a3-bg-raised);
  flex: 0 0 auto;
  /* Above the rule it sits on, or the line crosses the marker. */
  position: relative;
  z-index: 1;
}

.a3-timeline__mark .a3-icon { width: 0.75rem; height: 0.75rem; }

.a3-timeline__body { display: grid; min-width: 0; }

.a3-timeline__label { overflow-wrap: anywhere; }

.a3-timeline__at {
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
}

.a3-timeline__note {
  font-size: var(--a3-text-2xs);
  color: var(--a3-text-muted);
  align-self: center;
}

/* A step that has happened: filled marker, ticked. */
.a3-timeline__step[data-a3-state="done"] .a3-timeline__mark,
.a3-timeline__step[data-a3-state="skipped"] .a3-timeline__mark {
  background: var(--a3-primary-600);
  border-color: var(--a3-primary-600);
  color: var(--a3-text-on-action);
}

/* A step nobody recorded, on an order that has passed it. Muted rather than
   filled, and labelled in words beside it. */
.a3-timeline__step[data-a3-state="skipped"] .a3-timeline__mark {
  background: var(--a3-border-strong);
  border-color: var(--a3-border-strong);
}

/* Where the order is NOW. A ring rather than a fill, so it reads as the live
   one against the completed ones above it. */
.a3-timeline__step[data-a3-state="now"] .a3-timeline__mark {
  border-color: var(--a3-primary-600);
  box-shadow: 0 0 0 3px var(--a3-primary-100);
}

.a3-timeline__step[data-a3-state="now"] .a3-timeline__label {
  font-weight: var(--a3-weight-semibold);
}

/* Still to come. */
.a3-timeline__step[data-a3-state="todo"] .a3-timeline__label { color: var(--a3-text-muted); }

/* -----------------------------------------------------------------------------
   THE ORDER IDENTIFIER, on the one page that needs it.

   The order DETAIL page was titled `Order order-14b6078122994d299827` until
   11 September -- found by walking the journey on Production, and the same
   defect the row above was rebuilt to remove. The shop leads now, and the
   identifier sits under it: a customer never thinks in twenty hexadecimal
   characters, but they do have to quote them when something goes wrong.

   `overflow-wrap: anywhere` for the reason `.a3-orderrow__title` gives -- an
   identifier has no spaces to break at, so on a 390px phone it would otherwise
   push the card sideways.
   ----------------------------------------------------------------------------- */
.a3-orderref {
  color: var(--a3-text-muted);
  font-size: var(--a3-text-sm);
  font-weight: var(--a3-weight-regular);
  overflow-wrap: anywhere;
}
/* Below this the pill drops to its own line rather than crushing the title. */
@media (max-width: 30rem) {
  .a3-orderrow__link { grid-template-columns: auto minmax(0, 1fr); }
  .a3-orderrow__link .a3-status { grid-column: 2; justify-self: start; }
  .a3-orderrow__chev { display: none; }
}

/* The glyph the Direction D mockups draw inside a field. The wrapper is what
   carries the box's own padding shift, so the input keeps its full height and
   its focus ring, and the glyph never overlaps typed text. */
.a3-inputwrap { display: block; position: relative; }
.a3-inputwrap__glyph {
  position: absolute;
  left: var(--a3-space-3);
  top: 50%;
  transform: translateY(-50%);
  color: var(--a3-text-muted);
  pointer-events: none;
}
.a3-inputwrap__glyph .a3-icon { width: var(--a3-icon-md); height: var(--a3-icon-md); }
.a3-inputwrap--glyph .a3-input { padding-left: calc(var(--a3-space-3) * 2 + var(--a3-icon-md)); }
/* A currency sign inside a price box (Founder, 3 October 2026: "GH₵ shown as a
   currency prefix"). Text rather than an icon, in the text colour rather than
   muted, because it is part of what the box asks for, not decoration. The
   padding is sized in em so it grows with the merchant's text size. */
.a3-inputwrap__glyph--text { color: var(--a3-text); font-weight: var(--a3-weight-medium); }
.a3-inputwrap--currency .a3-input { padding-left: calc(var(--a3-space-3) * 2 + 2.25em); }

/* A page head that carries an illustration beside its heading.
   Distinct from `.a3-hero`, which is the storefront's own banner and already
   owns a photograph, an eyebrow and a shop name. Two different things sharing
   one class is how a component stops meaning anything. */
.a3-pagehero {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--a3-space-4);
  align-items: center;
  margin-bottom: var(--a3-space-5);
}
.a3-pagehero__text { min-width: 0; }
.a3-pagehero__text h1 { margin: 0 0 var(--a3-space-2); }
.a3-pagehero__text p { margin: 0; }
.a3-pagehero__art { justify-self: center; max-width: 16rem; }
@media (min-width: 44rem) {
  .a3-pagehero { grid-template-columns: minmax(0, 1fr) auto; }
  .a3-pagehero__art { justify-self: end; }
}

/* -----------------------------------------------------------------------------
   The bar above a storefront
   Where you are, and the way back when there genuinely is one. The back control
   is absent for a visitor who did not arrive through the marketplace, which is
   Article 12 rather than a layout choice -- see `shopBar`.
----------------------------------------------------------------------------- */
.a3-shopbar {
  display: flex;
  align-items: center;
  gap: var(--a3-space-3);
  padding: var(--a3-space-3) var(--a3-space-4);
  border-bottom: 1px solid var(--a3-border);
  background: var(--a3-bg-raised);
  min-width: 0;
}
.a3-shopbar__mark {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--a3-radius-md);
  object-fit: cover;
  flex: 0 0 auto;
}
.a3-shopbar__mark--material[data-a3-material] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* The material only paints where a class opts in -- see .a3-shop-mark. A
     bare data attribute sets the custom property and nothing consumes it, which
     is how this square rendered invisible on its first pass. */
  background: var(--a3-material);
  color: var(--a3-text-on-material);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-sm);
}
.a3-shopbar__body { display: grid; min-width: 0; }
.a3-shopbar__name {
  font-weight: var(--a3-weight-semibold);
  overflow-wrap: anywhere;
}
.a3-shopbar__category {
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
}


/* -----------------------------------------------------------------------------
   The storefront hero, read off the Founder's drawing element by element
   -----------------------------------------------------------------------------
   Order in the mockup, top to bottom: the eyebrow with a rule running off it,
   the shop name in the display serif over two lines, the CATEGORY as a subtitle,
   a short gold rule, the merchant's tagline, then the claims panel.

   No rating anywhere on it. The standing is drawn on the marketplace card as a
   pill, and that is where it stays -- see the note where the second renderer
   used to be.
----------------------------------------------------------------------------- */

/* "WELCOME TO", and the rule that runs to the right of it. The rule is a
   flex child rather than a border on the text, so it takes the space the words
   leave instead of matching their width. */
.a3-hero__eyebrow {
  display: flex;
  align-items: center;
  gap: var(--a3-space-3);
  max-width: 18rem;
}
.a3-hero__eyebrow::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--a3-rating-mark, currentColor);
  opacity: 0.7;
}

.a3-hero__name {
  /* Larger than the shared display step: this is the biggest thing on the page
     and the drawing gives it two lines of its own. */
  font-size: clamp(2.5rem, 9vw, 4rem);
  font-weight: var(--a3-weight-semibold);
  line-height: 1.02;
  max-width: 14ch;
}

/* The category, as a subtitle under the name. Not the eyebrow treatment: the
   drawing sets it at reading size in the body face, and it is the sentence that
   tells a customer what the shop actually sells. */
.a3-hero__category {
  margin: 0;
  font-size: var(--a3-text-xl);
  font-weight: var(--a3-weight-regular);
  opacity: 0.95;
}

/* The short gold rule between the category and the tagline. */
.a3-hero__rule {
  border: 0;
  height: 1px;
  width: 3rem;
  margin: var(--a3-space-2) 0;
  background: var(--a3-rating-mark, currentColor);
  opacity: 0.9;
}

.a3-hero__summary { max-width: 34ch; }

/* -----------------------------------------------------------------------------
   The claims panel inside the hero
   The drawing puts the glyph to the LEFT of each claim, not above it, and sizes
   the panel to the text column rather than the hero.
----------------------------------------------------------------------------- */
/* -----------------------------------------------------------------------------
   The claims panel, drawn once and the same everywhere
   -----------------------------------------------------------------------------
   The Founder drew this on paper on 1 September 2026: one box, and a single
   division running its full height from edge to edge.

   It used to be two panels. Below 34rem the claims STACKED, one per row, with
   a horizontal rule between them; only from 34rem up did they sit side by side
   with a vertical one. So no phone ever showed the box he drew, and the panel
   he was looking at was a different component from the one on the laptop.

   It is now a row at every width. What changes on a narrow screen is the size
   of the type inside it, never the shape.

   Sizes are the drawing's own, measured off `06-storefront.png` magnified
   beside the built page at the same 904px width.
----------------------------------------------------------------------------- */
.a3-hero .a3-claims {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, auto);
  grid-template-columns: none;
  /* STRETCH, so the division below reaches both edges of the box. */
  align-items: stretch;
  gap: 0;
  margin: clamp(0.6rem, 2vw, 1.25rem) 0 0;
  border: 1px solid color-mix(in srgb, currentColor 26%, transparent);
  border-radius: clamp(0.6rem, 1.7vw, 1.05rem);
  /* Barely there. The drawing lets the photograph through the panel; a third
     of black turned it into a dark box sitting on the picture. */
  background: rgba(0, 0, 0, 0.16);
  /* NO PADDING ON THE BOX. The items carry it, which is what lets the
     division run the full height rather than floating inside a margin. */
  padding: 0;
  width: fit-content;
  max-width: 100%;
}
.a3-hero .a3-claims__item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  /* ~19px between the glyph and the words, at the drawing's width. */
  column-gap: clamp(0.5rem, 2.1vw, 1.3rem);
  row-gap: 0;
  align-items: center;
  /* ~18px and ~25px. */
  padding: clamp(0.5rem, 2vw, 1.2rem) clamp(0.7rem, 2.8vw, 1.7rem);
}
/* THE DIVISION. Edge to edge, as drawn. */
.a3-hero .a3-claims__item + .a3-claims__item {
  border-left: 1px solid color-mix(in srgb, currentColor 26%, transparent);
}
.a3-hero .a3-claims__title { font-size: clamp(0.78rem, 1.75vw, 1.05rem); }
/* Nearly as present as the title, which is how the drawing reads. */
.a3-hero .a3-claims__text { font-size: clamp(0.7rem, 1.5vw, 0.92rem); opacity: 0.88; }
/* The glyph spans both rows on the left; the title and text stack beside it.

   ~33px at the drawing's 904px, which is much larger than a line of text and
   is what gives the panel its weight. It was `--a3-icon-md`, 20px, which read
   as a bullet rather than as a mark. Measured off `06-storefront.png`
   magnified three times beside the same region of the built page. */
.a3-hero .a3-claims__item > .a3-icon {
  grid-row: 1 / span 2;
  grid-column: 1;
  width: clamp(1.05rem, 3.7vw, 2.15rem);
  height: clamp(1.05rem, 3.7vw, 2.15rem);
  align-self: center;
}
.a3-hero .a3-claims__title { grid-column: 2; }
.a3-hero .a3-claims__text { grid-column: 2; }

/* The 34rem switch is gone: the panel has one shape now. See above. */

/* The attribution sits above the panel and stays quiet. */
.a3-hero .a3-claims__source { margin-bottom: var(--a3-space-2); opacity: 0.85; }

/* -----------------------------------------------------------------------------
   The product row, read off the drawing
   -----------------------------------------------------------------------------
   The mockup's picture is FULL BLEED against the card: it touches the top, left
   and bottom edges, takes roughly four tenths of the width, and the card's own
   padding applies only to the text beside it. The earlier version inset the
   picture inside the padding with the card ground showing around it, which made
   every product look like a thumbnail in a list rather than the thing for sale.

   Overriding rather than editing the block above, so the base row -- which other
   surfaces use -- keeps its shape.
----------------------------------------------------------------------------- */
.a3-productrow {
  grid-template-columns: minmax(0, 4fr) minmax(0, 6fr);
  gap: 0;
  padding: 0;
  overflow: hidden;
  align-items: stretch;
}
.a3-productrow__media {
  aspect-ratio: auto;
  border-radius: 0;
  min-height: 11rem;
}
/* The picture fills its half of the row.

   `.a3-product-image` is 4:3 by itself, which is right where it stands alone
   and wrong here: the row's height is set by the words beside it, so a fixed
   ratio left a band of empty ground under every photograph. The Founder draws
   the picture flush to the top, bottom and outer edge of the row. */
.a3-productrow__media .a3-product-image {
  aspect-ratio: auto;
  height: 100%;
  border-radius: 0;
  /* AND IT KEEPS ITS PROPORTIONS. `productImage` renders the photograph AS
     this element -- `<img class="a3-product-image">` -- so the `object-fit` a
     few rules above, written for `.a3-product-image img`, never reaches it:
     that child only exists for the placeholder. Telling the element to fill
     the row without this stretched every real photograph. Introduced and
     caught within the hour, by the Founder, on screen. */
  object-fit: cover;
}
.a3-productrow__body {
  padding: var(--a3-space-4);
  align-content: center;
}
/* The name is the display serif here, as it is in the hero: a product is the
   other thing on this page a customer reads as a name rather than as a label. */
.a3-productrow__name {
  font-family: var(--a3-font-display);
  font-size: var(--a3-text-xl);
  line-height: var(--a3-leading-tight);
}
.a3-productrow__text {
  margin: var(--a3-space-2) 0 0;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
  overflow-wrap: anywhere;
}
.a3-productrow__price {
  font-size: var(--a3-text-xl);
  color: var(--a3-primary-700);
  font-weight: var(--a3-weight-semibold);
}
/* Filled, not outlined. The drawing gives the row one solid green action, and
   an outlined button beside a filled price reads as the secondary thing on a
   card whose entire purpose is that action. */
.a3-productrow .a3-btn--secondary {
  background: var(--a3-primary-600);
  border-color: var(--a3-primary-600);
  color: var(--a3-text-on-action);
  justify-content: center;
}
/* THE HERO KEEPS THE DRAWING'S PROPORTIONS ON A PHONE.

   Measured against `06-storefront.png`, where the claims panel sits 74% down
   the band, ends at 87%, leaves 13% clear beneath it and spans 49% of the
   width. At 360px the built hero had it ending at 99% with 1% clear and 84%
   wide -- jammed on the bottom edge, which is what the Founder circled.

   The cause is not the layout. Type stops scaling at its legible minimum while
   the band keeps shrinking with the screen, so everything grows relative to
   the picture. His drawing is 972px wide; at 360 its own type would be 8px.

   The first attempt at this dropped the summary and the second claim, and the
   Founder's answer settled it: "I didn't mean delete what was there, the
   position was just different". Both are back. What thins instead is one layer
   of DETAIL rather than any element: the claims keep their titles and lose
   their second lines below 30rem, and the vertical rhythm tightens. Every
   thing he drew is still on the page, in the place he drew it. */
@media (max-width: 30rem) {
  /* WHERE THE BLOCK SITS, which is what the Founder circled.

     Measured at 390px: the content is 216px inside a 260px band, so there are
     44px of slack -- and `justify-content: center` split it evenly, leaving
     22px under the panel. The drawing leaves 13% of the band clear beneath it
     and the approved laptop leaves more. Sitting the block near the top puts
     all of the slack underneath, which is 34px, which is 13%. */
  .a3-hero__inner { justify-content: flex-start; padding-top: 0.6rem; }
  .a3-hero__eyebrow { margin-bottom: 0.2rem; }
  .a3-hero__category { margin-top: 0.1rem; }
  .a3-hero__rule { margin-top: 0.35rem; }
  .a3-hero__summary { margin-top: 0.35rem; }
  .a3-hero .a3-claims__source { margin-top: 0.25rem; }
  .a3-hero .a3-claims { margin-top: 0.4rem; }
  .a3-hero .a3-claims__item { padding-block: 0.32rem; }
}

/* THE PICTURE STAYS BESIDE THE TEXT, AT EVERY WIDTH.

   This stacked below 30rem, with the reasoning in its own comment: a picture
   shrunk to a strip is one nobody can see the goods in. Reasonable, and not
   what the Founder drew. `06-storefront.png` is his PHONE design and it puts
   the photograph beside the words in every product row -- which is what makes
   the list scannable, because a customer sees four products without scrolling
   instead of one and a half.

   His drawings are the design authority for Direction D. Where anything in
   design/ disagrees with them they win, subject to the Constitution, and the
   Constitution has nothing to say about this one. `mockups/README.md`.

   At 390px the picture is 156px wide, which is not a strip. */
/* THE PRODUCT PICTURE, ON A PHONE.

   Measured before touching it: the picture cell was 142 x 213 -- PORTRAIT --
   holding a landscape 1.5:1 photograph, so most of the plate was thrown away.
   The cell was that tall because the text beside it was 213px, and 44px of
   that was the "View product" pill.

   That pill is a `<span>` inside `<a class="a3-productrow">`. The row is the
   link; the pill is decoration, and `--a3-touch-min` on it was the same wrong
   assumption that crowded the shop card. Freed, the text comes down to about
   130px, and giving the picture half the row instead of two fifths makes the
   cell roughly 1.4:1 -- close to the 1.7:1 the Founder draws, so the crop is
   a trim rather than a demolition. */
@media (max-width: 30rem) {
  .a3-productrow { grid-template-columns: minmax(0, 5fr) minmax(0, 5fr); }
  .a3-productrow__media { min-height: 0; }
  .a3-productrow__body { padding: 0.6rem 0.7rem; gap: 0.1rem; }
  .a3-productrow__name { font-size: 1.02rem; line-height: 1.15; }
  .a3-productrow__shop { font-size: 0.68rem; }
  .a3-productrow__text { font-size: 0.72rem; line-height: 1.3; }
  .a3-productrow__price { font-size: 1rem; margin-top: 0.15rem; }
  .a3-productrow__actions { margin-top: 0.3rem; }
  /* A span inside the row link, not a control of its own. */
  .a3-productrow__actions .a3-btn {
    min-height: 0;
    padding: 6px 13px;
    font-size: 0.72rem;
  }
}

/* A section heading in the display face. The drawing sets "Our Products" at
   the same weight and family as the shop name, which is what makes the
   storefront read as one page rather than as a banner with a list under it. */
.a3-subheading--display {
  font-family: var(--a3-font-display);
  font-size: var(--a3-text-2xl);
  font-weight: var(--a3-weight-semibold);
  letter-spacing: -0.01em;
}

/* A section heading INSIDE a workspace page, where the grey label above reads
   as a caption rather than as the start of something. `06-merchant-3` sets
   "Recent orders" in the page ink at the body face, one step up and one
   weight over the prose beneath it, and no bigger -- the page already has a
   heading and this is not competing with it. */
.a3-subheading--strong {
  color: var(--a3-text);
  font-size: var(--a3-text-lg);
  font-weight: var(--a3-weight-semibold);
}

/* -----------------------------------------------------------------------------
   A section heading with a control beside it
   "Our Products" on the left, the order on the right, as the drawing puts them.
----------------------------------------------------------------------------- */
.a3-sectionhead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--a3-space-3);
  flex-wrap: wrap;
}
.a3-sectionhead > h2 { margin: 0; }

.a3-sortform {
  display: flex;
  align-items: center;
  gap: var(--a3-space-2);
  min-width: 0;
}
/* The word "Order" is for a screen reader and for anyone who needs to know what
   the select does; the drawing shows the value alone. */
.a3-sortform__label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.a3-sortform .a3-btn { flex: 0 0 auto; }

/* ---------------------------------------------------------------------------
   The basket

   DERIVED FROM THE MOCKUPS RATHER THAN DRAWN, on the Founder's instruction of
   1 September 2026: "The mockup doest have all pages so draw inspiration from
   them and aplly to other pages."

   Taken from `05-customer-orders-list.png`, which is the nearest drawing --
   a list of things a customer owns. Its vocabulary: a page head with a title
   and one supporting line; each row a white card with generous rounding,
   spaced apart rather than divided by rules; a pale tinted tile holding a
   line glyph at the left of each row; the name bold and a muted second line
   beneath it.

   `07-sell-on-asiam3.png` supplies the tinted callout with a round glyph,
   used here for the one-shop note.

   NO ILLUSTRATION IN THE HEAD, deliberately. Every illustration Asiam3 has
   belongs to another page -- the parcel is the orders page, the bag is sign
   in -- and repeating one here would read as a mistake rather than as a
   family. `DIRECTION_D_UNBUILT` 2a records the set as unproduced; the basket
   is now one of the pages waiting on it.
   --------------------------------------------------------------------------- */

/* A pale tile carrying a line glyph. Both drawings use one to open a row or a
   column: a rounded square in the orders list, a circle in the panel of three.
   It is what makes a row read as an object rather than as text. */
.a3-tile {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: 2.75rem; height: 2.75rem;
  border-radius: var(--a3-radius-md);
  background: color-mix(in srgb, var(--a3-surface-accent) 12%, transparent);
  color: var(--a3-surface-accent);
}

/* The tile, when it is the product's own photograph rather than a glyph. Sheet
   `03-basket-v2` draws one on every basket line. Live rather than snapshotted:
   a basket shows what is on sale now, which is the opposite of an order line
   and deliberately so. */
.a3-tile--shot {
  object-fit: cover;
  padding: 0;
  border: 0;
}
.a3-tile--round { border-radius: 999px; }
.a3-tile .a3-icon { width: var(--a3-icon-md); height: var(--a3-icon-md); }

.a3-basket {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--a3-space-3);
}

/* A row is a card, as the orders list draws it: raised, rounded, and separated
   by space rather than by a rule. */
.a3-basket__line {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--a3-space-3);
  padding: var(--a3-space-4);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-bg-raised);
}
/* Name and note share one column, so the quantity box keeps its place. They
   were siblings of the input, and the note being full width pushed the box
   onto its own line on the withdrawn row only -- one list with its control
   in two different positions. */
.a3-basket__text {
  flex: 1 1 12ch; min-width: 0;
  display: flex; flex-direction: column; gap: var(--a3-space-1);
}
.a3-basket__name {
  overflow-wrap: anywhere;
  font-weight: var(--a3-weight-semibold);
}
.a3-basket__gone {
  /* A withdrawn product stays on the page and says so. Removing the line
     silently is how a customer decides the site ate their basket. */
  font-size: var(--a3-text-sm); color: var(--a3-text-muted);
}
.a3-field--inline { margin: 0; flex: 0 0 auto; }

/* The tinted note with a round glyph, from `07-sell-on-asiam3.png`. Used for a
   statement about how the product works rather than for a problem, which is
   what `.a3-notice` is for. */
.a3-callout {
  display: flex; align-items: flex-start; gap: var(--a3-space-4);
  padding: var(--a3-space-4);
  border-radius: var(--a3-radius-lg);
  background: color-mix(in srgb, var(--a3-surface-accent) 7%, transparent);
}
.a3-callout__text { min-width: 0; }
.a3-callout__title { margin: 0 0 var(--a3-space-1); font-weight: var(--a3-weight-semibold); }
.a3-callout__body { margin: 0; color: var(--a3-text-muted); font-size: var(--a3-text-sm); }

/* A row of actions: the primary, the secondary, and a quiet way out.

   Added 1 September 2026, after looking at the product page. Its two buy paths
   were a green button, a bare link and a grey button on a line of their own,
   which read as three unrelated things rather than as one decision with a
   default. The drawings group actions and rank them; this is that, and the
   basket page uses the same three-part shape. */
.a3-actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--a3-space-3);
}

/* A money table that becomes cards on a phone.

   Added 1 September 2026, from looking at the checkout on a 390px screen. The
   table was 385px inside the viewport, so the amount a customer is about to pay
   rendered as "GHS 110.0(" against the edge and had to be scrolled to. A total
   you have to scroll sideways to read is not a total anyone will trust.

   Opt-in, as a modifier, because the other tables in the product have not been
   looked at on a phone and a blanket change would restyle pages nobody
   inspected -- which is how the class-definition guard once passed for months
   against markup no merchant would ever see.

   The header row goes away and each cell carries its own label, so the reading
   order stays the same for a screen reader while the layout changes for the
   eye. */
@media (max-width: 34rem) {
  .a3-table--stacks thead { display: none; }
  /* A caption on a table forced to `display: block` collapses to the width of
     its longest word -- it rendered as "Your / basket / from / Adepa / Foods",
     one word per line. */
  .a3-table--stacks caption { display: block; width: auto; }
  .a3-table--stacks, .a3-table--stacks tbody, .a3-table--stacks tfoot,
  .a3-table--stacks tr, .a3-table--stacks th, .a3-table--stacks td { display: block; width: auto; }
  .a3-table--stacks tbody tr {
    padding: var(--a3-space-3) var(--a3-space-4);
    border-bottom: 1px solid var(--a3-border);
  }
  .a3-table--stacks tbody th[scope="row"] {
    font-weight: var(--a3-weight-semibold);
    padding: 0 0 var(--a3-space-2);
    border: 0;
  }
  .a3-table--stacks tbody td {
    display: flex; justify-content: space-between; align-items: center;
    gap: var(--a3-space-3);
    padding: var(--a3-space-1) 0;
    border: 0;
    text-align: left;
  }
  /* The label the removed header used to carry. */
  .a3-table--stacks tbody td::before {
    content: attr(data-label);
    color: var(--a3-text-muted);
    font-size: var(--a3-text-sm);
  }
  .a3-table--stacks tfoot tr {
    display: flex; justify-content: space-between; align-items: baseline;
    gap: var(--a3-space-3);
    padding: var(--a3-space-4);
  }
  .a3-table--stacks tfoot th, .a3-table--stacks tfoot td { padding: 0; border: 0; }
}

/* The invitation to add another shop.

   `09-merchant-shops` draws it as a dashed card rather than as another row: a
   border of its own, a tile the same size as a shop's, and a chevron. It had
   been a plain row with a small glyph, which read as another shop in the list
   rather than as the way to add one -- and a list where the last item is a
   different KIND of thing needs to say so. */
.a3-listrow--invite {
  border: 1px dashed var(--a3-border-strong);
  border-radius: var(--a3-radius-lg);
  padding: var(--a3-space-4);
  margin-top: var(--a3-space-4);
}
.a3-listrow--invite:first-of-type { border-top: 1px dashed var(--a3-border-strong); }

/* -------------------------------------------------------------------------
   The merchant's preview of their own cover photograph
   -------------------------------------------------------------------------

   THE SAME FRAMING THE CUSTOMER GETS, and that is the whole requirement.
   A preview that showed the picture whole while the storefront hero crops it
   to a 3:2 band would be a merchant approving one thing and publishing
   another -- and they would find out from a customer, or never.

   So: the hero's band, the hero's object-fit, the hero's object-position.
   `coverFraming.test.ts` holds the two names to the same rule.

   The image itself carries the shape rather than a wrapper, because there is
   no wrapper here: this sits in a form, not over a scrim. */
/* ---------------------------------------------------------------------------
   What the merchant just chose, shown back to them.

   Signal 04 of the upload flow: *"A filename is not feedback."* Asiam3 showed
   neither a thumbnail nor a size after a picture was chosen, and the Founder
   could not tell whether his upload had worked -- twice.

   Sheet `04-upload-flow` draws the photograph in states 3, 4 and 5. Bounded
   for the reason `.a3-cover-preview` is bounded: a phone photograph at its own
   size is a slab that pushes every control off the screen, and that was found
   by rendering the page rather than by reading it.

   No aspect ratio here, deliberately. This is the file as chosen, before
   Asiam3 has cropped or re-encoded anything, and forcing it into the cover's
   3:2 would show the merchant a picture that is not the one they picked.
   --------------------------------------------------------------------------- */
.a3-upload-preview {
  display: flex;
  flex-direction: column;
  gap: var(--a3-space-2);
  margin-block: var(--a3-space-3);
}

.a3-upload-preview__image {
  display: block;
  width: 100%;
  max-width: 14rem;
  /* Tall photographs are the common case from a phone held upright, so the
     ceiling is on HEIGHT rather than width. Without it a portrait picture is
     drawn full-length and the Save control leaves the screen -- which is the
     same defect the cover preview was bounded for. */
  max-height: 12rem;
  width: auto;
  object-fit: contain;
  border-radius: var(--a3-radius-md);
  background: var(--a3-bg-sunken);
}

.a3-cover-preview {
  display: block;
  width: 100%;
  /* A PREVIEW, NOT THE HERO. Unbounded, a 3:2 band across a form is 760px tall
     on a laptop -- a slab that pushes every control below it off the screen.
     Found by rendering this page on 2026-09-02 and looking at it. The shape is
     the hero's because it has to be; the size is not. */
  max-width: 28rem;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  object-position: center;
  border-radius: var(--a3-radius-md);
  /* The shop's own material where there is one, which is what a customer
     actually sees before a cover arrives, and the sunken ground otherwise.
     This said `--a3-bg-sunken` outright, which beat `.a3-placeholder` on source
     order alone and painted the empty state flat grey -- so the preview showed
     a merchant something no customer would ever see. */
  background: var(--a3-material, var(--a3-bg-sunken));
}

/* -----------------------------------------------------------------------------
   A PRODUCT TILE, and the grid of them
   -----------------------------------------------------------------------------
   Founder, 2026-09-03, from his own phone gallery: "I can see more than 4
   pictures to choose from and scroll to see more. not single by single. then if
   I want to see single I tap."

   NAMED `producttile` RATHER THAN `tile`, and that is not a style choice.
   `.a3-tile` already exists above: a 2.75rem glyph square that opens a row. The
   first version of this block called itself `.a3-tile` and silently inherited
   `height: 2.75rem`, so every product rendered 44px tall with its picture, name
   and price spilling out. The page looked broken and the grid was innocent.
   REPOSITORY.md: "do not create a second version of an existing rule...
   duplicate rules, parallel definitions and undocumented exceptions are how
   governance dies quietly." A duplicated class NAME is that failure in CSS.

   `.a3-productrow` stays. A wide card with the words beside the picture is
   right where a page shows a handful; this is the other case, a catalogue a
   customer scans, and it takes the same language.

   A LADDER, not one rule. ENGINEERING_FOUNDATION section 2: worked out for the
   form factor rather than a desktop shrunk down. DEVICE_VALIDATION section 5
   names the widths -- 320, 360, 414 touch; 768, 1280 mouse. One `auto-fill`
   minimum cannot express that: the number giving four on a tablet gives one on
   a phone, which is what the storefront had. */
.a3-grid--tiles {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--a3-space-3);
}
@media (min-width: 25rem) { .a3-grid--tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 48rem) {
  .a3-grid--tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--a3-space-4); }
}
@media (min-width: 80rem) { .a3-grid--tiles { grid-template-columns: repeat(5, minmax(0, 1fr)); } }

.a3-producttile {
  display: flex;
  flex-direction: column;
  gap: var(--a3-space-2);
  padding: var(--a3-space-3);
  background: var(--a3-bg-raised);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-md);
  color: inherit;
  text-decoration: none;
}
.a3-producttile:hover { border-color: var(--a3-border-strong, var(--a3-border)); }
.a3-producttile:focus-visible { outline: var(--a3-focus-ring); outline-offset: 2px; }
.a3-producttile .a3-product-image { width: 100%; }

.a3-producttile__name {
  font-family: var(--a3-font-display);
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-md);
  line-height: var(--a3-leading-tight);
  overflow-wrap: anywhere;
}
.a3-producttile__shop {
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
  overflow-wrap: anywhere;
}
.a3-producttile__price {
  font-family: var(--a3-font-body);
  font-size: var(--a3-text-lg);
  font-weight: var(--a3-weight-semibold);
  margin-top: auto;
}

/*
  HOW FAR ALONG SOMETHING IS, when the answer is actually known.

  Added 2026-09-04. A spinner says "working" and says it identically at one
  second and at forty; on a file upload that reads as stuck. A photograph is sent
  in a known number of pieces, so the honest control is the one that says which
  piece -- the bar moves because the upload moved, not because time passed.

  Styled on both the element and its bar, because browsers disagree about which
  one carries the fill.
*/
.a3-progress {
  display: block;
  width: 100%;
  height: 0.5rem;
  margin-top: var(--a3-space-2);
  border: none;
  border-radius: var(--a3-radius-sm);
  background: var(--a3-bg-sunken);
  accent-color: var(--a3-primary-600);
}
.a3-progress::-webkit-progress-bar {
  background: var(--a3-bg-sunken);
  border-radius: var(--a3-radius-sm);
}
.a3-progress::-webkit-progress-value {
  background: var(--a3-primary-600);
  border-radius: var(--a3-radius-sm);
}
.a3-progress::-moz-progress-bar {
  background: var(--a3-primary-600);
  border-radius: var(--a3-radius-sm);
}

/* -----------------------------------------------------------------------------
   THE MARKET HOME, as the adopted design draws it.

   Founder decision G37, sheet `02-market-home`, and G46 on how it is adopted:
   "Take it as though we are discarding the old one... The new house might even
   have some rooms the old never had so you build."

   So these are new components rather than adjustments to the old ones. The
   page this replaces was a heading, a search band and two full-bleed cards the
   height of a phone screen, which is not this drawing with different colours.

   Every value is a token lookup. `componentSystem.test.ts` holds this file to
   zero hardcoded colour, and a direction that could not restyle this page
   would not be a direction.
   ----------------------------------------------------------------------------- */

/* The hero. A single deep-green panel with the headline and the search in it,
   rounded on all four corners and inset from the page edge -- it is a card the
   page opens with, not a full-bleed band.

   NAMED `market-hero` AND NOT `hero`, which is what it was called first. There
   is already an `.a3-hero`: the STOREFRONT's, full-bleed and square, carrying
   the shop's own material, changed deliberately on 2026-08-31 because "a shop's
   own banner is the top of its page, not a tile placed on Asiam3's page". Two
   components under one class name is Article 18 in CSS -- both rule sets
   applied at once, and the market hero rendered three times the height the
   design draws with nothing reporting it. */
.a3-market-hero {
  position: relative;
  overflow: hidden;
  border-radius: var(--a3-radius-lg);
  background: var(--a3-identity);
  color: var(--a3-text-inverse);
  padding: var(--a3-space-7) var(--a3-space-6);
  display: grid;
  gap: var(--a3-space-5);
}

/* A GRID CHILD TAKES ITS MIN-CONTENT WIDTH UNLESS TOLD OTHERWISE, and the
   search form's button does not shrink, so the column was seven pixels wider
   than the panel and the subtitle was clipped by the hero's own `overflow:
   hidden`. Reported by the eye as "cut by 7px", which is the kind of number
   nobody notices by looking. */
.a3-market-hero > * { min-width: 0; }

@media (min-width: 60rem) {
  .a3-market-hero {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
    padding: var(--a3-space-6);
  }
}

.a3-market-hero__title {
  margin: 0;
  font-family: var(--a3-font-display);
  font-size: var(--a3-text-2xl);
  line-height: 1.12;
  font-weight: 700;
  color: var(--a3-text-inverse);
  /* THREE LINES, and the design breaks them itself: "Real shops. / Real
     people. / A bigger Ghana." A sentence that rewraps by width loses the
     rhythm the drawing has, so the breaks are in the markup and this only
     stops the browser undoing them. */
  text-wrap: balance;
}

@media (min-width: 60rem) {
  .a3-market-hero__title { font-size: var(--a3-text-3xl); }
}

.a3-market-hero__text {
  margin: 0;
  max-width: 34rem;
  color: var(--a3-text-inverse);
  /* The subtitle sits quieter than the headline without changing hue: a second
     colour on a green ground is a second meaning, and G44 gives every colour
     exactly one row in the role table. */
  opacity: 0.88;
}

/* A stack with the gap closed up, for a heading and its own line. */
.a3-stack--tight { gap: var(--a3-space-1); }

/* ---------------------------------------------------------------------------
   THE SHOP, AS A CUSTOMER MEETS IT. Sheet `05-phone-established`.

   Cover, logo over it, name, line, address, state. Above every section,
   because it answers the question a merchant opens this page with -- is this
   the right shop, and how does it look -- and each section below answers a
   narrower one.
   --------------------------------------------------------------------------- */
.a3-shopcover {
  padding: 0;
  overflow: hidden;
}

.a3-shopcover__band {
  display: block;
  inline-size: 100%;
  block-size: 7rem;
  object-fit: cover;
}

.a3-shopcover__body {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--a3-space-3);
  align-items: start;
  padding: var(--a3-space-4);
  /* The logo overlaps the band, which is what the drawing does and what the
     storefront already does for a customer. */
  margin-block-start: -2.5rem;
}

.a3-shopcover__mark {
  display: grid;
  place-items: center;
  inline-size: 4.5rem;
  block-size: 4.5rem;
  border-radius: var(--a3-radius-md);
  object-fit: cover;
  background: var(--a3-bg-inverse);
  color: var(--a3-text-inverse);
  font-family: var(--a3-font-display);
  font-weight: var(--a3-weight-semibold);
  /* A ring, so the mark reads as sitting ON the band rather than punched
     through it. */
  outline: 3px solid var(--a3-bg-raised);
}

.a3-shopcover__naming { min-width: 0; padding-block-start: var(--a3-space-5); }

.a3-shopcover__name {
  margin: 0;
  font-family: var(--a3-font-display);
  font-size: var(--a3-text-lg);
  font-weight: var(--a3-weight-semibold);
}

.a3-shopcover__acts {
  padding: 0 var(--a3-space-4) var(--a3-space-4);
  gap: var(--a3-space-3);
  flex-wrap: wrap;
}

/* ---------------------------------------------------------------------------
   SHOP SETTINGS SECTIONS. Founder decision G62, sheets `05-phone-established`
   and `08-editing-a-section`.

   The page ran to 6,300 pixels on a 390px phone as seven stacked forms. Each
   is now a card in one of three states, and the third is what makes it work:

     OPEN         the form, with Cancel and Save
     SUMMARISED   glyph, title, line, Edit, and what the section holds
     COLLAPSED    glyph, title, line, chevron. Nothing else

   Collapsed is every OTHER section while one is open. Without it, opening a
   section adds a form to a page already showing everything.
   --------------------------------------------------------------------------- */
/* -----------------------------------------------------------------------------
   A SHOP SETTINGS SECTION, block and modifier, neither of which was defined.

   Same hole as `.a3-orders` above: every child was styled and the block was
   not, hidden behind the substring match against `.a3-shopsection__head`.

   `06-merchant-1` draws these as a list of rows -- Basic information, Cover
   photo, Logo, About your shop, Categories, Delivery areas, Shop policies --
   each one tappable across its whole width with a chevron at the end. The
   collapsed modifier is what makes a card behave as that row: the anchor
   covers the padding rather than sitting inside it, so the target is the row
   and not the words in it.
   ----------------------------------------------------------------------------- */
.a3-shopsection { display: block; }

.a3-shopsection--collapsed { padding: 0; }

.a3-shopsection--collapsed .a3-shopsection__whole {
  /* The padding moves OFF the card and ON to the link, so the whole row is the
     target. ACCESSIBILITY.md's touch floor applies to a row as much as to a
     button, and a row a finger misses is a setting a merchant cannot reach. */
  padding: var(--a3-space-4);
  min-height: var(--a3-touch-min);
}

.a3-shopsection--collapsed .a3-shopsection__whole:focus-visible {
  outline: var(--a3-focus-ring);
  outline-offset: -2px;
}

.a3-shopsection__head {
  display: grid;
  /* Glyph, naming, Edit, chevron. FOUR columns, because a summarised section
     carries both trailing controls and a three-column grid wrapped the
     chevron onto its own line -- which reads as a stray arrow under the
     title rather than as the row opening. Found by rendering it. */
  grid-template-columns: auto 1fr auto auto;
  gap: var(--a3-space-3);
  align-items: center;
}

/* The tile beside every title. A tinted square, which is what the sheet draws
   -- and tinted rather than filled, because a filled shape is how Asiam3 says
   "you may press this" and a decorative glyph may not borrow that. */
.a3-shopsection__glyph {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border-radius: var(--a3-radius-md);
  background: var(--a3-primary-100);
  color: var(--a3-primary-600);
}

.a3-shopsection__glyph .a3-icon {
  inline-size: 1.35rem;
  block-size: 1.35rem;
}

.a3-shopsection__naming { min-width: 0; }

.a3-shopsection__title {
  margin: 0;
  font-family: var(--a3-font-display);
  font-size: var(--a3-text-md);
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text);
}

.a3-shopsection__text {
  margin: 0;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
}

/* The chevron, which says the row opens. Quiet, because it is a hint about
   what a control does rather than the control itself. */
/* The Edit control of a summarised section. It sits in the head grid rather
   than in the content, so it lines up with the title on every section however
   much or little each one holds. */
.a3-shopsection__edit {
  white-space: nowrap;
  align-self: start;
}

.a3-shopsection__chev {
  display: grid;
  place-items: center;
  color: var(--a3-text-muted);
}

.a3-shopsection__chev .a3-icon {
  inline-size: 1.1rem;
  block-size: 1.1rem;
}

/* ---------------------------------------------------------------------------
   A COLLAPSED SECTION IS ONE TAP TARGET, and that is why the whole row is the
   link rather than a small control inside it.

   A merchant looking at six collapsed rows should not have to find a button in
   each. `ACCESSIBILITY.md` asks for a target a finger can hit, and the row is
   the biggest honest one -- the chevron says what it does.
   --------------------------------------------------------------------------- */
.a3-shopsection--collapsed { padding-block: var(--a3-space-4); }

.a3-shopsection__whole {
  display: block;
  color: inherit;
  text-decoration: none;
}

.a3-shopsection__whole:hover .a3-shopsection__title { text-decoration: underline; }

/* The controls of an open section. Cancel first, because it is the one a
   merchant reaches for by mistake. */
.a3-shopsection__acts {
  margin-top: var(--a3-space-4);
  gap: var(--a3-space-3);
}

/* What a section holds when it holds nothing yet. Not an error: a shop being
   set up is the ordinary state of a shop being set up. */
.a3-shopsection__empty {
  margin: 0;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
}

/* Categories, as chips. Sheet `05` shows WHICH ones, because a merchant
   scanning the page is checking their own choices rather than counting. */
.a3-shopchips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--a3-space-2);
}

.a3-shopchip {
  padding: var(--a3-space-1) var(--a3-space-3);
  /* The largest the system has. There is no pill token, and inventing one for
     a chip would be a fourth radius nobody decided. */
  border-radius: var(--a3-radius-lg);
  background: var(--a3-primary-100);
  color: var(--a3-primary-600);
  font-size: var(--a3-text-sm);
}

/* ---------------------------------------------------------------------------
   THE MERCHANT'S OWN CLAIMS, with the glyph they chose.

   Asiam3 states none of these and the storefront says so on the page. Drawn
   here exactly as a customer sees them, so a merchant checking their own shop
   is checking what is actually published rather than a paraphrase of it.
   --------------------------------------------------------------------------- */
.a3-shopclaims {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--a3-space-3);
}

.a3-shopclaim {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--a3-space-3);
  align-items: start;
}

.a3-shopclaim__glyph {
  display: grid;
  place-items: center;
  color: var(--a3-primary-600);
}

.a3-shopclaim__glyph .a3-icon {
  inline-size: 1.25rem;
  block-size: 1.25rem;
}

.a3-shopclaim__words { display: grid; min-width: 0; }

.a3-shopclaim__title {
  font-weight: var(--a3-weight-semibold);
  font-size: var(--a3-text-sm);
}

.a3-shopclaim__text {
  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
}

/* ---------------------------------------------------------------------------
   THE PICTURES, SHOWN RATHER THAN DESCRIBED.

   Sheet `05` draws the logo as a square and the cover as a band beside it. A
   merchant does not open this page to learn WHETHER they have a cover; they
   open it to see which one.
   --------------------------------------------------------------------------- */
.a3-shoppics {
  display: flex;
  flex-wrap: wrap;
  gap: var(--a3-space-3);
  align-items: flex-start;
}

.a3-shoppics__logo {
  inline-size: 5rem;
  block-size: 5rem;
  object-fit: cover;
  border-radius: var(--a3-radius-md);
  background: var(--a3-bg-sunken);
}

.a3-shoppics__cover {
  flex: 1 1 12rem;
  min-inline-size: 0;
  block-size: 5rem;
  object-fit: cover;
  border-radius: var(--a3-radius-md);
  background: var(--a3-bg-sunken);
}

/* The picture half.

   This held a MATERIAL until 9 September 2026, on the stated ground that
   Asiam3 drew no photographs of its own. It now holds the one photograph it
   does draw -- somebody choosing something to buy -- and the rule that made
   it a material is untouched: `design/README.md` refuses a PLACEHOLDER that
   looks like a photograph of an actual item, because that claims something
   about goods Asiam3 has never seen. Every card still shows the shop material
   until a merchant uploads their own.

   ON A PHONE TOO, CORRECTED 9 September 2026. This read "hidden below 60rem,
   unchanged -- a phone gets the headline and the search, which is what it came
   for, and not a 75 KB picture it has no room for."

   That was inherited from when the slot held a decorative MATERIAL, where
   hiding it cost nothing. It is now the one photograph on the front door, and
   `ENGINEERING_FOUNDATION.md` section 2 is unambiguous about who that is for:
   *"the phone is the primary case rather than the fallback"*, and a phone gets
   "a different arrangement of the same product, never a reduced one".

   So the picture is shown at every width and the ARRANGEMENT changes: a band
   above the words on a phone, a column beside them on a laptop. */
.a3-market-hero__art {
  display: block;
  position: relative;
  /* SHALLOWER THAN THE LAPTOP BAND. A phone meets the headline and the search
     first -- they are what the page is for -- and a picture deep enough to
     push the search below the fold would be the photograph taking the page
     over from the thing a customer came to do. */
  height: 9rem;
  width: 100%;
  border-radius: var(--a3-radius-md);
  background: var(--a3-material-market);
  /* Above the words, because the headline is what a customer reads first and
     a picture between it and the search would separate the two halves of one
     thought. */
  order: -1;
}

@media (min-width: 60rem) {
  .a3-market-hero__art {
    align-self: center;
    /* Beside the words rather than above them, which is what the extra width
       is for. */
    order: 0;
    /* AN EXPLICIT HEIGHT, not a proportion. `aspect-ratio` with `width: 100%`
       derives the height from the column and ignores `max-height`, which made
       the hero as tall as half the viewport on a laptop -- three times the band
       the design draws. The picture is decorative, so a fixed band that the
       words sit beside is the right shape rather than a compromise. */
    height: 14rem;
    width: 100%;
    border-radius: var(--a3-radius-md);
    background: var(--a3-material-market);
  }
}

/* The search inside the hero. On a white pill, because a bordered field on a
   dark ground reads as disabled. */
.a3-market-hero__search {
  display: flex;
  gap: var(--a3-space-2);
  align-items: center;
  background: var(--a3-bg-raised);
  border-radius: var(--a3-radius-lg);
  padding: var(--a3-space-2) var(--a3-space-3);
  max-width: 34rem;
}

.a3-market-hero__search input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: transparent;
  font: inherit;
  color: var(--a3-text);
  /* The touch floor applies to the field as much as to the button beside it.
     ACCESSIBILITY.md sets one floor for every control, and a search box a
     thumb misses is the first thing a person fails to use on this page. */
  min-height: var(--a3-touch-min);
  padding: var(--a3-space-2) 0;
}

.a3-market-hero__search input::placeholder { color: var(--a3-text-muted); }

.a3-market-hero__search .a3-icon { color: var(--a3-text-muted); flex: none; }

/* -----------------------------------------------------------------------------
   The category strip. Circular tiles in a row that scrolls sideways on a phone
   and fits on a laptop, with "View all" as the last item rather than a link
   floating above the row.
   ----------------------------------------------------------------------------- */
.a3-cats {
  display: flex;
  gap: var(--a3-space-4);
  overflow-x: auto;
  padding-block: var(--a3-space-2);
  /* The row is a horizontal scroller, which is allowed and is the reason the
     PAGE never scrolls sideways. `look.mjs` checks the page, not this. */
  scrollbar-width: thin;
}

/* The full list wraps instead of scrolling. A sideways scroller is right for
   nine tiles beside other content and wrong for the whole taxonomy on a page
   of its own, where the row would run off the screen with nothing to say so. */
.a3-cats--wrap {
  flex-wrap: wrap;
  overflow-x: visible;
  row-gap: var(--a3-space-5);
}

.a3-cat {
  flex: none;
  width: 5.5rem;
  display: grid;
  justify-items: center;
  gap: var(--a3-space-2);
  text-align: center;
  text-decoration: none;
  color: var(--a3-text-secondary);
  font-size: var(--a3-text-xs);
  line-height: 1.25;
}

.a3-cat__tile {
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--a3-primary-100);
  color: var(--a3-primary-600);
  border: 1px solid transparent;
}

.a3-cat:hover .a3-cat__tile,
.a3-cat:focus-visible .a3-cat__tile {
  border-color: var(--a3-primary-600);
}

/* -----------------------------------------------------------------------------
   The promoted band.

   A DIFFERENT GROUND FROM THE PAGE, and that is the whole point rather than
   decoration. Constitution Article 12: paid promotion buys visibility in
   promotional surfaces and never organic relevance, and sponsored placement is
   disclosed. A promoted shop that looked identical to an organic one would be
   the article being broken in the only place a customer could have noticed.
   ----------------------------------------------------------------------------- */
.a3-promoted {
  background: var(--a3-pending-100);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  padding: var(--a3-space-5);
  display: grid;
  gap: var(--a3-space-4);
}

.a3-promoted__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--a3-space-2) var(--a3-space-4);
  align-items: baseline;
  justify-content: space-between;
}

/* The word itself, as a chip. It is the first thing in the band because the
   disclosure has to be readable before the shops are, not after them. */
.a3-promoted__flag {
  display: inline-block;
  background: var(--a3-pending-600);
  color: var(--a3-text-inverse);
  border-radius: var(--a3-radius-sm);
  padding: 0.125rem var(--a3-space-2);
  font-size: var(--a3-text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.a3-promoted__title {
  margin: 0;
  font-family: var(--a3-font-display);
  font-size: var(--a3-text-lg);
  font-weight: 700;
  color: var(--a3-text);
}

.a3-promoted__note {
  margin: 0;
  font-size: var(--a3-text-xs);
  color: var(--a3-text-secondary);
}

/* -----------------------------------------------------------------------------
   The shop card, redrawn.

   The old card was a full-bleed material panel the height of a phone with the
   name written across it. This is the design's: a cover band on top, then the
   name, the category, the reputation line, one line of description, and a
   filled button that says what pressing it does.
   ----------------------------------------------------------------------------- */
/* -----------------------------------------------------------------------------
   THE SHOP ROW.

   COLUMNS AND ROWS, like a phone's photo gallery. Founder, 2026-09-05, on his
   own phone: "Can you make the phone view two or three shops in a row" and then
   "You know there must be columns too right? And for the laptop maintain the
   GPT layout."

   The first version of this was one row that slid sideways, which is not what a
   gallery does: a gallery fills the screen with small things in columns AND
   rows, and a person scrolls DOWN through them. Sliding was the wrong axis for
   the whole catalogue -- six shops behind a horizontal gesture is six shops
   most people never see. It is the right axis for a short curated set, which is
   what the promoted band is, and that is where it now lives.

   `--a3-shops-across` is the number of columns. Two on a phone, three from a
   larger one, four on a laptop -- and four is the adopted design's own laptop
   grid, kept exactly.
   ----------------------------------------------------------------------------- */
.a3-grid-shops {
  /* TWO ON A PHONE, and the count is the whole decision here. At three, on a
     390px screen, "Adepa Foods" breaks across two lines and "View shop" breaks
     across two more -- so a denser row costs a card that reads worse, which is
     the opposite of what the density was for. Three fits from a larger phone
     up, where the same card has the width to keep its words together. */
  --a3-shops-across: 2;
  display: grid;
  gap: var(--a3-space-3);
  grid-template-columns: repeat(var(--a3-shops-across), minmax(0, 1fr));
}

@media (min-width: 26rem) {
  .a3-grid-shops { --a3-shops-across: 3; }
}

@media (min-width: 60rem) {
  .a3-grid-shops {
    --a3-shops-across: 4;
    gap: var(--a3-space-4);
  }
}

/* -----------------------------------------------------------------------------
   THE PROMOTED BAND SLIDES, and it is the one row that should.

   It is a short curated set rather than the catalogue, so a horizontal gesture
   hides nothing a person was looking for -- and a band that scrolled the page
   downward would push the free results below the fold behind paid ones, which
   is Article 12's "buys attention, never relevance" broken by layout instead of
   by ranking.

   The partial card at the right edge is deliberate: a row that ends exactly at
   the screen edge looks finished.
   ----------------------------------------------------------------------------- */
.a3-grid-promoted {
  --a3-shops-across: 2;
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--a3-shops-across) - 0.35) * var(--a3-space-3)) / var(--a3-shops-across));
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: var(--a3-space-2);
}

.a3-grid-promoted > * { scroll-snap-align: start; }

@media (min-width: 60rem) {
  .a3-grid-promoted {
    grid-auto-flow: row;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    overflow-x: visible;
  }
}

.a3-shop {
  display: flex;
  flex-direction: column;
  background: var(--a3-bg-raised);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-md);
  overflow: hidden;
}

/* The cover. A fixed aspect so a row of cards has one horizon, whatever
   proportions the merchant's photograph arrived at. */
.a3-shop__cover {
  aspect-ratio: 16 / 10;
  background: var(--a3-bg-sunken);
  display: grid;
  place-items: center;
  color: var(--a3-text-muted);
  border-bottom: 1px solid var(--a3-border);
}

.a3-shop__cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.a3-shop__body {
  display: grid;
  gap: var(--a3-space-2);
  padding: var(--a3-space-4);
  /* The button is pushed to the bottom so a row of cards with descriptions of
     different lengths still lines its buttons up. */
  flex: 1 1 auto;
  align-content: start;
}

.a3-shop__name {
  margin: 0;
  font-size: var(--a3-text-md);
  font-weight: 700;
  color: var(--a3-text);
}

.a3-shop__cat {
  margin: 0;
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
}

.a3-shop__text {
  margin: 0;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
}

.a3-shop__foot {
  padding: 0 var(--a3-space-4) var(--a3-space-4);
  display: grid;
}

/* What the search card keeps from the one it replaced (26 September 2026):
   the shop's own mark beside its name, and the merchant's claims beneath. */
.a3-shop__id {
  display: flex;
  align-items: center;
  gap: var(--a3-space-2);
  min-width: 0;
}
.a3-shop__logo {
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  border-radius: var(--a3-radius-sm);
  display: grid;
  place-items: center;
  object-fit: cover;
  background: var(--a3-bg-sunken);
  color: var(--a3-text-secondary);
  font-size: var(--a3-text-xs);
  font-weight: 700;
}
.a3-shop__claims { font-size: var(--a3-text-xs); color: var(--a3-text-secondary); }

/* -----------------------------------------------------------------------------
   The empty state the design draws for a market with nothing in it.
   ----------------------------------------------------------------------------- */
.a3-nothing {
  display: grid;
  justify-items: center;
  text-align: center;
  gap: var(--a3-space-4);
  padding: var(--a3-space-8) var(--a3-space-4);
}

.a3-nothing__art {
  width: 8rem;
  height: 8rem;
  border-radius: var(--a3-radius-lg);
  background: var(--a3-primary-100);
  display: grid;
  place-items: center;
  color: var(--a3-primary-600);
}

.a3-nothing__title {
  margin: 0;
  font-family: var(--a3-font-display);
  font-size: var(--a3-text-xl);
  font-weight: 700;
  color: var(--a3-text);
}

.a3-nothing__text {
  margin: 0;
  max-width: 32rem;
  color: var(--a3-text-secondary);
}

.a3-nothing__acts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--a3-space-3);
  justify-content: center;
}

/* A section heading with its own quiet subtitle and an optional link on the
   right, which the design repeats for "Promoted", "Featured shops" and
   "All shops". */
.a3-section__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--a3-space-2) var(--a3-space-4);
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: var(--a3-space-3);
}

.a3-section__title {
  margin: 0;
  font-family: var(--a3-font-display);
  font-size: var(--a3-text-lg);
  font-weight: 700;
  color: var(--a3-text);
}

/* The way out of a section, as sheet `02-market-home-v3` draws it: a quiet
   link at the end of the heading row, not a button competing with the cards
   under it. Added 2026-09-12 with the market home's product row. */
.a3-section__more {
  display: inline-flex;
  align-items: center;
  /* A target a thumb can hit: it measured 140x20 on 25 September 2026. */
  min-height: var(--a3-touch-min);
  gap: var(--a3-space-1);
  font-size: var(--a3-text-sm);
  color: var(--a3-link);
  white-space: nowrap;
}

.a3-section__text {
  margin: 0;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
}

/* -----------------------------------------------------------------------------
   THE MARKETPLACE MASTHEAD, from sheet `02-market-home`.

   A variant of the authority band rather than a second component, because the
   band is what every page carries and `routes.test.ts` requires it by name on
   every customer response. What changes is what it shows and how it is laid
   out; what it IS stays the same thing.
   ----------------------------------------------------------------------------- */
.a3-authority--market {
  /* A ROW, EXPLICITLY. `.a3-authority` is redefined lower in this file as a
     grid with named areas -- "mark surface title" over "basis basis basis" --
     and the market variant inherited it. Everything written against a flex row
     then behaved strangely: the wordmark and its tagline landed in two grid
     cells side by side instead of stacking, and `order` did nothing. Measured
     in a browser rather than guessed from a screenshot, after the same picture
     had been read two different ways. */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  grid-template-areas: none;
  grid-template-columns: none;
  gap: var(--a3-space-3) var(--a3-space-4);
  padding: var(--a3-space-3) var(--a3-space-4);
}

/* "A market of independent businesses", under the wordmark on a laptop and
   hidden on a phone -- the drawing does the same, because a tagline that pushes
   the ways in off a small screen has cost more than it explains. */
/* The tagline is on the phone too, which is what the drawing shows -- it wraps
   under the wordmark rather than being dropped. It is the one line that says
   what this site is to somebody who arrived from a link. */
.a3-authority--market .a3-authority__tagline {
  color: var(--a3-text-muted);
  font-size: var(--a3-text-xs);
  flex: 1 1 8rem;
  min-width: 0;
}

/* The glyphs go to the far right, together, as the drawing has them. */
.a3-authority--market .a3-authority__control,
.a3-authority--market .a3-menu {
  order: 100;
}

.a3-authority--market .a3-authority__control { margin-inline-start: auto; }

@media (min-width: 48rem) {
  /* With the buttons showing, they take the right-hand end and the glyphs sit
     beside them rather than pushing them around. */
  .a3-authority--market .a3-authority__control { margin-inline-start: 0; }
}

/* HOW somebody is signed in, kept but quiet. The drawing does not show it and
   the authentication guards read it, so it stays in the page and stops taking
   a line of its own on the smallest screen. */
.a3-authority--market .a3-authority__basis {
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
}

/* Every way in is a target a thumb has to hit, including the one that is a
   plain link. "Sign in" was 41x20 and under the floor -- the buttons beside it
   were fine, which is exactly why it was the one that got missed. */
.a3-authority__acts > a {
  display: inline-flex;
  align-items: center;
  min-height: var(--a3-touch-min);
  padding-inline: var(--a3-space-2);
}

/* The ways in, pushed to the right. `margin-inline-start: auto` rather than a
   spacer element, so the row still wraps sensibly when the text is enlarged. */
.a3-authority__acts {
  margin-inline-start: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--a3-space-2);
}

/* A smaller button, for a masthead. The floor stays the touch minimum: a
   control in a header is still something somebody taps on a phone, and
   ACCESSIBILITY.md sets that floor for every control rather than for most. */
.a3-btn--sm {
  padding: 0 var(--a3-space-3);
  font-size: var(--a3-text-sm);
}

/* -----------------------------------------------------------------------------
   THE BOTTOM BAR, on a phone.

   Sheet `02-market-home` fixes it to the bottom of every mobile screen, and
   Asiam3 had nothing equivalent: every destination lived behind a menu control,
   so reaching one cost a tap to open the menu before it cost a tap to go.

   It is hidden above the phone breakpoint. A persistent bar across the bottom
   of a laptop is a strip of chrome nobody asked for, and the same links are
   already in the row under the masthead there.
   ----------------------------------------------------------------------------- */
.a3-tabbar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: var(--a3-z-sticky);
  display: flex;
  background: var(--a3-bg-raised);
  border-top: 1px solid var(--a3-border);
  /* -------------------------------------------------------------------------
     CLEAR OF THE PHONE'S OWN GESTURE AREA, and this had never worked.

     The rule was `padding-bottom: env(safe-area-inset-bottom, 0)` alone, whose
     comment said it keeps the labels out of the gesture area. **It resolved to
     the zero fallback on every device.** A browser reports no safe-area inset
     at all unless the page opts in with `viewport-fit=cover`, and the viewport
     meta did not carry it. So the bar sat flush against the bottom edge, with
     its lower half inside the strip the operating system reserves for its own
     swipes.

     The Founder found it from the other side, on an Android phone: tapping
     Search "reacts to show it has been tapped but does nothing". That is the
     exact signature -- `touchstart` reaches the page and paints the tap
     highlight, then the system claims the gesture, and the click is never
     delivered. The link was never broken and every probe in a headless browser
     navigated perfectly, because a headless browser has no gesture area.

     TWO CHANGES, because `viewport-fit=cover` on its own is not enough. It is
     what makes an inset reported at all, and iOS then reports the home
     indicator honestly. Android with gesture navigation commonly reports ZERO
     while still reserving the strip, so relying on the inset alone would fix
     one platform and leave the reporter's own phone exactly as it was.

     `max()` takes whichever is larger: the real inset where the platform gives
     one, and a floor everywhere else. A floor cannot be derived from anything
     the page can measure, so it is a judgement -- 0.75rem clears Android's
     gesture strip while keeping the bar shorter than the 4.5rem of room the
     page already reserves below it.
     ------------------------------------------------------------------------- */
  padding-bottom: max(env(safe-area-inset-bottom, 0px), var(--a3-space-3));
}

.a3-tabbar__link {
  flex: 1 1 0;
  min-width: 0;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 0.125rem;
  /* The touch floor applies to a bar as much as to a button, and this is the
     control a person uses most on a phone. ACCESSIBILITY.md sets it. */
  min-height: var(--a3-touch-min);
  padding: var(--a3-space-2) var(--a3-space-1);
  text-decoration: none;
  color: var(--a3-text-muted);
  font-size: var(--a3-text-2xs);
  line-height: 1.2;
  text-align: center;
}

.a3-tabbar__link > span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* WHERE YOU ARE, marked with weight and colour and a rule -- never with colour
   alone, which is the rule the rest of the navigation already follows. */
.a3-tabbar__link[aria-current="page"] {
  color: var(--a3-primary-600);
  font-weight: var(--a3-weight-semibold);
  box-shadow: inset 0 2px 0 0 var(--a3-primary-600);
}

/* The page needs room to scroll past the bar, or the last card sits under it. */
@media (max-width: 59.999rem) {
  .a3 { padding-bottom: 4.5rem; }
}

@media (min-width: 60rem) {
  .a3-tabbar { display: none; }
}

/* -----------------------------------------------------------------------------
   THE MENU, inside the marketplace masthead.

   Sheet `02-market-home` draws it as a glyph at the top right beside the
   account one. It was a full-width row underneath the masthead that said
   "Menu", which is a band of chrome between the top of the page and the page,
   on the screen with the least room to spare.

   Open, it drops OVER the page rather than pushing it down: a masthead that
   changes height when a menu opens moves the whole page under the reader's
   thumb, and the thing they were about to tap is no longer there.
   ----------------------------------------------------------------------------- */
/* NOT THE CONTAINING BLOCK, and this is the whole fix.

   The panel used to be positioned against this element, so it hung wherever the
   glyph hung. The glyph is a flex item in a wrapping row, and it wraps: at
   320px, at 360px, and at any width once a person turns their text up. When it
   wraps it lands at the START of a new line, on the left -- and a panel pinned
   to its trailing edge then opens off the side of the screen.

   Measured across five widths and three text sizes: eleven of the fifteen put
   the panel off screen, by as much as 175px. A fix verified at one width is not
   a fix, which is what ENGINEERING_FOUNDATION.md section 12 means by a
   responsive state being a state like any other.

   So the panel is positioned against the BAND instead, which is sticky and
   therefore a containing block, and which always spans the viewport. Where the
   glyph goes no longer decides where the panel goes. */
.a3-authority--market .a3-menu {
  position: static;
  order: 99;
}

.a3-authority--market .a3-menu > summary {
  min-height: var(--a3-touch-min);
  min-width: var(--a3-touch-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--a3-text-secondary);
}

.a3-authority--market .a3-menu[open] > .a3-nav {
  position: absolute;
  /* Against the band, so it cannot leave the screen. The inset keeps it off the
     very edge, and the ceiling keeps it inside a narrow phone. */
  inset-inline-end: var(--a3-space-4);
  top: 100%;
  max-width: calc(100% - var(--a3-space-4) * 2);
  z-index: var(--a3-z-sticky);
  min-width: 12rem;
  flex-direction: column;
  align-items: stretch;
  background: var(--a3-bg-raised);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-md);
  box-shadow: var(--a3-shadow-raised, none);
  padding: var(--a3-space-2);
}

/* On a laptop the destinations are worth having in the open, which is what the
   design draws: Shops, Categories and About sit on the masthead line itself. */
@media (min-width: 60rem) {
  .a3-authority--market .a3-menu > summary { display: none; }

  .a3-authority--market .a3-menu,
  .a3-authority--market .a3-menu[open] > .a3-nav {
    position: static;
    order: 0;
  }

  .a3-authority--market .a3-menu > .a3-nav {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--a3-space-4);
    border: 0;
    background: transparent;
    padding: 0;
    min-width: 0;
  }
}

/* -----------------------------------------------------------------------------
   THE MASTHEAD ON A PHONE, from sheet `02-market-home`.

   The drawing shows the wordmark and its tagline on the left and two glyphs at
   the top right. It shows no buttons, and rendering them anyway gave a 390px
   screen a three-line masthead: "Sign in" and "Register" on one line, "Start
   selling" wrapped onto the next, the wordmark and the menu on a third.

   Nothing is lost by hiding them. Every destination is in the menu beside them,
   and Sign in is one of the four tabs along the bottom.
   ----------------------------------------------------------------------------- */
.a3-authority--market .a3-authority__acts { display: none; }

@media (min-width: 48rem) {
  .a3-authority--market .a3-authority__acts { display: flex; }
}

/* The glyph alone, as the drawing has it. The word is worth its room on the
   merchant and control surfaces, which have no bar along the bottom and where
   the menu is the only way through -- so the label is dropped here rather than
   everywhere.

   BOTH STATES, and the second selector is the whole point. This rule used to
   name only the closed one, at (0,2,1), while `.a3-menu[open] .a3-menu__toggle`
   sets `content: "Close"` at (0,3,1) and therefore won the moment anybody
   opened the menu.

   What that did on a 390px screen, measured: the control grew from 44px to 78px
   as the word appeared, the masthead row could no longer hold it, it wrapped
   onto a line of its own at the far LEFT -- and the panel, which pins its right
   edge to the control, opened at x = -98 with more than half of it off the side
   of the screen. So the button moved across the page as it was tapped, and the
   navigation it opened was mostly unreachable. */
.a3-authority--market .a3-menu__toggle::after,
.a3-authority--market .a3-menu[open] .a3-menu__toggle::after { content: none; }

/* The control is a glyph in a row, not a band across the page. `.a3-menu` sets
   its own ground and a bottom rule for the strip it used to be. */
.a3-authority--market .a3-menu {
  background: transparent;
  border-bottom: 0;
}

.a3-authority--market .a3-menu__toggle { padding: var(--a3-space-2); }

/* -----------------------------------------------------------------------------
   THE MASTHEAD, laid out as sheet `02-market-home` draws it.

   The wordmark and its tagline are one group on the left, the navigation takes
   the line beside them, and the ways in and the glyphs sit at the right. Before
   this the tagline was a sibling in a wrapping row, so it landed in the middle
   of the bar and pushed the navigation onto a row of its own underneath.
   ----------------------------------------------------------------------------- */
.a3-authority--market .a3-authority__brand {
  display: grid;
  /* ONE COLUMN, stated. Without it the band's own template columns reached in
     and put the wordmark beside its tagline rather than above it. */
  grid-template-columns: minmax(0, 1fr);
  justify-items: start;
  gap: 0;
  align-content: center;
}

/* THE CHILDREN ASK FOR A NAMED AREA THEY NO LONGER LIVE IN. `.a3-brand` carries
   `grid-area: mark` for the band's own template, and the moment the wordmark and
   its tagline were wrapped in a grid of their own, that name reached into it and
   made three columns -- so they sat side by side however the columns were
   declared. Measured in the browser after three wrong guesses from a picture:
   grid-template-columns came back "204px 0px 57px". */
.a3-authority--market .a3-authority__brand > * {
  grid-area: auto;
}

.a3-authority--market .a3-authority__basis {
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
}

@media (min-width: 60rem) {
  /* The order the drawing reads in. Stated rather than left to source order,
     because the band carries a different set of children on every surface. */
  .a3-authority--market .a3-authority__brand { order: 0; }
  .a3-authority--market .a3-menu { order: 1; }
  .a3-authority--market .a3-authority__basis { order: 2; margin-inline-start: auto; }
  .a3-authority--market .a3-authority__acts { order: 3; margin-inline-start: 0; }
  .a3-authority--market .a3-authority__control { order: 4; }
}

/* THE ACCOUNT GLYPH BELONGS TO WHICHEVER BAR IS CARRYING IT, and never to both.
   On a phone the bottom bar has "Sign in" with the same person icon, so the one
   in the masthead was the same destination twice on one screen -- the Founder
   spotted it, and the first version of this masthead had removed it for exactly
   that reason before it was put back to match the drawing.

   Above the phone breakpoint there is no bottom bar, so the masthead keeps it. */
.a3-authority--market .a3-authority__control { display: none; }

@media (min-width: 60rem) {
  .a3-authority--market .a3-authority__control { display: inline-flex; }
}

/* A LINE BREAK THAT ONLY APPLIES ON A PHONE. The headline is three lines where
   the screen is narrow and two where it is not, which is what the drawing does:
   the turn always gets its own line, the pair share one when there is room. */
@media (min-width: 60rem) {
  .a3-br--phone { display: none; }
}

/* -----------------------------------------------------------------------------
   The words over the hero picture.
   ----------------------------------------------------------------------------- */
.a3-market-hero__art {
  position: relative;
}

.a3-market-hero__photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The hands and the screen are left of centre in the frame, and the right
     is where the words sit. Anchoring the crop left keeps the subject when
     the column narrows rather than sliding it out of view. */
  object-position: 30% center;
  border-radius: var(--a3-radius-md);
}

/* ---------------------------------------------------------------------------
   A SCRIM, BECAUSE CONTRAST MAY NOT BE ASSUMED OVER A PHOTOGRAPH.

   The words used to sit on a material whose ink was chosen against it, so
   `--a3-text-on-material` was a guaranteed pairing. A photograph guarantees
   nothing: the same corner is dark in this frame and could be a lamp in the
   next, and `ACCESSIBILITY.md` does not accept a contrast that happens to
   hold for one image.

   So the darkness is drawn rather than borrowed. A gradient over the lower
   right of the picture, under the words and above the image, which makes the
   pairing a property of the component instead of a property of the
   photograph somebody swapped in.
   --------------------------------------------------------------------------- */
.a3-market-hero__art::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--a3-radius-md);
  background: linear-gradient(to top left, var(--a3-scrim-strong) 0%, var(--a3-scrim-soft) 38%, var(--a3-scrim-clear) 62%);
  pointer-events: none;
}

.a3-market-hero__script {
  position: absolute;
  inset-block-end: var(--a3-space-4);
  inset-inline-end: var(--a3-space-4);
  margin: 0;
  max-width: 60%;
  text-align: end;
  font-family: var(--a3-font-display);
  font-style: italic;
  font-weight: 700;
  font-size: var(--a3-text-lg);
  line-height: 1.2;
  /* On the material rather than on the green, so it takes the material's ink.
     The underline is the drawing's amber stroke, which is the rating mark's
     colour and the only other place Asiam3 uses it. */
  color: var(--a3-text-on-material);
  padding-bottom: var(--a3-space-1);
  border-bottom: 3px solid var(--a3-rating-mark);
}

/* -----------------------------------------------------------------------------
   The invitation to a business owner, from the foot of the drawing.
   ----------------------------------------------------------------------------- */
.a3-invite {
  display: flex;
  gap: var(--a3-space-4);
  align-items: start;
  background: var(--a3-bg-sunken);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  padding: var(--a3-space-5);
}

.a3-invite__mark {
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--a3-primary-100);
  color: var(--a3-primary-600);
}

.a3-invite__title {
  margin: 0;
  font-family: var(--a3-font-display);
  font-size: var(--a3-text-md);
  font-weight: 700;
  color: var(--a3-text);
}

.a3-invite__text {
  margin: 0;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
}

/* The link is a control a thumb has to hit, and it was 100x20 -- the same
   failure as "Sign in" in the masthead, and for the same reason: a plain link
   among styled buttons is the one nobody measures. */
.a3-invite a {
  display: inline-flex;
  align-items: center;
  gap: var(--a3-space-1);
  /* `--a3-target-inline-min`, not the control floor: a link inside a sentence
     has its own minimum, and `targetSize.test.ts` requires this exact token so
     that every such rule is one decision rather than a number somebody picked.
     It caught the wrong token here immediately. */
  min-height: var(--a3-target-inline-min);
}

/* -----------------------------------------------------------------------------
   THE STOREFRONT, as sheet `03-storefront` draws it.

   A short cover band with a round badge overlapping its lower edge, then the
   name and everything a customer needs to judge the shop. What this replaces
   was a full-bleed panel eight hundred pixels tall with the name written across
   it, which is a poster rather than a shopfront: a customer scrolled past a
   whole screen before reaching a single product.
   ----------------------------------------------------------------------------- */
/* A MERCHANT'S OWN BAND: the shop's mark, and how the viewer is signed in.
   Laid out as a row rather than the named grid the market band uses, because it
   has two children rather than six. */
.a3-authority--shop {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--a3-space-3);
  grid-template-areas: none;
  grid-template-columns: none;
}

.a3-authority--shop .a3-authority__basis {
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
}

/* The legal links in a storefront footer are links in a sentence and carry the
   inline floor, not the control one. They measured 42x16. */
.a3-attribution a {
  display: inline-flex;
  align-items: center;
  min-height: var(--a3-target-inline-min);
}

/* "Powered by" and Asiam3's grey lockup, beside the legal links (G106 point 3,
   amended 1 October 2026). Words and mark on one line, the mark the height of
   a line of this small text and never larger: it says the shop uses a tool,
   and it must not compete with the shop's own name. Not a link. */
.a3-attribution__powered {
  display: inline-flex;
  align-items: center;
  gap: var(--a3-space-1);
  margin-inline-start: var(--a3-space-2);
}

.a3-attribution__powered img {
  height: 1.5em;
  width: auto;
}

.a3-shopid {
  position: relative;
  display: grid;
  gap: var(--a3-space-3);
  margin-bottom: var(--a3-space-5);
}

/* The band. Short on purpose: it is the shop's colour and its photograph, not
   the page. */
.a3-shopid__band {
  height: 9rem;
  border-radius: var(--a3-radius-lg);
  overflow: hidden;
  background: var(--a3-material, var(--a3-bg-sunken));
}

@media (min-width: 60rem) {
  .a3-shopid__band { height: 13rem; }
}

.a3-shopid__cover {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* THE BADGE OVERLAPS THE BAND, which is the drawing's own device and the thing
   that makes a short band read as a shopfront rather than a picture with words
   under it. Pulled up by half its height so it sits on the seam. */
.a3-shopid__badge {
  width: 4.5rem;
  height: 4.5rem;
  /* HALF THE BADGE OVER THE BAND, and the gap has to be paid for. The grid's
     row gap sits between the band and the badge, so a plain -2.25rem left only
     a third of the circle overlapping and it read as a semicircle rather than
     a badge. Measured: the band ended at 417 and the badge began at 393.
     Asiam3 measures these now rather than reading them off a picture. */
  margin-block-start: calc(-2.25rem - var(--a3-space-3));
  margin-inline-start: var(--a3-space-4);
  border-radius: 50%;
  border: 3px solid var(--a3-bg-raised);
  background: var(--a3-bg-raised);
  color: var(--a3-primary-600);
  display: grid;
  place-items: center;
  object-fit: cover;
  font-family: var(--a3-font-display);
  font-weight: 700;
  font-size: var(--a3-text-lg);
  /* Above the band, or the border is drawn under the picture. */
  position: relative;
  z-index: 1;
}

.a3-shopid__body { display: grid; gap: var(--a3-space-1); }

.a3-shopid__name {
  margin: 0;
  font-family: var(--a3-font-display);
  font-size: var(--a3-text-2xl);
  font-weight: 700;
  color: var(--a3-text);
}

.a3-shopid__text { margin: 0; color: var(--a3-text-secondary); }

.a3-shopid__cat,
.a3-shopid__rating {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--a3-space-2);
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
}

.a3-shopid__cat .a3-icon { color: var(--a3-text-muted); }
.a3-shopid__acts { display: flex; flex-wrap: wrap; gap: var(--a3-space-3); }

/* -----------------------------------------------------------------------------
   The tabs. Two, because two of the drawing's five have somewhere to go.
   ----------------------------------------------------------------------------- */
.a3-shoptabs {
  display: flex;
  gap: var(--a3-space-5);
  border-bottom: 1px solid var(--a3-border);
  margin-bottom: var(--a3-space-4);
  overflow-x: auto;
}

.a3-shoptabs__tab {
  padding: var(--a3-space-3) 0;
  min-height: var(--a3-touch-min);
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  white-space: nowrap;
  color: var(--a3-text-secondary);
  border-bottom: 3px solid transparent;
}

/* Marked with weight and a rule as well as colour, never colour alone. */
.a3-shoptabs__tab[aria-current="page"] {
  color: var(--a3-primary-600);
  font-weight: var(--a3-weight-semibold);
  border-bottom-color: var(--a3-primary-600);
}

/* -----------------------------------------------------------------------------
   Search inside this shop. Not the market's search: a customer standing in a
   shop looking for a kettle means this shop's kettle.
   ----------------------------------------------------------------------------- */
.a3-shopsearch {
  display: flex;
  gap: var(--a3-space-2);
  align-items: center;
  background: var(--a3-bg-raised);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  padding: var(--a3-space-2) var(--a3-space-3);
  margin-bottom: var(--a3-space-4);
}

.a3-shopsearch input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: transparent;
  font: inherit;
  color: var(--a3-text);
  min-height: var(--a3-touch-min);
}

.a3-shopsearch .a3-icon { color: var(--a3-text-muted); flex: none; }

/* -----------------------------------------------------------------------------
   A product on its shop's page. It can be bought from without being opened,
   which is the change from the old tile.
   ----------------------------------------------------------------------------- */
.a3-storeprod {
  display: flex;
  flex-direction: column;
  gap: var(--a3-space-2);
  background: var(--a3-bg-raised);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-md);
  padding: var(--a3-space-3);
}

.a3-storeprod__link {
  display: grid;
  gap: var(--a3-space-2);
  text-decoration: none;
  color: inherit;
}

.a3-storeprod__image {
  aspect-ratio: 1 / 1;
  border-radius: var(--a3-radius-sm);
  background: var(--a3-material, var(--a3-bg-sunken));
  display: grid;
  place-items: center;
  overflow: hidden;
  color: var(--a3-text-muted);
}

.a3-storeprod__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.a3-storeprod__name {
  font-size: var(--a3-text-sm);
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text);
}

/* `auto` above the price takes the card's spare height, so the price and its
   button sit at the bottom of every card in a row whatever the name's length.
   The Founder found them misaligned on 2 October 2026: a one-line name beside
   a two-line one lifted its price and button. */
.a3-storeprod__price {
  margin: auto 0 0;
  font-family: var(--a3-font-body);
  font-size: var(--a3-text-md);
  font-weight: 700;
  color: var(--a3-text);
}

/* Not a disabled button. A control that looks pressable and refuses is the same
   lie one step earlier, which Engineering Foundation section 7 forbids. */
.a3-storeprod__gone {
  margin: 0;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
}

.a3-storeprod form { display: grid; }

/* -----------------------------------------------------------------------------
   About this shop.
   ----------------------------------------------------------------------------- */
.a3-about {
  display: grid;
  gap: var(--a3-space-3);
  margin-top: var(--a3-space-6);
  padding-top: var(--a3-space-5);
  border-top: 1px solid var(--a3-border);
}

.a3-about__text { margin: 0; color: var(--a3-text-secondary); }

/* WHOSE WORDS THESE ARE. Asiam3 may not print "Trusted Seller" over a shop it
   has never looked at, so anything a merchant says about themselves is
   attributed on the page rather than presented as Asiam3's finding. */
.a3-about__source {
  margin: 0;
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
}

.a3-about__claims {
  margin: 0;
  padding-inline-start: var(--a3-space-5);
  display: grid;
  gap: var(--a3-space-2);
  color: var(--a3-text-secondary);
  font-size: var(--a3-text-sm);
}

.a3-about__where {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--a3-space-2);
  font-size: var(--a3-text-sm);
  color: var(--a3-text-muted);
}

/* -----------------------------------------------------------------------------
   THE PRODUCT PAGE, from sheet 05-product.

   The drawing puts the photograph and the decision side by side on a laptop and
   stacks them on a phone, with the picture first in both. That order is the
   markup's, so it holds with no stylesheet at all.

   What is deliberately not here: a variant picker, a Contact control, a Returns
   panel, a delivery claim and an attribute table. Each was drawn and each names
   a capability Asiam3 does not have. productPage.ts records why, and the
   decisions sit in OPEN_DECISIONS rather than in a comment nobody reads.
   ----------------------------------------------------------------------------- */
.a3-product {
  display: grid;
  gap: var(--a3-space-6);
  align-items: start;
}
@media (min-width: 60rem) {
  /* The picture takes the larger share, which is what the drawing gives it. */
  .a3-product { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: var(--a3-space-8); }
}

.a3-product__gallery { min-width: 0; }
.a3-product__detail { display: grid; gap: var(--a3-space-4); align-content: start; }

/* -----------------------------------------------------------------------------
   Product details, the merchant's own table

   PLACED WHERE THE SHEET PLACES IT, which is two different places. On a wide
   screen it sits under the photograph, in the picture's column: sheet
   `05-product-a`, desktop frames. On a phone it is the last thing on the page,
   after the basket and the shop -- a customer scrolling a phone wants the price
   and the button before the material.

   One element, two positions, and no duplicate markup: the grid places it in
   column one, row two, only at the width where there are two columns.
----------------------------------------------------------------------------- */
.a3-productdetails { min-width: 0; }
@media (min-width: 60rem) {
  .a3-productdetails { grid-column: 1; grid-row: 2; }
}
.a3-productdetails__title {
  margin: 0 0 var(--a3-space-3);
  font-family: var(--a3-font-display);
  font-size: var(--a3-text-lg);
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text);
}
.a3-productdetails__table {
  width: 100%;
  border-collapse: collapse;
  /* The sheet's table has no outer frame and no zebra. Its rows are separated
     by one hairline and nothing else. */
  font-size: var(--a3-text-sm);
}
.a3-productdetails__row { border-top: 1px solid var(--a3-border); }
.a3-productdetails__row:last-child { border-bottom: 1px solid var(--a3-border); }
.a3-productdetails__label {
  /* A label, not a heading shouted at the reader. The sheet sets both columns
     at the same weight and separates them by colour and position alone. */
  width: 40%;
  padding: var(--a3-space-3) var(--a3-space-4) var(--a3-space-3) 0;
  text-align: start;
  font-weight: var(--a3-weight-regular);
  color: var(--a3-text-secondary);
  vertical-align: top;
}
.a3-productdetails__value {
  padding: var(--a3-space-3) 0;
  color: var(--a3-text);
  vertical-align: top;
  /* A merchant may type a long value and it must wrap rather than widen the
     table past the column and take the page with it. */
  overflow-wrap: anywhere;
}
/* WHOSE WORDS THESE ARE. Quiet, because it is a caption and not a warning, but
   present, because Article 4 is why the table is allowed to exist at all. */
.a3-productdetails__whose {
  margin: var(--a3-space-3) 0 0;
  font-size: var(--a3-text-xs);
  color: var(--a3-text-muted);
}

.a3-product__name {
  margin: 0;
  font-size: var(--a3-text-2xl);
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text);
  line-height: 1.2;
}
.a3-product__summary { margin: 0; color: var(--a3-text-secondary); max-width: var(--a3-measure); }

/* The loudest thing after the name, as the drawing has it. */
.a3-product__price {
  margin: 0;
  font-size: var(--a3-text-2xl);
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text);
}

.a3-product__buy { display: grid; gap: var(--a3-space-3); }
.a3-btn--wide { width: 100%; justify-content: center; }

/* -----------------------------------------------------------------------------
   The photograph, and the strip beside it.
   ----------------------------------------------------------------------------- */
.a3-productshot { display: grid; gap: var(--a3-space-3); min-width: 0; }

/* -----------------------------------------------------------------------------
   THE STRIP STANDS BESIDE THE PHOTOGRAPH ON A WIDE SCREEN.

   Sheet `05-product-a` draws it that way in both desktop frames -- a column of
   thumbnails to the left of the picture -- and below the picture on both phone
   frames. The markup is one order (picture, then strip) because that is the
   order they matter in when they stack; the grid moves the strip on a wide
   screen without a second copy of the markup and without moving it in the
   reading order, which is where a customer on a screen reader wants it.
----------------------------------------------------------------------------- */
@media (min-width: 60rem) {
  .a3-productshot { grid-template-columns: 4.5rem minmax(0, 1fr); align-items: start; }
  /* The picture takes the second column and both rows when there is a strip; a
     product with one photograph has no strip, and the picture spans anyway. */
  .a3-productshot__image,
  .a3-productshot__empty { grid-column: 2; grid-row: 1; }
  .a3-productshot__strip {
    grid-column: 1;
    grid-row: 1;
    /* A column, and it scrolls downward for the same reason the row scrolls
       sideways: five thumbnails must not set the height of the picture. */
    flex-direction: column;
    overflow-x: visible;
    overflow-y: auto;
    max-height: 100%;
  }
}

.a3-productshot__image,
.a3-productshot__empty {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--a3-radius-lg);
  border: 1px solid var(--a3-border);
  background: var(--a3-bg-sunken);
}
.a3-productshot__empty { display: grid; place-items: center; }
.a3-productshot__monogram {
  font-size: var(--a3-text-2xl);
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text-muted);
}

/* A row that scrolls rather than wraps: five thumbnails on a 320px screen wrap
   into a block that pushes the price off the first screen. */
.a3-productshot__strip {
  display: flex;
  gap: var(--a3-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  scrollbar-width: thin;
}
.a3-productshot__thumb { flex: 0 0 auto; }
.a3-productshot__thumb a {
  /* inline-flex and centred, which is the pattern every other link target in
     the system uses. `block` would honour the minimum too, but the guard over
     these rules asks for one shape and a second shape for one thumbnail is not
     worth the exception. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4.5rem;
  height: 4.5rem;
  /* Well past the floor already, and declared anyway: the rule is that a
     component styling its links states the minimum, so that shrinking the
     thumbnail later cannot quietly take it under. */
  min-height: var(--a3-target-inline-min);
  border-radius: var(--a3-radius-md);
  border: 2px solid transparent;
  overflow: hidden;
}
.a3-productshot__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.a3-productshot__thumb a[aria-current] { border-color: var(--a3-primary-700); }
.a3-productshot__thumb a:focus-visible { outline: var(--a3-focus-ring); outline-offset: 2px; }

/* -----------------------------------------------------------------------------
   Which basket this joins, said beside the button rather than at checkout.
   ----------------------------------------------------------------------------- */
.a3-basketscope {
  display: flex;
  gap: var(--a3-space-3);
  align-items: flex-start;
  margin: 0;
  padding: var(--a3-space-3) var(--a3-space-4);
  border-radius: var(--a3-radius-md);
  background: var(--a3-primary-100);
  color: var(--a3-text);
  font-size: var(--a3-text-sm);
}
.a3-basketscope__glyph { color: var(--a3-primary-700); flex: 0 0 auto; }

/* -----------------------------------------------------------------------------
   Whose shop this is. A market is not a shelf inside somebody else's shop, so
   the business is a panel rather than a line of small print.
   ----------------------------------------------------------------------------- */
.a3-soldby {
  display: grid;
  gap: var(--a3-space-3);
  padding: var(--a3-space-4);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-bg-raised);
}
.a3-soldby__shop {
  display: flex;
  gap: var(--a3-space-3);
  align-items: center;
  text-decoration: none;
  color: inherit;
  min-width: 0;
}
.a3-soldby__badge {
  flex: 0 0 auto;
  width: 3rem;
  height: 3rem;
  border-radius: var(--a3-radius-md);
  display: grid;
  place-items: center;
  overflow: hidden;
  font-weight: var(--a3-weight-semibold);
}
.a3-soldby__badge img { width: 100%; height: 100%; object-fit: cover; }
.a3-soldby__words { display: grid; min-width: 0; }
.a3-soldby__label {
  font-size: var(--a3-text-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--a3-text-muted);
}
.a3-soldby__name {
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text);
  overflow-wrap: anywhere;
}
.a3-soldby__category { font-size: var(--a3-text-sm); color: var(--a3-text-secondary); }
.a3-soldby__rating {
  display: flex;
  gap: var(--a3-space-2);
  align-items: center;
  margin: 0;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
}
/* -----------------------------------------------------------------------------
   A fact the merchant states, inside the Sold-by panel.

   The sheet draws an icon, a small label and a sentence. The attribution line
   under it is ours and not the sheet's: without it a Delivery heading under
   Asiam3's chrome reads as Asiam3 arranging the delivery, which Asiam3's own
   published Terms say it does not do.
----------------------------------------------------------------------------- */
.a3-soldby__fact {
  display: flex;
  gap: var(--a3-space-3);
  align-items: start;
  margin: var(--a3-space-4) 0 0;
  padding-top: var(--a3-space-4);
  border-top: 1px solid var(--a3-border);
}
.a3-soldby__facticon { flex: 0 0 auto; color: var(--a3-text-muted); line-height: 0; }
.a3-soldby__factwords { display: grid; gap: 0.15em; min-width: 0; }
.a3-soldby__factlabel {
  font-size: var(--a3-text-xs);
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text);
}
.a3-soldby__facttext { font-size: var(--a3-text-sm); color: var(--a3-text-secondary); }
.a3-soldby__factwhose { font-size: var(--a3-text-xs); color: var(--a3-text-muted); }

.a3-soldby__link {
  min-height: var(--a3-target-inline-min);
  display: inline-flex;
  align-items: center;
}

/* -----------------------------------------------------------------------------
   Out of stock is a state, not an error. Nothing red, nothing apologising, and
   the way on leads into the rest of the shop.
   ----------------------------------------------------------------------------- */
.a3-product__gone {
  display: grid;
  gap: var(--a3-space-2);
  padding: var(--a3-space-4);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-bg-sunken);
}
.a3-product__gonetitle {
  margin: 0;
  font-weight: var(--a3-weight-semibold);
  color: var(--a3-text);
}
.a3-product__gonetext { margin: 0; color: var(--a3-text-secondary); font-size: var(--a3-text-sm); }
/* -----------------------------------------------------------------------------
   The merchant's own details rows

   Two boxes to a row on a wide screen, stacked on a phone. The label box is
   narrower than the value box because a label is a word and a value is a
   phrase, and giving them equal width makes every value box look too small for
   what goes in it.
----------------------------------------------------------------------------- */
.a3-detailsfield { border: 0; margin: 0; padding: 0; min-width: 0; }
.a3-detailrows { display: grid; gap: var(--a3-space-3); margin-top: var(--a3-space-2); }
@media (min-width: 40rem) {
  .a3-detailrows { gap: var(--a3-space-2); }
}
.a3-detailsfield__add { margin-top: var(--a3-space-3); }
/* "Add photos", the step after "Add this product" (Founder, 3 October 2026).
   The photos already added sit as a row of thumbnails above the picker, so a
   merchant sees what they have before adding another. */
.a3-photostep__lead { margin-bottom: 0; }
.a3-photostep__market { margin-bottom: 0; color: var(--a3-text-secondary); }
.a3-photostep__photos {
  list-style: none;
  margin-bottom: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
  gap: var(--a3-space-2);
  max-width: 32rem;
}
.a3-photostep__photos .a3-product-image { aspect-ratio: 1; object-fit: cover; }
.a3-photostep__next { justify-self: start; }
/* "Hints" (Founder, 3 October 2026): small, closed until tapped. The same
   native `details` as the Payments page's disclosures, made smaller because it
   sits inside one section rather than heading one. */
.a3-detailshints { margin-top: var(--a3-space-3); }
.a3-detailshints .a3-disclose__summary {
  padding: var(--a3-space-2) var(--a3-space-3);
  font-size: var(--a3-text-sm);
}
.a3-detailshints__body { font-size: var(--a3-text-sm); }
.a3-detailshints__body p { margin: 0 0 var(--a3-space-2); }
.a3-detailshints__lead { font-weight: var(--a3-weight-medium); color: var(--a3-text); }
.a3-detailshints__examples {
  list-style: none;
  margin: 0 0 var(--a3-space-3);
  padding: 0;
  display: grid;
  gap: var(--a3-space-2);
}
.a3-detailshints__example { display: grid; }
.a3-detailshints__name { font-weight: var(--a3-weight-medium); color: var(--a3-text); }
/* The word inside each box's label carries `a3-field__label` so the guard on
   grey examples can see a visible name; here it keeps the label's own small,
   quiet look. */
.a3-detailrow__name .a3-field__label {
  display: inline;
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
}
/* "Remove this detail": under the pair on a phone, beside it on a wide screen. */
.a3-detailrow__remove { display: flex; align-items: center; min-width: 0; }
.a3-detailrow__removetick {
  display: inline-flex;
  align-items: center;
  gap: var(--a3-space-2);
  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
  min-height: var(--a3-touch-min);
}
/* A ROW IS A GRID, and this comes BEFORE the head's rules on purpose. The head
   carries both classes, so `.a3-detailrow { display: grid }` and
   `.a3-detailrow--head { display: none }` tie on specificity and the later one
   wins. Until 3 October 2026 the grid came later, and on a phone the column
   names showed above the rows while every box showed its own name too: the
   labels repeated above every input. `theDetailsBoxesAreOnThePage.test.ts`
   reads this order. */
/* ON A PHONE, EACH PAIR IS ONE UNIT (Founder, 3 October 2026: "Each Detail +
   Information pair should be visually clear as one unit on mobile"). The two
   boxes stack, so without an edge a Detail reads as belonging to the
   Information above it as easily as the one below. On a wide screen the pair
   is one line, which is already a unit, and the edge goes. */
.a3-detailrow {
  display: grid;
  gap: var(--a3-space-2);
  min-width: 0;
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-md);
  padding: var(--a3-space-3);
}
@media (min-width: 40rem) {
  .a3-detailrow {
    /* A fixed third column, not `auto`: the column names above are their own
       grid, and `auto` would size each grid's third column differently. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr) 11rem;
    border: 0;
    padding: 0;
  }
}
/* THE COLUMN NAMES AND THE PER-BOX NAMES ARE THE SAME RULE AT TWO WIDTHS.
   Exactly one of them is on screen at any width, and never neither: a form
   labelled at one width and unlabelled at another is the defect
   `ENGINEERING_FOUNDATION.md` section 12 is about. */
.a3-detailrow--head { margin-top: var(--a3-space-3); display: none; }
.a3-detailrow__head { font-size: var(--a3-text-xs); color: var(--a3-text-muted); }
.a3-detailrow__name { font-size: var(--a3-text-xs); color: var(--a3-text-muted); }
@media (min-width: 40rem) {
  /* Wide: one pair of column names above, and the per-box names go to the
     screen reader only -- repeating them twelve times beside the columns that
     already say it is three of the same words. */
  .a3-detailrow--head { display: grid; }
  .a3-detailrow__name {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}
/* -----------------------------------------------------------------------------
   What a basket costs

   The brief requires prices and a subtotal here, and requires them marked as
   not final: the server decides price at the moment of the transaction, so a
   figure on a basket is what these items cost NOW.

   The per-line amounts sit between the name and the quantity box, which is the
   order the sheet's table uses -- Item, Price, Quantity, Total -- collapsed to
   one column on the narrow layout this list already is.
----------------------------------------------------------------------------- */
.a3-basket__money {
  display: grid;
  justify-items: end;
  gap: 0.1em;
  min-width: 0;
  text-align: end;
}
.a3-basket__each { font-size: var(--a3-text-xs); color: var(--a3-text-muted); }
.a3-basket__linetotal { font-weight: var(--a3-weight-semibold); color: var(--a3-text); }
/* A withdrawn line has no current price, and saying so is not an error. */
.a3-basket__nomoney { font-size: var(--a3-text-xs); color: var(--a3-text-muted); }

.a3-subtotal {
  border-top: 1px solid var(--a3-border);
  padding-top: var(--a3-space-4);
  display: grid;
  gap: var(--a3-space-2);
}
.a3-subtotal__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--a3-space-4);
  margin: 0;
}
.a3-subtotal__label { font-weight: var(--a3-weight-semibold); color: var(--a3-text); }
.a3-subtotal__amount { font-size: var(--a3-text-lg); font-weight: var(--a3-weight-semibold); }
/* NOT small print. It is the sentence that stops a figure being read as a
   quote, and a customer who cannot read it has been quoted a number. */
.a3-subtotal__note { margin: 0; font-size: var(--a3-text-sm); color: var(--a3-text-secondary); }

/* ---------------------------------------------------------------------------
   Setup progress
   ---------------------------------------------------------------------------

   Drawn from `design/context/explorations/2026-09-09-gpt/11-corrected-top-bar`
   and `05-phone-established`, which put this card directly under the shop and
   above the seven sections at both widths.

   `FINDINGS.md` on that run called it the most valuable thing on the sheets,
   and not because of the bars: it is a definition of what makes a shop ready,
   and Asiam3 did not have one. `src/domains/shop/domain/shopSetup.ts` is that
   definition. Everything here is the picture of it.
   --------------------------------------------------------------------------- */

.a3-setup {
  display: grid;
  gap: var(--a3-space-4);
}

.a3-setup__head {
  display: grid;
  /* glyph | words | count. The count is last so it sits against the right edge
     at every width, which is what both sheets draw. */
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--a3-space-3);
}

.a3-setup__glyph {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border-radius: var(--a3-radius-md);
  /* DARKER THAN A SECTION GLYPH, deliberately. The seven section glyphs below
     are pale tinted squares; this one is solid, because the sheets make it the
     one thing on the page that is not a section. */
  background: var(--a3-primary-700);
  color: var(--a3-white);
}

.a3-setup__glyph .a3-icon {
  inline-size: 1.35rem;
  block-size: 1.35rem;
}

.a3-setup__naming {
  /* The middle track of the head grid. It exists so the title and its sentence
     occupy ONE column between the glyph and the count, rather than two, and it
     keeps them tight against each other while the row is centred. */
  display: grid;
  gap: 0.125rem;
  min-inline-size: 0;
}

.a3-setup__title {
  margin: 0;
  font-size: var(--a3-text-md);
  font-weight: 600;
  color: var(--a3-text);
}

.a3-setup__text {
  margin: 0;
  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
}

.a3-setup__count {
  margin: 0;
  font-weight: 600;
  color: var(--a3-text);
  /* The count is the accessible statement of progress, so the bars beside it
     carry `aria-hidden` and this must never be allowed to wrap away from them. */
  white-space: nowrap;
}

.a3-setup__bars {
  display: grid;
  /* One track per item, from the list itself. `auto-fit` rather than a literal
     seven: the definition owns how many there are, and a stylesheet repeating
     the number is a second place for it to be wrong. */
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--a3-space-1);
}

.a3-setup__bar {
  block-size: 0.35rem;
  /* Rounded to its own height rather than to a token: these are 5.6px tall and
     every radius token in the system is larger than that, so a token would
     square them off. */
  border-radius: 0.35rem;
  background: var(--a3-border);
}

.a3-setup__bar--done {
  background: var(--a3-success-600);
}

.a3-setup__list {
  margin: 0;
  padding: 0;
  list-style: none;
  /* TWO COLUMNS, FILLED DOWN, AND THE SPLIT IS EXACT.

     The sheets read `Business information, Photos, Categories and delivery,
     Highlights` down the left and the remaining three down the right, at BOTH
     widths. That is column flow rather than a grid filling across, which would
     put `Shop address` beside `Business information` and reorder the checklist
     against the sections underneath it.

     THE FIRST VERSION USED `columns: 2` AND WAS WRONG ON A REAL PHONE. CSS
     multicolumn balances by HEIGHT, not by count, so `Business information`
     wrapping onto a second line at 390px moved the break up and put `Highlights`
     at the top of the right column. Found by rendering the page and looking at
     it, which is the only way it could have been found -- the markup was correct
     and every assertion passed.

     So the split is stated rather than balanced.

     THE FOUR IS `ceil(7 / 2)` AND IT IS WRITTEN HERE RATHER THAN SENT FROM THE
     MARKUP, which was the first attempt. The merchant surface serves
     `style-src 'self'` with no `unsafe-inline`, so an inline custom property on
     the list would be dropped by the browser and the grid would silently fall
     back. `theSetupCard.test.ts` fails if `SETUP_STEPS` ever stops agreeing
     with this number, which is what keeps the two honest. */
  display: grid;
  grid-template-rows: repeat(4, auto);
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  column-gap: var(--a3-space-5);
}

.a3-setup__item {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--a3-space-2);
  padding-block: var(--a3-space-1);
  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
  /* Left from the multicolumn version this started as, and kept deliberately:
     it does nothing under a grid, and it is what stops a row splitting between
     its tick and its words if the list is ever printed. */
  break-inside: avoid;
}

.a3-setup__item--done {
  color: var(--a3-text);
}

.a3-setup__tick {
  display: grid;
  place-items: center;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border-radius: 50%;
  /* AN EMPTY RING FOR WHAT IS NOT DONE, which is sheet 10 at `0 of 7`. Not a
     cross and not a warning colour: nothing is wrong with a shop that is being
     set up, and a red list would tell a new merchant they have seven problems
     on their first day. */
  border: 1.5px solid var(--a3-border-strong);
  color: transparent;
}

.a3-setup__item--done .a3-setup__tick {
  border-color: var(--a3-success-600);
  background: var(--a3-success-600);
  color: var(--a3-white);
}

.a3-setup__tick .a3-icon {
  inline-size: 0.8rem;
  block-size: 0.8rem;
  stroke-width: 3;
}

@media (min-width: 60rem) {
  .a3-setup__list {
    column-gap: var(--a3-space-6);
  }
}

/* ---------------------------------------------------------------------------
   HOW A CUSTOMER CHOOSES TO PAY. Sheet `05-paying-2` screen 6.

   A bordered row per method, with the chosen one lifted by its border and a
   tinted ground -- which is how the sheet distinguishes it. The radio itself
   stays visible and stays a radio: it is the control a screen reader announces
   and the thing a keyboard moves between, and replacing it with a drawn circle
   is how a chooser stops being operable without a mouse.

   `:has()` carries the selected state with no script, which is what lets this
   surface keep `D4` -- the customer surface ships none.
   --------------------------------------------------------------------------- */
.a3-methods { border: 0; padding: 0; margin: 0; min-width: 0; }

.a3-method {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: center;
  gap: var(--a3-space-3);
  padding: var(--a3-space-3) var(--a3-space-4);
  min-height: var(--a3-touch-min);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  background: var(--a3-bg-raised);
  cursor: pointer;
}

/* The chosen one, drawn as the sheet draws it. Border AND ground, never colour
   alone: a customer who cannot separate the green from the grey must still be
   able to see which row they are on. */
.a3-method:has(.a3-method__input:checked) {
  border-color: var(--a3-surface-accent);
  box-shadow: inset 0 0 0 1px var(--a3-surface-accent);
  background: var(--a3-primary-100);
}

/* Focus belongs on the row, because the row is what a person sees. The input
   keeps its own ring for anything that does not support `:has()`. */
.a3-method:has(.a3-method__input:focus-visible) {
  outline: 2px solid var(--a3-focus);
  outline-offset: 2px;
}

.a3-method__glyph { display: inline-flex; color: var(--a3-text-muted); }
.a3-method:has(.a3-method__input:checked) .a3-method__glyph { color: var(--a3-surface-accent); }

.a3-method__body { display: grid; gap: 2px; min-width: 0; }
.a3-method__label { font-weight: var(--a3-weight-semibold); }
.a3-method__detail { font-size: var(--a3-text-sm); color: var(--a3-text-muted); }

/* ---------------------------------------------------------------------------
   THE PAYMENT PAGE AT BOTH WIDTHS. Sheet `05-paying-2` screen 6.

   The sheet draws this screen twice: a phone with the method chooser and
   nothing else, and a desktop with the chooser on the left and an order
   summary on the right. Asiam3 merges the sheet's "choose" and "review"
   screens into one page, so the summary must appear on the phone too -- and it
   must appear ABOVE the button, because a person should not press Pay and then
   be shown the total.

   SO THE SOURCE ORDER IS THE PHONE'S and the desktop moves the summary, rather
   than the reverse. Placing it second in the source and relying on a column to
   rescue it would leave the total after the button on every phone, which is the
   width that matters most here.
   --------------------------------------------------------------------------- */
.a3-paylayout { display: grid; gap: var(--a3-space-6); min-width: 0; align-content: start; }

@media (min-width: 60rem) {
  .a3-paylayout {
    grid-template-columns: minmax(0, 1fr) 20rem;
    align-items: start;
  }
  /* Explicit placement, because the summary comes FIRST in the source and
     belongs on the right. Auto-flow would put it back in column one. */
  .a3-paylayout__main { grid-column: 1; grid-row: 1; }
  .a3-paylayout__side { grid-column: 2; grid-row: 1; }
}

/* The method rows are comfortable at any width and gain nothing from becoming
   a row of three: a list a person reads down is easier to compare than a strip
   they read across, and the sheet draws them stacked at BOTH widths. */

/* ---------------------------------------------------------------------------
   YOUR ORDERS AT BOTH WIDTHS. Sheet `04-orders-b`.

   The sheet draws this list twice and they are genuinely different shapes: a
   phone gets a stack of cards, each led by a photograph of what was bought; a
   wide screen gets a TABLE -- Shop, Items, Total, Status, Date, View.

   THE MARKUP DOES NOT CHANGE. The same row becomes a table by gaining columns,
   which keeps one set of semantics, one reading order for a screen reader, and
   one thing to maintain. A second markup path for a second width is how the
   two quietly stop agreeing.
   --------------------------------------------------------------------------- */
@media (min-width: 60rem) {
  .a3-orderrow__link {
    grid-template-columns: auto minmax(8rem, 1.6fr) auto auto auto auto;
    align-items: center;
    gap: var(--a3-space-4);
  }

  /* The body stops being a stack and becomes the shop column alone: the facts
     beside it are columns of their own now. */
  .a3-orderrow__body { display: contents; }
  .a3-orderrow__title { grid-column: 2; }

  /* One line, in the sheet's column order. */
  .a3-orderrow__meta { display: contents; }
  .a3-orderrow__count { grid-column: 3; justify-self: end; }
  .a3-orderrow__total { grid-column: 4; justify-self: end; font-variant-numeric: tabular-nums; }
  .a3-orderrow__date  { grid-column: 6; justify-self: end; white-space: nowrap; }
  .a3-orderrow__link .a3-status { grid-column: 5; justify-self: start; }

  /* The separators exist for the phone, where nothing else divides the facts.
     At this width the columns do it, and a dot between two columns is noise. */
  .a3-orderrow__sep { display: none; }
}

/* ---------------------------------------------------------------------------
   ONE ORDER AT BOTH WIDTHS. Sheet `04-orders-b`, order detail.

   The desktop frame is a different shape rather than the phone stretched: the
   heading spans the page, and beneath it the timeline and what was ordered sit
   on the left while the payment, the note to the shop and the way to cancel sit
   in a rail on the right.

   The same 20rem rail the merchant payments page already uses. One measurement
   for the same idea, rather than a second number that drifts from the first.
   --------------------------------------------------------------------------- */
.a3-orderlayout {
  display: grid;
  gap: var(--a3-space-6);
  min-width: 0;
  align-content: start;
}
.a3-orderlayout__main,
.a3-orderlayout__side {
  display: grid;
  gap: var(--a3-space-5);
  min-width: 0;
  align-content: start;
}

@media (min-width: 60rem) {
  .a3-orderlayout {
    grid-template-columns: minmax(0, 1fr) 20rem;
    align-items: start;
  }
}

/* -----------------------------------------------------------------------------
   THE MERCHANT WORKSPACE AT DESKTOP WIDTH. Sheets `06-merchant-1`, `-2`, `-3`.

   ALL THREE SHEETS DRAW THE SAME DESKTOP SHAPE and it is not the phone's made
   wider. `06-merchant-1` heads its lower half "Merchant experience -- desktop /
   A larger workspace for growing your business", and every one of the three
   draws the same thing: a LEFT RAIL carrying the navigation with the work
   beside it, where the phone stacks a bottom bar under a single column.

   ENGINEERING_FOUNDATION section 2 argued for this before any sheet did:
   *"Not a desktop shrunk down. The information hierarchy, NAVIGATION, forms,
   TABLES ... are each worked out for the form factor they run on. A merchant
   scanning two hundred orders on a laptop and the same merchant checking one on
   a phone in a market are doing different tasks with the same data."*

   Until now the merchant surface had NO desktop layout of any kind. Above 60rem
   `.a3-tabbar` hid, the horizontal `.a3-nav` appeared, and every merchant page
   was the phone's one column with empty space either side. The customer surface
   had `a3-orderlayout` and `a3-paylayout`; the merchant surface, which is where
   somebody actually sits at a desk, had nothing. Article 15 lists cross-device
   usability among the things *"addressed when they become applicable, not
   scheduled for a later batch"*, so that absence was a gap rather than a polish
   item nobody had reached.

   WHY A STYLESHEET BLOCK AND NOT A SHELL REWRITE, which is the load-bearing
   decision here. Section 2 again: *"the first market is mobile-money-first and
   phone-heavy, so the phone is the primary case rather than the fallback."*
   Everything here sits behind `min-width: 60rem` and nothing below it moves.
   And `merchantPage` passes no masthead, so `.a3-nav` and `#main` are ALREADY
   ADJACENT in the source -- a grid pairs them into one row without moving a tag.
   Invariant 23 wants a capability identical on every device with only its
   presentation adapting; a change that alters no markup satisfies that by
   construction rather than by argument.

   G68, BOTH HALVES, applied here rather than left to be inferred:

   BUILT because the sheets draw it and Asiam3 lacked it -- the rail itself.

   KEPT because Asiam3 has it and no sheet draws it:
     - NOTIFICATIONS, with its unread count. Every sheet puts a bell glyph in
       the top bar and none carries a navigation entry for it. Asiam3 has the
       page, the count, and the reason the count exists -- *"a merchant who has
       to open a page to discover whether anything happened is back to
       refreshing"*. The rail keeps the entry and WRAPS rather than truncating,
       so "Notifications (12)" stays readable in a narrower column.
     - "YOUR SHOPS", PLURAL. All three sheets draw a single-shop merchant: one
       identity card, "Your shop", "Shop profile". Asiam3 has
       `/merchant/shops`, `/merchant/shops/new` and `/merchant/shops/:shopId`,
       and a merchant may run several. The brief did not mention it, so the
       designer could not draw it, and a sheet's silence is not a decision.
     - PAYMENTS. The sheets draw "Payouts", which is settlement history.
       Asiam3's `/merchant/payments` is the payment ARRANGEMENT and destination
       -- a different thing, and the one that is actually built.

   THE SHOP IDENTITY CARD the sheets place in the rail is NOT built, and the
   reason is the multi-shop finding above rather than effort: a card naming one
   shop cannot honestly sit in the chrome of a merchant who has three. Recorded
   as a finding rather than approximated with whichever shop an adapter happened
   to return first, which is the failure `soleMembership` already refuses for
   memberships.
   --------------------------------------------------------------------------- */
@media (min-width: 60rem) {
  /* `:has()` RATHER THAN A BARE SELECTOR, and it is not a flourish. The grid
     must only exist where there is a rail to put in column one. A refusal page
     rendered with no navigation links has no `.a3-menu` at all, and a bare
     grid would leave its content sitting in column two behind 14rem of
     nothing. Where `:has()` is unsupported the body is simply not a grid and
     the page is the single column it was before, which is EF section 2's
     "an absent capability degrades to something that still works". */
  body.a3[data-a3-surface="merchant"]:has(> .a3-menu) {
    display: grid;
    grid-template-columns: 14rem minmax(0, 1fr);
    align-content: start;
  }

  /* THE GRID CHILD IS `.a3-menu`, NOT `.a3-nav`, and a guard is the only
     reason this is right. `.a3-nav` is wrapped in a `<details class="a3-menu">`
     -- the disclosure that gives a phone its menu toggle -- so it is a
     GRANDCHILD of body. The first version of this block placed `> .a3-nav`,
     which matches nothing: every stylesheet assertion passed and the rail did
     not exist. Invariant 30, earned rather than quoted. */
  body.a3[data-a3-surface="merchant"] > * { grid-column: 1 / -1; }
  body.a3[data-a3-surface="merchant"] > .a3-menu { grid-column: 1; }

  /* `width: 100%` AND IT IS NOT REDUNDANT, which is the whole of this defect.

     `.a3-page` carries `margin: 0 auto`, the ordinary idiom for centring a
     block under a `max-width`. As a BLOCK it means "fill the space, and centre
     if you are capped". As a GRID ITEM it means something else entirely: an
     auto inline margin suppresses the stretch a grid item would otherwise get,
     so the item is sized to FIT-CONTENT and the margins then absorb whatever
     is left. The rail made `#main` a grid item, and that one line silently
     changed meaning underneath it.

     What it cost, measured on the deployed site signed in as a merchant at
     1440px: `/merchant/orders` used 615px of a 1216px column and
     `/merchant/notifications` 617px, while `/merchant/shops` got 911px and
     `/merchant/payments` 1200px -- the differences are the pages' own
     max-content widths, which is the signature of fit-content sizing rather
     than of any rule anybody wrote. THE WORKSPACE GOT NARROWER AS THE WINDOW
     GOT WIDER: the same orders list is 892px at 940px wide and 615px at
     1440px, so crossing into the desktop layout COST the merchant 277px of
     the table the desktop layout exists to give them.

     A definite width restores the stretch without touching `.a3-page`, which
     is still right everywhere it is not a grid item. The cap and the centring
     both survive: at 1440 the column is 1216, `100%` caps to 1200, and the
     auto margins split the remaining 16px exactly as they always did.

     EVERY SHEET DRAWS THE WORK FILLING THE WIDTH. `06-merchant-1`'s desktop
     half runs its stat row, its recent-orders table and its lower panels edge
     to edge beside the rail. Nothing in the three merchant sheets draws a
     centred column floating in the middle of the workspace.

     No guard could have seen this. Every stylesheet assertion about the grid
     was true -- the columns are 14rem and 1fr, `.a3-menu` is in column one and
     `#main` is in column two. What was false was the RESULT, and a guard that
     reads a stylesheet cannot compute a layout. GLOSSARY section 8 names this
     boundary: the gate protects the artefact and says nothing about what is
     true of a running page. It was found by rendering the page and measuring
     it, which is what section 10 of the handover sent this session to do. */
  body.a3[data-a3-surface="merchant"] > #main { grid-column: 2; width: 100%; }

  body.a3[data-a3-surface="merchant"] > .a3-menu {
    /* STICKY, so the rail follows a merchant down a table of two hundred
       orders. `align-self: start` is what makes that work: a grid item
       stretched to its row has no room to move inside it, and sticky becomes a
       no-op. */
    position: sticky;
    top: 0;
    align-self: start;

    /* AND NOT THE LINE UNDERNEATH IT. `.a3-menu` carries a `border-bottom`
       because on a phone it is a collapsible strip ABOVE the content, and a
       strip needs an edge. In a rail it is a 224px rule floating in the
       middle of the left column with nothing under it -- seen once the
       divider beside it ran the full height and stopped hiding it. */
    border-bottom: 0;
  }

  /* THE DIVIDER IS THE COLUMN EDGE, AND IT MOVED HERE TO BE ONE.

     It was `border-right` on the rail, and the rail is `align-self: start`, so
     the line was only as tall as the links: MEASURED AT 245px against a 490px
     workspace, a stub hanging off the top of the page rather than the edge of a
     column. All three merchant sheets draw a full-height rail.

     Recorded as open on 17 September for the pass that builds the four page
     layouts, because it is anatomy rather than a page. The dashboard is the
     fourth of those four, so it is settled here.

     On `#main` rather than on the rail because `#main` STRETCHES to the grid
     row while the rail must not, and the two edges are the same edge. The line
     now runs whichever of the two is taller, which is what a column edge is. */
  body.a3[data-a3-surface="merchant"] > #main { border-left: 1px solid var(--a3-border); }

  /* A column of links rather than a strip of them. `overflow-x: auto` and the
     bottom rule belong to the horizontal nav and are wrong here: a vertical
     rail that scrolls sideways is a rail with something hidden in it. */
  body.a3[data-a3-surface="merchant"] .a3-menu .a3-nav {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: var(--a3-space-1);
    padding: var(--a3-space-5) var(--a3-space-3);
    overflow-x: visible;
    border-bottom: 0;
  }

  body.a3[data-a3-surface="merchant"] .a3-menu .a3-nav__link {
    justify-content: flex-start;
    padding: var(--a3-space-3) var(--a3-space-4);
    border-radius: var(--a3-radius-md);
    border-bottom: 2px solid transparent;
    /* THE COUNT MUST SURVIVE THE NARROWER COLUMN. The horizontal nav sets
       `nowrap` because a strip scrolls sideways; a rail does not, and a
       clipped "Notifications (12)" is the count silently not delivered. */
    white-space: normal;
  }

  /* The current page reads as a filled row, which is what all three sheets
     draw. The horizontal nav marks it with an underline, and an underline
     beneath a full-width row in a rail reads as a divider instead. */
  body.a3[data-a3-surface="merchant"] .a3-menu .a3-nav__link[aria-current="page"] {
    background: var(--a3-bg-sunken, var(--a3-bg));
    border-bottom-color: transparent;
  }
}

/* -----------------------------------------------------------------------------
   THE SHOP PAGE AT DESKTOP WIDTH. Sheet `06-merchant-3`, Shop profile.

   The sheet draws three zones: the cover and the logo on the left, the labelled
   fields down the centre, and the shop status with a Tips panel on the right.
   Asiam3 had one column of seven section cards at every width.

   PLACEMENT RULES, NOT WRAPPERS, AND A GUARD DECIDED THAT. Three zone wrappers
   were built first, because wrappers give each column its own flow where
   placement rules make the columns share rows. Grouping the sections into
   wrappers changed the order they render in, and `theSetupCard.test.ts`
   refused it: "the sections are not in the order the sheet puts them". That
   order is fixed by the sheets and is the same order the setup checklist
   reads in, so the markup keeps it and the stylesheet does the placing.

   WHAT THAT COSTS, stated rather than discovered later: grid rows align
   ACROSS columns, so where one column holds a taller card than its neighbour
   the shorter one carries slack beneath it. Measured on a real shop at
   1440px, that is two gaps of about a hundred pixels. Spacing is the cost;
   the order was the decision, and the decision wins.

   EACH SECTION IS PLACED BY ITS OWN HEADING ID rather than by `nth-child`.
   Opening a section collapses the others, so the children of this page
   genuinely change shape, and `GLOSSARY.md` section 8 names what a positional
   rule does then: "the target is identified by position or by somewhere, and
   a distant change redefines it".
   --------------------------------------------------------------------------- */
@media (min-width: 60rem) {
  .a3-shoplayout {
    display: grid;
    /* The same 20rem rail as `a3-orderlayout` and the merchant payments page
       on the right. The left column is a little wider because it holds a cover
       photograph, the one thing on this page with a fixed shape. */
    grid-template-columns: 21rem minmax(0, 1fr) 20rem;
    gap: var(--a3-space-5);
    align-items: start;
  }

  /* `.a3-stack` spaces siblings with a margin. Inside a grid that margin sits
     within the cell and adds itself to the gap. */
  .a3-shoplayout > * + * { margin-top: 0; }

  /* EVERYTHING SPANS, THEN THE PLACED THINGS TAKE THEIR COLUMNS. Written this
     way round so anything added to this page later lands full width rather
     than in whichever column it happens to follow. The breadcrumb, the
     heading, the shop cover card and Setup progress are each about the whole
     shop rather than about one zone of it. */
  .a3-shoplayout > * { grid-column: 1 / -1; }

  .a3-shoplayout > .a3-shopsection:has(#section-photos) { grid-column: 1; grid-row: 5; }

  .a3-shoplayout > .a3-shopsection:has(#section-business) { grid-column: 2; grid-row: 5; }
  .a3-shoplayout > .a3-shopsection:has(#section-categories) { grid-column: 2; grid-row: 6; }
  .a3-shoplayout > .a3-shopsection:has(#section-highlights) { grid-column: 2; grid-row: 7; }
  .a3-shoplayout > .a3-shopsection:has(#section-address) { grid-column: 2; grid-row: 8; }

  .a3-shoplayout > .a3-shopsection:has(#section-status) { grid-column: 3; grid-row: 5; }
  .a3-shoplayout > .a3-shopsection:has(#section-catalogue) { grid-column: 3; grid-row: 6; }
  .a3-shoplayout > .a3-shoplayout__tips { grid-column: 3; grid-row: 7; }
}

/* -----------------------------------------------------------------------------
   THE PRODUCTS LIST BECOMES A TABLE AT DESKTOP WIDTH. Sheet `07-products`.

   Its desktop half draws Product, Price (GHS), Stock, Status and Updated as
   columns, with the currency in the HEADING so the cells hold bare numbers.

   THIS EXTENDS THE ORDER ROW TABLE ABOVE RATHER THAN REPLACING IT. That block
   already turns an `.a3-orderrow` into a table at this width, and its own
   docblock states the rule this follows: "THE MARKUP DOES NOT CHANGE. The same
   row becomes a table by gaining columns ... A second markup path for a second
   width is how the two quietly stop agreeing."

   So the product row uses that vocabulary -- `__total` for the price, `__sep`
   for the separator the phone needs and this width does not, `__date` for when
   it last changed. A parallel set of class names was written first and thrown
   away: `__sep` and `__date` already existed for exactly these two jobs, and a
   new `__between` beside an old `__sep` is Article 22 drift by accumulation.
   One of the invented names also COLLIDED with an existing component and
   silently took its large green price styling, which is what a second
   vocabulary costs before anybody notices it is a second vocabulary.

   WHY THE TRACKS ARE STATED HERE AND NOT INHERITED. The order row table sizes
   its columns `auto`, and each ROW is its own grid, so columns line up only as
   far as their contents happen to agree. That is invisible without headings.
   This sheet draws headings, and a heading only means anything if the column
   under it starts where it does -- so the tracks are fixed and the heading row
   repeats them.

   AND EVERY CELL IS PLACED EXPLICITLY, because the block above already places
   `.a3-orderrow__link .a3-status` in column 5 and a product row has TWO status
   badges: the stock and the state. Left to inherit, both landed in column 5,
   stacked, and the Stock column stood empty -- measured on the live page
   before this was written.
   --------------------------------------------------------------------------- */
.a3-producttable__head { display: none; }

/* AND NO DATE ON A PHONE. `__date` is shown on a phone in the ORDERS list,
   where a customer scanning their own orders needs it; the products sheet
   draws no such thing on its mobile half, and a borrowed class brings its
   borrowed visibility with it. Measured before this line existed: the row
   grew from 113px to 143px at 390px, which is the width the first market
   actually uses. */
.a3-producttable .a3-orderrow__date { display: none; }

/* ADD PRODUCT, THE PHONE HALF: full width at the foot of the list, after the
   rows, which is where `07-products` draws it on that half.

   The desktop half draws a DIFFERENT control in a DIFFERENT place -- top right
   beside the heading -- and that one is `.a3-pagetop__action`, built 18
   September. This comment recorded it as owed; it is done, and the record is
   corrected here rather than left to be believed. */
.a3-producttable__add { margin-top: var(--a3-space-4); }
.a3-producttable__add .a3-btn { width: 100%; justify-content: center; }

@media (min-width: 60rem) {
  /* A full-width control is a phone shape. On a wide screen it would be a
     green band the width of the table. */
  .a3-producttable__add .a3-btn { width: auto; }
}

/* THE PHONE ROW, as the mobile half of `07-products` draws it: the name, then
   the PRICE ON ITS OWN LINE, then the stock under that. Asiam3 had all three
   facts on one line with dots between them, which is the ORDERS row shape --
   right there, because an order has a count, a total and a date that read as
   one sentence, and wrong here, because a price and a stock level are two
   different questions a merchant scans for separately.

   THE SEPARATOR GOES WITH IT. Its own rule one block up says the dots "exist
   for the phone, where nothing else divides the facts". Lines divide them
   now, so a dot between two lines is the noise that rule already names.

   ONLY BELOW 60REM. Above it the meta is `display: contents` and these are
   table cells, where stacking them would undo the columns. */
@media (max-width: 59.999rem) {
  .a3-producttable .a3-orderrow__meta {
    display: grid;
    justify-items: start;
    gap: var(--a3-space-1);
  }
  .a3-producttable .a3-orderrow__sep { display: none; }

  /* AND THE STOCK BADGE TAKES THE NEXT LINE, NOT THE NEXT COLUMN. The narrow
     block above places `.a3-orderrow__link .a3-status` in `grid-column: 2`
     for the LINK grid, and that selector reaches this badge too now that the
     meta is a grid of its own -- so the price and the stock sat side by side
     and only the state badge dropped. Found by rendering it at 390px. */
  .a3-producttable .a3-orderrow__meta > .a3-status { grid-column: 1; }

  /* The price is the fact the sheet makes heaviest on this half. */
  .a3-producttable .a3-orderrow__total {
    font-size: var(--a3-text-md);
    font-weight: var(--a3-weight-semibold);
    color: var(--a3-text);
  }
}

@media (min-width: 60rem) {
  .a3-producttable__head {
    display: grid;
    grid-template-columns: 2.5rem minmax(0, 1fr) 7rem 11rem 9rem 7rem 1.25rem;
    gap: var(--a3-space-2) var(--a3-space-3);
    align-items: center;
    padding: var(--a3-space-2) var(--a3-space-4);
  }
  /* Product covers the picture and the name, which are one thing. */
  .a3-producttable__head .a3-producttable__heading:first-child { grid-column: 1 / 3; }
  .a3-producttable__heading {
    font-size: var(--a3-text-sm);
    font-weight: var(--a3-weight-medium);
    color: var(--a3-text-muted);
  }

  /* THE STATUS COLUMN IS SIZED BY ITS LABEL, not by a guess.

     MEASURED ON PREVIEW, signed in, at 1440px: it was a fixed `9rem` = 144px,
     and `STATE_LABEL.draft` is "Draft, not for sale yet", which is wider. The
     badge ran under the `Updated` column and clipped the date: "17 Sept 2026"
     read "7 Sept 2026", with the leading digit behind the badge. A date that
     is wrong by ten days and looks deliberate.

     Asiam3 does not use the one-word states the sheet draws -- Published,
     Draft, Withdrawn -- because a merchant reading Draft is not told it is not
     for sale. Longer words are the decision; a column that cannot hold them
     was the defect. `auto` takes what the content needs and the name column
     is `minmax(0, 1fr)`, so it gives the width back.
     --------------------------------------------------------------------- */
  .a3-producttable .a3-orderrow__link {
    grid-template-columns: 2.5rem minmax(0, 1fr) 7rem 11rem auto 7rem 1.25rem;
  }

  .a3-producttable .a3-orderrow__mark { grid-column: 1; }
  .a3-producttable .a3-orderrow__title { grid-column: 2; }
  .a3-producttable .a3-orderrow__total { grid-column: 3; justify-self: start; }
  .a3-producttable .a3-orderrow__meta > .a3-status { grid-column: 4; justify-self: start; }
  .a3-producttable .a3-orderrow__link > .a3-status { grid-column: 5; }
  .a3-producttable .a3-orderrow__date { display: block; grid-column: 6; justify-self: start; }
  .a3-producttable .a3-orderrow__chev { grid-column: 7; }

  /* The currency moves to the heading, which is where the sheet puts it. The
     cell keeps its own markup and loses one span, so a phone still reads
     `GH₵ 540` on the row and the column reads `540` under `Price (GHS)`. */
  .a3-producttable .a3-orderrow__total .a3-money__currency { display: none; }
}

/* -----------------------------------------------------------------------------
   THE MERCHANT ORDER PAGE ON A PHONE. Sheet `06-merchant-3`, third panel.

   The sheet draws each item as ONE row in three parts: the photograph, then the
   name with `Qty 1` beneath it, then the price on the right. Asiam3 drew three
   stacked labelled rows -- the name, then "Quantity 1", then "Total GH 420" --
   and repeated that figure in the table foot, so a one-item order showed its
   price twice and the word Total twice.

   THE THUMBNAIL GETS ITS OWN COLUMN, which is what makes the quantity sit under
   the NAME rather than under the picture. The first attempt left the picture
   inside the heading cell and the quantity started at the row edge, under the
   photograph. Seen in a render at 390px, not in the numbers: the measurements
   said the cell had moved and said nothing about what it had moved under.

   SCOPED TO THE MERCHANT SURFACE, AND THE SCOPE IS A HOLDING POSITION rather
   than a boundary. `renderOrder` is SHARED with the customer order page, whose
   own sheet is `04-orders-b` and was not opened in the turn that wrote this.
   Article 22: implementation does not begin so that the governing decision can
   be discovered by colliding with it. Reshaping a shared component to one
   sheet while the other is unread is exactly that, so the rules wait here
   until somebody reads the other sheet and decides whether the same row shape
   is right for a customer.

   NOT ARTICLE 18, which an earlier draft of this comment cited. That article
   governs a surface acquiring its own copy of a BUSINESS RULE or a shared
   concept, and its first line is that business rules live in the platform and
   surfaces present them. EF section 2: "every surface is presentation", and
   "one capability, adapted presentation" positively expects two surfaces to
   draw the same data differently. A row shape is not an Article 18 hazard, and
   the wrong citation is worse than none because the next reader trusts it.

   BELOW 34REM IS WHERE `a3-table--stacks` ALREADY TAKES OVER, so this reshapes
   what that modifier produces rather than adding a second phone layout.

   AND IT REACHES ONLY A ROW HOLDING AN ORDER ITEM, which is a narrowing made
   on 18 September, the day the merchant surface acquired its SECOND stacked
   table. As written, `tbody tr` claimed every stacked table on the surface:
   the dashboard's recent orders would have become a three-column item grid,
   its `Total` cell pinned to column 3 across two rows with its label removed,
   and its order reference dissolved by `th[scope="row"] { display: contents }`.
   Nothing was broken when it shipped -- `renderOrder` held the only stacked
   table a merchant could reach -- and it would have broken the next one
   silently. These rules describe an ITEM ROW: a photograph, a name, a
   quantity, a price. `:has(.a3-orderitem)` is them saying so.
   --------------------------------------------------------------------------- */
@media (max-width: 34rem) {
  [data-a3-surface="merchant"] .a3-table--stacks tbody tr:has(.a3-orderitem) {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    column-gap: var(--a3-space-3);
    row-gap: 2px;
  }

  /* Two levels of `display: contents` so the photograph and the name become
     cells of the row instead of one cell containing both. */
  [data-a3-surface="merchant"] .a3-table--stacks tbody th[scope="row"]:has(.a3-orderitem),
  [data-a3-surface="merchant"] .a3-table--stacks tbody .a3-orderitem { display: contents; }

  [data-a3-surface="merchant"] .a3-table--stacks tbody .a3-orderitem__shot {
    grid-column: 1;
    grid-row: 1 / span 2;
  }
  [data-a3-surface="merchant"] .a3-table--stacks tbody .a3-orderitem__name {
    grid-column: 2;
    grid-row: 1;
    font-weight: var(--a3-weight-medium);
  }

  /* Under the name, and labelled `Qty` as the sheet writes it. The markup says
     Quantity because the wide table needs a full column heading; only the
     phone shortens it, and only here. */
  [data-a3-surface="merchant"] .a3-table--stacks tbody tr:has(.a3-orderitem) td[data-label="Quantity"] {
    grid-column: 2;
    grid-row: 2;
    justify-content: start;
    gap: var(--a3-space-1);
    padding: 0;
    font-size: var(--a3-text-sm);
    color: var(--a3-text-muted);
  }
  [data-a3-surface="merchant"] .a3-table--stacks tbody tr:has(.a3-orderitem) td[data-label="Quantity"]::before {
    content: "Qty";
  }

  /* The price once, on the right, across both lines. The sheet sets it in the
     row rather than as a labelled line, so the label goes. */
  [data-a3-surface="merchant"] .a3-table--stacks tbody tr:has(.a3-orderitem) td[data-label="Total"] {
    grid-column: 3;
    grid-row: 1 / span 2;
    justify-content: flex-end;
    padding: 0;
    font-weight: var(--a3-weight-semibold);
    font-variant-numeric: tabular-nums;
  }
  [data-a3-surface="merchant"] .a3-table--stacks tbody tr:has(.a3-orderitem) td[data-label="Total"]::before {
    content: none;
  }

  /* THE ACTION IS FULL WIDTH, which both halves of the sheet draw and which is
     the ordinary shape for the one thing a merchant came here to do. `G37`:
     green is the action colour and the signal is the filled shape. */
  [data-a3-surface="merchant"] #main form > .a3-btn--primary {
    width: 100%;
    justify-content: center;
  }
}

/* -----------------------------------------------------------------------------
   THE SHOP HERO ON A PHONE. Sheet `06-merchant-3`, fifth panel.

   MEASURED ON PREVIEW AT 390px BEFORE THIS EXISTED: the hero body is a grid of
   `auto 1fr auto`, and at that width the tracks resolve to 72px, 83px and
   153px. The state badge is the third `auto` and takes what it needs, so the
   SHOP NAME GETS 83 PIXELS and wraps to three lines, the summary to six, and
   the shop own public address breaks mid-word. At 1440px the same column is
   869px and the name is one line, which is why nobody looking at a desktop
   would see it.

   The sheet does not draw the name beside the logo at all on this half: the
   cover and the logo are the header, and the name is a row in the list below.
   So the badge keeps its place beside the mark and the naming takes the whole
   width underneath, which is the same reading order in one column.

   MERCHANT-ONLY BY CONSTRUCTION. `a3-shopcover` appears in `shopRoutes.ts` and
   nowhere else, so this needs no surface scope -- checked rather than assumed,
   after a shared component was nearly reshaped from one sheet earlier today.
   --------------------------------------------------------------------------- */
@media (max-width: 34rem) {
  .a3-shopcover__body { grid-template-columns: auto minmax(0, 1fr); }

  /* Row two, both columns: the name, the line and the address get the width
     the page has rather than what is left after a badge. */
  .a3-shopcover__naming { grid-column: 1 / -1; }
}

/* -----------------------------------------------------------------------------
   THE MERCHANT DASHBOARD. Sheet `06-merchant-3`, first panel of its desktop
   half.

   FOUR COUNTS, THEN THE TOTALS, THEN THE RECENT ORDERS. The sheet draws the
   counts four across and the totals three across; Asiam3 builds two of those
   three, because the third is a SHOP rating band and this page belongs to a
   business that may hold several shops. `dashboard.ts` carries that refusal
   in full.

   TWO COLUMNS BELOW 60REM AND FOUR ABOVE IT, from one set of tags. The phone
   half of the sheet draws the same figures as a row of three chips and a
   summary card; four cards two-up is that information in the shape this
   surface already uses everywhere else, and it means there is no second
   markup for the second width. `ENGINEERING_FOUNDATION.md` section 2 asks for
   one capability with its presentation adapted, and invariant 23 wants the
   adaptation to be presentation rather than a different page.

   `minmax(0, 1fr)` rather than `1fr`, because a grid track defaults to
   `min-content` and a long money string would push the row wider than the
   column it sits in. The same reason `a3-orderlayout` writes it that way.
   --------------------------------------------------------------------------- */
.a3-figures {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--a3-space-4);
  margin: 0 0 var(--a3-space-5);
  padding: 0;
  list-style: none;
}
.a3-figure {
  background: var(--a3-bg-raised);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-lg);
  padding: var(--a3-space-4);
  text-align: center;
}
.a3-figure__value {
  margin: 0;
  font-size: var(--a3-text-2xl);
  font-weight: var(--a3-weight-semibold);
  line-height: var(--a3-leading-tight);
  /* Money and counts sit one above the other down a column of cards. Lining
     figures keep the digits in the same places, which is the whole reason
     `tokens.css` argues for a numeric face on amounts. */
  font-variant-numeric: tabular-nums;
}
.a3-figure__label {
  margin: var(--a3-space-1) 0 0;
  color: var(--a3-text-secondary);
  font-size: var(--a3-text-sm);
}

/* The totals row stacks on a phone: a mark, a label and an amount side by side
   in half a phone's width cut "GH₵ 1,650" to "GH₵ 1,65" (the Founder's
   walk, 1 October 2026). Two across from 60rem, below. */
.a3-figures--totals { grid-template-columns: minmax(0, 1fr); }

/* The totals row: the sheet sets a round mark beside the figure rather than
   centring it, which is what separates a total from a count at a glance. */
.a3-figure--wide {
  display: flex;
  align-items: center;
  gap: var(--a3-space-4);
  text-align: left;
}
.a3-figure__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 999px;
  background: var(--a3-primary-100);
  color: var(--a3-primary-700);
}
.a3-figure__body { min-width: 0; }
.a3-figure--wide .a3-figure__value,
.a3-figure--wide .a3-figure__label { display: block; }
.a3-figure--wide .a3-figure__value { font-size: var(--a3-text-xl); }

/* Not a figure at all: the sentence that stands where a total would be when
   the orders are in more than one currency. It must not wear the size and
   weight of an amount, because it is not one. */
.a3-figure__absent {
  font-size: var(--a3-text-md);
  font-weight: var(--a3-weight-regular);
  color: var(--a3-text-secondary);
}

/* The chevron the sheet puts at the end of every row. It is a SECOND way into
   the order the reference already links to, so a phone drops it rather than
   giving it a line of its own in the stacked card. */
.a3-recenttable__chev { width: 1px; text-align: right; }
/* `targetSize.test.ts` refused this as a bare `inline-flex`: a chevron is a
   20px line box, and `min-height` on an inline element is ignored entirely,
   which is the failure that "ships looking correct in the stylesheet and
   measures unchanged in a browser". */
.a3-recenttable__chev a {
  display: inline-flex;
  align-items: center;
  min-height: var(--a3-target-inline-min);
  color: var(--a3-text-muted);
}

@media (min-width: 60rem) {
  .a3-figures { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* Two, not four: the sheet draws three and the third is refused. Four would
     leave two cards floating at half the row. */
  .a3-figures--totals { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 34rem) {
  /* Two classes and an element, so it outranks `.a3-table--stacks tbody td`,
     which turns every cell into a block. */
  .a3-recenttable tbody .a3-recenttable__chev { display: none; }

  /* MEASURED AT 390px BEFORE THIS EXISTED: every cell in the stacked card was
     44px tall holding 17px of text, so one order took 176px and read as four
     lines separated by gaps. `--a3-density-row` is a fixed `height` on
     `.a3-table th, .a3-table td`, and it is there to make COLUMNS line up
     across rows. A stacked card has no columns, so the height has nothing to
     do and the space is not padding anybody chose.

     Scoped to this table rather than to `.a3-table--stacks`, which would
     reshape the customer checkout and the customer order page. Their sheets
     are `04-orders-a` and `-b` and neither was opened here; Article 22 says
     implementation does not begin so the governing decision can be discovered
     by colliding with it. The same holding position `#206` took, and the
     general defect is recorded in the completion map. */
  .a3-recenttable tbody th,
  .a3-recenttable tbody td { height: auto; }

  /* The reference is the row own way in, so it CARRIES the 44px target rather
     than sitting at the top of a cell that had one. `ACCESSIBILITY.md`
     section 5 asks for a target "adequate for touch"; `.a3-shopitem__name` is
     the same shape for the same reason. */
  .a3-recenttable tbody th a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

/* -----------------------------------------------------------------------------
   THE MERCHANT DASHBOARD, REBUILT TO THE 26 SEPTEMBER SHEETS (G99).
   design/context/explorations/2026-09-26-gpt-dashboard/, sheets 01 to 07.

   FOUR STAGE TILES, TINTED. Each wears its status pill's own colour family
   (`orderStatusVariant`), so a tile and the pills in the table below it cannot
   disagree about one set of orders. Four across at every width, as the sheets
   draw them: on a phone the mark sits above the number, on a laptop beside it.

   THE TOTALS carry a line each ("This is what you sold."), the label above the
   figure as drawn.

   THE TWO PANELS -- recent orders and the business's shops -- sit side by side
   from 60rem and one above the other below it.
   --------------------------------------------------------------------------- */
.a3-pagehead--dash { margin-block-end: var(--a3-space-4); }

/* The renamed shops' email as it will be sent (G96, administration): plain
   text, so it is shown as plain text, wrapped, in the reading face. */
.a3-emailpreview {
  margin: 0;
  padding: var(--a3-space-4);
  border-radius: var(--a3-radius-md);
  background: var(--a3-bg-sunken);
  font-family: inherit;
  font-size: var(--a3-text-sm);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.a3-figures--stages { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--a3-space-2); }
.a3-figure--stage {
  display: grid;
  gap: var(--a3-space-1);
  justify-items: start;
  text-align: left;
  border: 0;
  padding: var(--a3-space-3);
}
.a3-figure--stage .a3-figure__mark { width: auto; height: auto; background: none; border-radius: 0; }
.a3-figure--stage .a3-figure__mark .a3-icon { width: 1.5rem; height: 1.5rem; }
.a3-figure--stage .a3-figure__body { display: grid; }
.a3-figure--stage .a3-figure__value { font-size: var(--a3-text-xl); }
.a3-figure--stage .a3-figure__label { margin: 0; color: inherit; }
.a3-figure--settled { background: var(--a3-settled-100); color: var(--a3-settled-600); }
.a3-figure--pending { background: var(--a3-pending-100); color: var(--a3-pending-600); }
.a3-figure--success { background: var(--a3-success-100); color: var(--a3-success-600); }
.a3-figure--neutral { background: var(--a3-bg-sunken); color: var(--a3-text); }
.a3-figure__note { display: block; margin-block-start: var(--a3-space-1); color: var(--a3-text-secondary); font-size: var(--a3-text-sm); }
.a3-figure--items .a3-figure__mark { background: var(--a3-verified-100); color: var(--a3-verified-600); }

.a3-dashgrid { display: grid; gap: var(--a3-space-4); }
.a3-dashpanel { padding: var(--a3-space-4); min-width: 0; }
.a3-dashpanel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--a3-space-3);
  padding-block-end: var(--a3-space-3);
  border-block-end: 1px solid var(--a3-border);
  margin-block-end: var(--a3-space-2);
}
.a3-dashpanel__head .a3-subheading { margin: 0; }
.a3-dashpanel__more {
  display: inline-flex;
  align-items: center;
  gap: var(--a3-space-1);
  min-height: var(--a3-target-inline-min);
  white-space: nowrap;
}
.a3-dashpanel__more .a3-icon { width: 1rem; height: 1rem; }
.a3-dashpanel__none { margin: var(--a3-space-3) 0; color: var(--a3-text-secondary); }
.a3-empty--quiet { border: 0; padding: var(--a3-space-6) var(--a3-space-3); }
.a3-empty__mark { display: inline-grid; place-items: center; width: 3.5rem; height: 3.5rem; margin-block-end: var(--a3-space-3); border-radius: 999px; background: var(--a3-primary-100); color: var(--a3-primary-600); }
.a3-empty__mark .a3-icon { width: 1.75rem; height: 1.75rem; }

/* A photograph of what was bought, and the shop's name under the count --
   both drawn on every recent-order row (sheets 01, 02). */
.a3-recenttable__ref { display: inline-flex; align-items: center; gap: var(--a3-space-3); min-height: var(--a3-target-inline-min); }
.a3-recenttable__shot { flex: none; width: 3rem; height: 3rem; border-radius: var(--a3-radius-md); object-fit: cover; }
.a3-recenttable__shot--glyph { display: inline-grid; place-items: center; background: var(--a3-bg-sunken); color: var(--a3-text-muted); }
.a3-recenttable__items, .a3-recenttable__shop { display: block; }
.a3-recenttable__shop { color: var(--a3-text-secondary); font-size: var(--a3-text-sm); }

.a3-dashshops { display: grid; gap: var(--a3-space-2); margin: 0; padding: 0; list-style: none; }
.a3-dashshop {
  display: flex;
  align-items: center;
  gap: var(--a3-space-3);
  min-height: var(--a3-target-inline-min);
  padding: var(--a3-space-2);
  border: 1px solid var(--a3-border);
  border-radius: var(--a3-radius-md);
  color: inherit;
  text-decoration: none;
}
.a3-dashshop__mark { flex: none; width: 4rem; height: 4rem; border-radius: var(--a3-radius-md); object-fit: cover; }
.a3-dashshop__mark--glyph { display: inline-grid; place-items: center; background: var(--a3-primary-100); color: var(--a3-primary-600); }
.a3-dashshop__body { display: grid; gap: 2px; min-width: 0; flex: 1; }
.a3-dashshop__name { font-weight: var(--a3-weight-semibold); overflow-wrap: anywhere; }
.a3-dashshop__summary { color: var(--a3-text-secondary); font-size: var(--a3-text-sm); }
.a3-dashshop__count { color: var(--a3-text-secondary); font-size: var(--a3-text-sm); }
.a3-dashshop__chev { flex: none; color: var(--a3-text-muted); }

/* The way out when nothing has been ordered yet (sheet 03). */
.a3-dashshare {
  display: flex;
  align-items: center;
  gap: var(--a3-space-3);
  margin-block-start: var(--a3-space-3);
  padding: var(--a3-space-3);
  border-radius: var(--a3-radius-md);
  background: var(--a3-primary-100);
}
.a3-dashshare__mark { display: inline-grid; place-items: center; flex: none; width: 2.75rem; height: 2.75rem; border-radius: 999px; background: var(--a3-bg-raised); color: var(--a3-primary-600); }
.a3-dashshare__text { margin: 0; display: grid; gap: 2px; font-size: var(--a3-text-sm); }
.a3-dashshare__text span { color: var(--a3-text-secondary); }
.a3-dashshare__act { margin: var(--a3-space-3) 0 0; }
.a3-btn--block { display: flex; width: 100%; justify-content: center; }

/* THE ADDRESS BANNER, sheets 04, 05 and 07: each new address a card that opens
   the shop, a filled button, a rule, then the closing and the sign-off. On a
   laptop with one shop, the closing moves to a column of its own (sheet 07). */
.a3-addressnotice__mark { display: inline-grid; place-items: center; width: 2.75rem; height: 2.75rem; border-radius: 999px; background: var(--a3-bg-raised); }
.a3-addressnotice__title { font-size: var(--a3-text-lg); }
/* Room for the close control beside the label and the title only: the phone
   screenshot of 26 September showed the whole card squeezed into a column to
   clear a 44px corner, where sheet 05 uses the full width below the title. */
.a3-addressnotice.a3-notice--closable { padding-inline-end: var(--a3-space-4); }
.a3-addressnotice .a3-notice__kind, .a3-addressnotice__title { margin-inline-end: var(--a3-touch-min); }
.a3-pagehead--dash .a3-heading { font-size: var(--a3-text-2xl); }
.a3-addressnotice__list { display: grid; gap: var(--a3-space-2); margin: var(--a3-space-3) 0; padding: 0; list-style: none; }
.a3-addresscard {
  display: flex;
  align-items: center;
  gap: var(--a3-space-3);
  min-height: var(--a3-touch-min);
  padding: var(--a3-space-2) var(--a3-space-3);
  border-radius: var(--a3-radius-md);
  background: var(--a3-bg-raised);
  color: inherit;
  text-decoration: none;
}
.a3-addresscard__mark, .a3-addresscard__open { flex: none; color: var(--a3-primary-600); }
.a3-addresscard__body { display: grid; flex: 1; min-width: 0; }
.a3-addresscard__name { font-size: var(--a3-text-sm); font-weight: var(--a3-weight-semibold); color: var(--a3-text); }
.a3-addresscard__address { font-weight: var(--a3-weight-semibold); color: var(--a3-primary-700); overflow-wrap: anywhere; }
.a3-addressnotice__act { margin: 0 0 var(--a3-space-3); }
.a3-addressnotice__act .a3-btn { display: flex; justify-content: center; width: 100%; }
.a3-addressnotice__closing { padding-block-start: var(--a3-space-3); border-block-start: 1px solid var(--a3-border); }

/* THE MERCHANT'S PHONE: the five places in the bottom bar, the extras in the
   menu (G99). Below the rail's breakpoint the menu hides what the bar already
   carries, so no destination is offered twice; at 60rem and above the bar is
   gone and the rail shows everything. The unread count rides the bar's bell,
   as sheet 01 draws it. */
@media (max-width: 59.99rem) {
  .a3-menu .a3-nav__link--wide { display: none; }
}
.a3-tabbar__link { position: relative; }
.a3-tabbar__link > .a3-nav__count {
  position: absolute;
  inset-block-start: 2px;
  inset-inline-start: calc(50% + 0.35rem);
  margin: 0;
}

@media (max-width: 34rem) {
  /* Four tiles in 390px: the mark above the number, and the label allowed to
     wrap, as sheet 01 draws them. */
  .a3-figures--stages .a3-figure__label { font-size: var(--a3-text-xs); }
}

@media (min-width: 60rem) {
  .a3-figure--stage { grid-template-columns: auto 1fr; align-items: center; gap: var(--a3-space-3); padding: var(--a3-space-4); }
  .a3-dashgrid { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); align-items: start; }
  .a3-addressnotice--many .a3-addressnotice__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .a3-addressnotice:not(.a3-addressnotice--many) .a3-addressnotice__body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(12rem, 18rem);
    column-gap: var(--a3-space-5);
  }
  .a3-addressnotice:not(.a3-addressnotice--many) .a3-addressnotice__closing {
    grid-column: 2;
    grid-row: 1 / span 5;
    border-block-start: 0;
    border-inline-start: 1px solid var(--a3-border);
    padding: 0 0 0 var(--a3-space-5);
  }
  .a3-addressnotice__act .a3-btn { width: auto; min-width: 16rem; }
}

/* -----------------------------------------------------------------------------
   THE PRODUCTS PAGE HEAD AND ITS SEARCH. Sheet `07-products`, both halves.

   ADD PRODUCT IS DRAWN TWICE, IN TWO PLACES, and the sheet means both: top
   right beside the heading on the desktop half, full width at the BOTTOM after
   the rows on the phone half. They are different controls in different places,
   not one control that moves, so the page carries both and `display: none`
   shows one at each width. `display: none` removes an element from the
   accessibility tree as well as the layout, so there is exactly one reachable
   Add product in the page at any size -- which is the whole reason this is not
   done with `visibility` or an off-screen class.

   THE SEARCH BOX IS ON BOTH HALVES and Asiam3 had none. It is a GET form with
   no script, like the tabs beside it, so it works for the merchant whose phone
   dropped the enhancement bundle.
   --------------------------------------------------------------------------- */
.a3-pagetop { margin-bottom: var(--a3-space-4); }

/* The bottom one, on a phone. The sheet draws it full width there. */
.a3-pagetop__action { display: none; }

@media (min-width: 60rem) {
  /* The top one, at a desk, where the sheet draws it beside the heading. */
  .a3-pagetop__action { display: inline-flex; }
  .a3-producttable__add { display: none; }
}

/* The category dropdown, inside the search form so ONE BUTTON APPLIES BOTH.
   Sheet `07-products` sets it beside the box at desktop width; on a phone the
   row wraps and it takes a line of its own, which is what `.a3-row` already
   does with everything in it.

   It does not grow: a select is as wide as its longest option needs and no
   wider, and the search box beside it is the field a merchant is typing into.

   The order search's status and dates (`G119`) take the same shape. */
.a3-categoryfilter,
.a3-searchfilter {
  display: grid;
  gap: var(--a3-space-1);
  flex: 0 1 14rem;
  min-width: 0;
}

/* VISIBLE, which is both what the sheet draws and what this repository asks
   for everywhere except a search box: `crossDevice.test.ts` permits a hidden
   name on a field that announces itself by its own semantics, and a select
   announces a list rather than what the list is about. */
.a3-categoryfilter__label,
.a3-searchfilter__label {
  font-size: var(--a3-text-sm);
  color: var(--a3-text-secondary);
}

/* THE ROW ALIGNS ON ITS BASELINE ONCE ONE FIELD HAS A LABEL ABOVE IT.
   `.a3-row` centres, which is right for a row of equals and wrong the moment
   one item is two lines tall: the select would sit ten pixels below the box
   beside it for no reason a reader could name. */
form[role="search"]:has(.a3-categoryfilter, .a3-searchfilter) { align-items: flex-end; }

/* The orders search folds its status and dates beneath the box and the button
   (`G119`), on a line of their own, laid out like filters beside a box. */
.a3-searchmore { flex: 1 1 100%; }
.a3-searchmore .a3-disclose__body {
  display: flex;
  flex-wrap: wrap;
  gap: var(--a3-space-3);
  align-items: flex-end;
}

/* -----------------------------------------------------------------------------
   THE PRODUCT PAGE AS A WORKSPACE. Sheet `07-products`, desktop half.

   MEASURED SIGNED IN AT 1440px: one column, 2060 pixels tall, with text inputs
   1070 pixels wide. A field that wide is past every measure this design system
   sets for reading, and a merchant changing a price scrolls a page and a half to
   find the control they came for.

   THE SHEET DRAWS THREE ZONES on this page and this follows them: the
   photograph on the left, the fields in the middle, and what the product's
   STATE is on the right with the control that changes it. It is the same
   arrangement `a3-shoplayout` uses two pages over, at the same measurements --
   the handover asks for one measurement for the same idea rather than a second
   opinion per page.

   G68, BOTH HALVES.

   BUILT because the sheet draws it and Asiam3 lacked it: the three zones, and a
   centre column narrow enough to read.

   KEPT because Asiam3 has it and no sheet draws it, each in the right rail
   where a merchant looks for what a product IS rather than what it says:

     - CHANGE THE STOCK as a MOVEMENT, not a number to overwrite. The sheet
       draws "Stock quantity" as a plain field. Asiam3 records add-and-remove
       because the inventory domain holds a ledger, and typing an absolute over
       it would lose the history that answers "where did it go".
     - WHERE THE STOCK WENT, which is that history.
     - PRODUCT DETAILS, the merchant's own table. Article 4: Asiam3 has not
       checked that the pot is aluminium, so the rows are the merchant speaking
       and the sheet has no such thing.

   REFUSED, and it is already recorded rather than decided here: the sheet's
   right-hand zone is a PRODUCT STATUS radio group -- Published, Draft,
   Withdrawn -- and `BATCH_2_COMPLETION_MAP.md` carries the refusal in its own
   words: "wider than the state machine permits, and each transition carries its
   own authorisation. The per-transition buttons `06-merchant-1` draws are taken
   instead." So the zone is built and what sits in it is Asiam3's controls.
   --------------------------------------------------------------------------- */
@media (min-width: 60rem) {
  .a3-productlayout {
    display: grid;
    /* `a3-orderlayout`s measurement exactly, which is what the handover asks
       for: one measurement for the same idea rather than a second opinion per
       page. */
    grid-template-columns: minmax(0, 1fr) 20rem;
    gap: var(--a3-space-5);
    align-items: start;
  }

  /* `.a3-stack` spaces siblings with a margin, and inside a grid that margin
     sits within the cell and adds itself to the gap. */
  .a3-productlayout > * + * { margin-top: 0; }

  /* EVERYTHING SPANS, THEN THE PLACED THINGS TAKE THEIR COLUMNS. Written this
     way round so an element added to this page later lands full width and
     visible rather than silently stacking into column one. */
  .a3-productlayout > * { grid-column: 1 / -1; }

  .a3-productlayout > #section-status { grid-column: 2; grid-row: 4; }
  .a3-productlayout > #section-summary { grid-column: 2; grid-row: 5; }
  .a3-productlayout > #section-edit { grid-column: 1; grid-row: 4 / span 5; }
  .a3-productlayout > #section-stock { grid-column: 2; grid-row: 6; }
  .a3-productlayout > :has(#section-photos),
  .a3-productlayout > #section-photos { grid-column: 2; grid-row: 7; }
  .a3-productlayout > #section-movements { grid-column: 2; grid-row: 8; }

  /* THE FIELDS STOP BEING A BANNER. Even inside the centre column a bare input
     stretches to whatever it is given, and the sheet sets its fields at about
     this width. `Product details` keeps its two columns, which is what that
     table is for. */
  .a3-productlayout > #section-edit .a3-input { max-width: 34rem; }
}

/* -----------------------------------------------------------------------------
   The phone menu, rebuilt -- 25 September 2026
   The Founder, on the open menu: "Very bad", then: "you have to tap on it to
   close it. A user should tap the screen and see the menu close", and "you
   can't even scroll the items on the menu especially when you sign in".

   Four things, each answering one of those:
     - THE SCREEN CLOSES IT. The toggle's own ::before becomes a dimmed layer
       over the whole page while the menu is open, so a tap anywhere outside the
       panel is a tap on the toggle, and a `details` closes itself. No script:
       the market ships none, and this works on a phone that runs none.
     - IT SCROLLS. The panel hangs from a sticky band, so a long signed-in list
       ran under the bottom bar where nothing could reach it. It now has a
       ceiling and scrolls inside itself, without dragging the page behind.
     - IT IS A LIST. Full width, one row per destination at the touch floor,
       where you are shown as a filled row rather than the laptop's underline.
     - THE WAYS IN ARE IN IT. See `menuActs` in `pageShell.ts`.
----------------------------------------------------------------------------- */
.a3-nav__acts { display: none; }

@media (max-width: 59.99rem) {
  .a3-authority--market .a3-menu[open] > summary::before {
    content: "";
    position: fixed;
    inset: 0;
    background: var(--a3-scrim-soft);
    z-index: calc(var(--a3-z-sticky) - 1);
    cursor: default;
  }

  .a3-authority--market .a3-menu[open] > .a3-nav {
    /* PINNED ABOVE THE BOTTOM BAR, not hung from the band. Hung from the band,
       its top sat as low as the notice above it pushed it, so a signed-in list
       ran off the foot of the screen and "Sign out" could not be reached by
       any scroll. Founder, 26 September 2026: "I want to scroll to sign out".
       Fixed to the bottom, the whole panel is always on screen and scrolls
       inside itself; 64px is the bar, plus the phone's gesture strip. */
    position: fixed;
    top: auto;
    bottom: calc(4rem + env(safe-area-inset-bottom, 0px) + var(--a3-space-2));
    inset-inline: var(--a3-space-2);
    max-width: none;
    max-height: calc(100vh - 4rem - env(safe-area-inset-bottom, 0px) - 6rem);
    max-height: calc(100dvh - 4rem - env(safe-area-inset-bottom, 0px) - 6rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    gap: 0;
    padding: var(--a3-space-3);
  }

  .a3-authority--market .a3-menu[open] > .a3-nav .a3-nav__form { display: flex; }
  .a3-authority--market .a3-menu[open] > .a3-nav .a3-nav__link {
    flex: 1 1 auto;
    gap: var(--a3-space-3);
    padding: var(--a3-space-2) var(--a3-space-3);
    border-bottom-color: transparent;
    border-radius: var(--a3-radius-sm);
    font-size: var(--a3-text-md);
    text-align: start;
  }
  .a3-authority--market .a3-menu[open] > .a3-nav .a3-nav__link[aria-current="page"] {
    box-shadow: none;
    border-bottom-color: transparent;
    background: var(--a3-bg-sunken);
    color: var(--a3-identity);
  }
}

@media (max-width: 47.99rem) {
  .a3-authority--market .a3-nav__acts {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--a3-space-2);
    padding-bottom: var(--a3-space-3);
    margin-bottom: var(--a3-space-2);
    border-bottom: 1px solid var(--a3-border);
  }
  .a3-authority--market .a3-nav__acts > :last-child:nth-child(odd) { grid-column: 1 / -1; }
  /* Sign in and Start selling in the link blue, text and outline alike: the
     Founder, 26 September 2026, "Start selling and sign in should be in blue".
     Register keeps the filled green: it is the one primary action here. */
  .a3-authority--market .a3-nav__acts .a3-btn--secondary { color: var(--a3-link); border-color: var(--a3-link); }
}

/* The same menu on the merchant and administration surfaces, on a phone.
   It opened as the laptop's tab row wrapped onto two lines, with the tab
   underline, pushing the page down, and only its own button closed it. Now it
   opens over the page as a list, closes on a tap anywhere outside, and scrolls
   inside itself, as the market's does above. */
@media (max-width: 47.99rem) {
  body.a3:not([data-a3-surface="customer"]) .a3-menu { position: relative; }

  body.a3:not([data-a3-surface="customer"]) .a3-menu[open] > summary::before {
    content: "";
    position: fixed;
    inset: 0;
    background: var(--a3-scrim-soft);
    z-index: calc(var(--a3-z-sticky) - 1);
    cursor: default;
  }

  body.a3:not([data-a3-surface="customer"]) .a3-menu[open] > .a3-nav {
    position: absolute;
    top: 100%;
    inset-inline: var(--a3-space-2);
    z-index: var(--a3-z-sticky);
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 0;
    padding: var(--a3-space-3);
    background: var(--a3-bg-raised);
    border: 1px solid var(--a3-border);
    border-radius: var(--a3-radius-md);
    box-shadow: var(--a3-shadow-raised, none);
    max-height: min(36rem, 65vh);
    max-height: min(36rem, 65dvh);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  body.a3:not([data-a3-surface="customer"]) .a3-menu[open] > .a3-nav .a3-nav__form { display: flex; }
  body.a3:not([data-a3-surface="customer"]) .a3-menu[open] > .a3-nav .a3-nav__link {
    flex: 1 1 auto;
    gap: var(--a3-space-3);
    padding: var(--a3-space-2) var(--a3-space-3);
    border-bottom-color: transparent;
    border-radius: var(--a3-radius-sm);
    font-size: var(--a3-text-md);
    text-align: start;
  }
  body.a3:not([data-a3-surface="customer"]) .a3-menu[open] > .a3-nav .a3-nav__link[aria-current="page"] {
    box-shadow: none;
    border-bottom-color: transparent;
    background: var(--a3-bg-sunken);
    color: var(--a3-identity);
  }
}

/* -----------------------------------------------------------------------------
   THE MARKET HOME HERO, TO THE FOUNDER'S OWN PICTURES -- G92, 26 September 2026
   "I want Asiam3 homepage to have an improvement exactly as this. Exactly as
   this." Two sheets govern: design/context/explorations/2026-09-26-founder/
   01-market-home.webp (laptop) and 02-market-home-phone.webp (phone).

   It replaces the green box before it and is written after it, so it wins at
   equal weight; the rules above stay until their attacks are retired.
   ----------------------------------------------------------------------------- */
body.a3:has(.a3-market-hero) { overflow-x: clip; }

.a3-market-hero {
  /* Edge to edge: the ground runs the width of the window, the words keep the
     page's column. */
  margin-inline: calc(50% - 50vw);
  margin-block-start: calc(-1 * var(--a3-page-gutter));
  /* One inset, used by the words and undone by the photograph where it bleeds. */
  --a3-hero-inset: max(var(--a3-space-6), calc(50vw - 600px + var(--a3-page-gutter)));
  padding: var(--a3-space-6) var(--a3-hero-inset) var(--a3-space-7);
  border-radius: 0;
  overflow: visible;
  color: var(--a3-text);
  background:
    radial-gradient(55% 70% at 0% 15%, color-mix(in srgb, var(--a3-primary-100) 90%, transparent), transparent 70%),
    radial-gradient(40% 50% at 80% 100%, color-mix(in srgb, var(--a3-rating-mark) 10%, transparent), transparent 70%),
    linear-gradient(180deg, color-mix(in srgb, var(--a3-primary-100) 55%, var(--a3-bg)) 0%, var(--a3-bg) 100%);
  /* The wave into the categories. */
  /* currentColor is only the mask's opacity here, never a colour on screen. */
  mask: radial-gradient(140% 3rem at 50% 100%, transparent 96%, currentColor 100%) bottom / 100% 3rem no-repeat,
        linear-gradient(currentColor, currentColor) top / 100% calc(100% - 3rem) no-repeat;
}

.a3-market-hero__title {
  color: var(--a3-text);
  font-size: clamp(2.6rem, 4.3vw, 4rem);
  line-height: 1.02;
  letter-spacing: -0.02em;
  font-weight: 800;
}
.a3-market-hero__turn { color: var(--a3-identity); }
/* The short gold bar under the headline. */
.a3-market-hero__title::after {
  content: "";
  display: block;
  width: 5rem;
  height: 5px;
  border-radius: 3px;
  margin-block-start: var(--a3-space-5);
  margin-block-end: var(--a3-space-2);
  background: var(--a3-rating-mark);
}

.a3-market-hero__text {
  color: var(--a3-text-secondary);
  opacity: 1;
  font-size: var(--a3-text-xl);
  line-height: 1.35;
  max-width: 30rem;
}

.a3-market-hero__search {
  border-radius: var(--a3-radius-lg);
  padding: var(--a3-space-2) var(--a3-space-2) var(--a3-space-2) var(--a3-space-4);
  box-shadow: 0 6px 24px color-mix(in srgb, var(--a3-text) 8%, transparent);
  max-width: 36rem;
}
.a3-market-hero__search .a3-btn { border-radius: var(--a3-radius-md); padding-inline: var(--a3-space-6); }

/* THE PHOTOGRAPH, in the rounded organic frame the sheet draws, a green rim
   behind it and a field of dots at its shoulder. */
.a3-market-hero__art {
  height: 17rem;
  background: none;
  border-radius: 0;
  isolation: isolate;
}
.a3-market-hero__art::after { content: none; }
.a3-market-hero__art::before {
  content: "";
  position: absolute;
  inset: -0.7rem -1rem -1rem -0.3rem;
  z-index: -1;
  border-radius: 34% 66% 58% 42% / 52% 38% 62% 48%;
  background: color-mix(in srgb, var(--a3-identity) 20%, color-mix(in srgb, var(--a3-rating-mark) 15%, var(--a3-primary-100)));
}
.a3-market-hero__photo {
  border-radius: 0 0 46% 54% / 0 0 22% 28%;
  object-position: 55% 35%;
}

/* The bubble: "Local shops. Brighter." on cream, tilted, with the gold stroke
   and three green sparks. Upright type: the sheet draws it hand-lettered, and
   Asiam3 has two faces; the display face carries it (see the markup). */
.a3-market-hero__script {
  inset-block-end: 12%;
  inset-inline-end: var(--a3-space-3);
  max-width: none;
  text-align: start;
  font-style: normal;
  font-weight: 800;
  font-size: var(--a3-text-xl);
  line-height: 1.1;
  color: var(--a3-text);
  background: color-mix(in srgb, var(--a3-rating-mark) 10%, var(--a3-bg));
  border: 0;
  border-radius: 58% 42% 55% 45% / 52% 60% 40% 48%;
  padding: var(--a3-space-5) var(--a3-space-6) var(--a3-space-6);
  transform: rotate(-7deg);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--a3-text) 12%, transparent);
}
.a3-market-hero__script::after {
  content: "";
  display: block;
  width: 3.5rem;
  height: 4px;
  border-radius: 2px;
  margin-block-start: var(--a3-space-2);
  background: var(--a3-rating-mark);
}
.a3-market-hero__spark {
  position: absolute;
  width: 2.5rem;
  inset-block-start: -1.6rem;
  inset-inline-start: 1.2rem;
  fill: none;
  stroke: var(--a3-identity);
  stroke-width: 3;
  stroke-linecap: round;
}

@media (min-width: 60rem) {
  .a3-market-hero {
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
    padding-block: var(--a3-space-6) calc(var(--a3-space-6) + 1.5rem);
    gap: var(--a3-space-6);
  }
  .a3-market-hero__art {
    height: 28rem;
    width: auto;
    /* Out towards the window's edge, as the sheet draws it. */
    margin-inline-end: calc(-1 * var(--a3-hero-inset) + 2.5rem);
  }
  .a3-market-hero__photo {
    border-radius: 34% 66% 58% 42% / 52% 38% 62% 48%;
    object-position: 62% 40%;
  }
  /* Dots at the photograph's upper left shoulder. */
  .a3-market-hero__art > picture::before {
    content: "";
    position: absolute;
    inset-block-start: 1rem;
    inset-inline-start: -2rem;
    width: 6rem;
    height: 4.5rem;
    z-index: -1;
    background: radial-gradient(circle, color-mix(in srgb, var(--a3-identity) 25%, transparent) 2px, transparent 2.5px) 0 0 / 14px 14px;
  }
  /* On a laptop the bubble sits on the photograph's left edge, half over it. */
  .a3-market-hero__script {
    inset-block-end: 16%;
    inset-inline-start: -2.5rem;
    inset-inline-end: auto;
    font-size: var(--a3-text-2xl);
  }
}

/* The categories under the wave: larger rounds, as the sheet has them. */
.a3-cat__tile { width: 4rem; height: 4rem; }

.a3-market-hero__art > picture { display: block; position: relative; height: 100%; }
@media (max-width: 59.99rem) {
  /* The phone sheet runs the photograph edge to edge above the words. */
  .a3-market-hero { padding-block-start: 0; }
  .a3-market-hero__art { margin-inline: calc(-1 * var(--a3-hero-inset)); width: auto; height: 19rem; }
  .a3-market-hero__text { font-size: var(--a3-text-lg); }
  /* Edge to edge, so the rim shows above and below only: sideways it would
     widen the page past the screen. */
  .a3-market-hero__art::before { inset-inline: 0; }
}

/* THE PHONE HOME, SHOPS SOONER -- the Founder, 26 September 2026: "it is too
   big that a user have to scroll down before seeing shops", choosing in the
   popup "Hide categories, smaller hero". The category circles leave the phone
   home only: Categories stays one tap away in the bottom bar and on its own
   page, so this is a different arrangement, not a reduced product
   (ENGINEERING_FOUNDATION: "a different arrangement of the same product,
   never a reduced one"). The hero keeps the sheet's parts, smaller. */
@media (max-width: 59.99rem) {
  .a3-market-hero ~ .a3-cats:not(.a3-cats--wrap) { display: none; }
  /* The wave shallower here, and room under the search so it never cuts it. */
  .a3-market-hero {
    padding-block-end: calc(1.5rem + var(--a3-space-3));
    gap: var(--a3-space-3);
    mask: radial-gradient(140% 1.5rem at 50% 100%, transparent 96%, currentColor 100%) bottom / 100% 1.5rem no-repeat,
          linear-gradient(currentColor, currentColor) top / 100% calc(100% - 1.5rem) no-repeat;
  }
  .a3-market-hero__art { height: 12rem; }
  .a3-market-hero__title { font-size: 2rem; }
  .a3-market-hero__title::after { margin-block-start: var(--a3-space-3); margin-block-end: 0; width: 4rem; height: 4px; }
  .a3-market-hero__text { font-size: var(--a3-text-md); }
  .a3-market-hero__script { font-size: var(--a3-text-md); padding: var(--a3-space-3) var(--a3-space-4) var(--a3-space-4); }
}

/* A CATEGORY'S PRODUCTS IN A ROW THAT SLIDES -- G93, 26 September 2026. The
   row scrolls sideways by hand and snaps to a tile; the page never does. */
.a3-slide {
  display: flex;
  gap: var(--a3-space-3);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  padding-block-end: var(--a3-space-2);
  -webkit-overflow-scrolling: touch;
}
/* Three across on a phone (Founder, 26 September 2026: "Can you make it three
   in a row?"); the fourth and on are a swipe away. */
.a3-slide__item { flex: 0 0 calc((100% - 2 * var(--a3-space-3)) / 3); scroll-snap-align: start; }
@media (min-width: 60rem) { .a3-slide__item { flex-basis: 13rem; } }
/* In a tile a third of a phone wide the placeholder's words keep to one line. */
.a3-slide .a3-placeholder__label { letter-spacing: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: calc(100% - 2 * var(--a3-space-2)); }
.a3-catrow__name { color: var(--a3-link); text-decoration: none; display: inline-flex; align-items: center; min-height: var(--a3-target-inline-min); }
.a3-catrow__name:hover { text-decoration: underline; }

/* A category's picture fills most of its circle (G93). */
.a3-cat__tile > picture { display: contents; }
.a3-cat__picture { width: 76%; height: 76%; object-fit: contain; display: block; }

/* -----------------------------------------------------------------------------
   THE LAPTOP MASTHEAD IN TWO ROWS -- G92, the Founder's laptop picture
   (design/context/explorations/2026-09-26-founder/01-market-home.webp): the
   mark with its tagline beside it, then the surface, how the person is signed
   in and the account glyph at the right; beneath a rule, the destinations,
   with the ways in at the right and "Start selling" a pale green button.
   ----------------------------------------------------------------------------- */
@media (min-width: 60rem) {
  .a3-authority--market {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto auto auto;
    grid-template-areas:
      "brand . surface basis basket account"
      "menu menu menu menu acts acts";
    align-items: center;
    column-gap: var(--a3-space-5);
    row-gap: 0;
    padding-block-end: 0;
  }
  /* The rule between the rows, the full width of the band. */
  .a3-authority--market::after {
    content: "";
    grid-row: 2;
    grid-column: 1 / -1;
    align-self: start;
    border-top: 1px solid var(--a3-border);
    margin-inline: calc(-1 * var(--a3-space-4));
  }
  .a3-authority--market .a3-authority__brand {
    grid-area: brand;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--a3-space-5);
    padding-block: var(--a3-space-3);
  }
  .a3-authority--market .a3-authority__tagline { font-size: var(--a3-text-md); flex: none; }
  .a3-authority--market .a3-authority__surface { grid-area: surface; }
  .a3-authority--market .a3-authority__basis {
    grid-area: basis;
    font-size: var(--a3-text-md);
    color: var(--a3-text-secondary);
    padding-inline-start: var(--a3-space-5);
    border-inline-start: 1px solid var(--a3-border);
  }
  .a3-authority--market .a3-authority__control { grid-area: account; margin: 0; }
  .a3-authority--market .a3-authority__basket { grid-area: basket; margin: 0; justify-self: center; }
  .a3-authority--market .a3-menu { grid-area: menu; align-self: stretch; }
  .a3-authority--market .a3-authority__acts { grid-area: acts; justify-self: end; margin: 0; }
  .a3-authority--market .a3-menu .a3-nav { gap: var(--a3-space-6); }
  .a3-authority--market .a3-menu .a3-nav__link { font-size: var(--a3-text-md); gap: var(--a3-space-2); }
  .a3-authority--market .a3-authority__acts .a3-btn--secondary {
    gap: var(--a3-space-2);
    background: var(--a3-primary-100);
    color: var(--a3-primary-700);
    border-color: transparent;
    font-weight: 700;
  }
}

/* THE CATEGORIES PAGE, TIDIED -- the Founder, 26 September 2026, on his phone:
   "the boxes of the products are not aligned which makes it disorganised",
   "Try making the categories 4 in a row", and the category heading "in blue
   colour as the see all. For easy identity of demarcation" (see
   .a3-catrow__name above).

   Every box in a row is the same width and the same height: a long name no
   longer widens its box (min-width: 0), names keep to two lines, and the
   price sits on the bottom edge of every box. */
.a3-slide__item { min-width: 0; display: flex; }
.a3-slide__item > .a3-producttile { flex: 1 1 auto; min-width: 0; }
.a3-slide .a3-producttile__name {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.a3-slide .a3-producttile__price { margin-top: auto; }

/* Four circles to a row on a phone, so the products below show sooner. */
@media (max-width: 47.99rem) {
  .a3-cats--wrap {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: var(--a3-space-2);
  }
  .a3-cats--wrap .a3-cat { width: auto; font-size: var(--a3-text-xs); }
  .a3-cats--wrap .a3-cat__tile { width: 3.5rem; height: 3.5rem; }
}

/* THE BASKET AT THE TOP RIGHT (26 September 2026), with the number of shops
   that have something waiting. At the right end of the band on a phone, beside
   the account glyph on a laptop. */
.a3-authority__basket {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--a3-touch-min);
  min-height: var(--a3-touch-min);
  color: var(--a3-text-secondary);
  /* After the menu, at the right end of its row, as the Founder circled it. */
  order: 101;
  margin-inline-start: auto;
}
.a3-authority__basket .a3-icon { width: 1.5rem; height: 1.5rem; }
.a3-authority__basket-count {
  position: absolute;
  inset-block-start: 4px;
  inset-inline-end: 2px;
  min-width: 1.15rem;
  height: 1.15rem;
  padding-inline: 4px;
  border-radius: 999px;
  display: grid;
  place-items: center;
  /* Red, the Founder, 26 September 2026: "instead of the number in green it
     should be in red. Red calls for attention right?" */
  background: var(--a3-danger-600);
  color: var(--a3-text-inverse);
  font-size: var(--a3-text-2xs);
  font-weight: 700;
  line-height: 1;
}

/* THE PHONE MASTHEAD IN TWO FIXED ROWS, so the basket is where the Founder
   circled it on every phone: the mark (and the surface, when shown) above;
   how the person is signed in, the menu, and the basket at the right below.
   It was a wrapping row, so which line the menu landed on depended on the
   screen's width and text size -- his phone and a 390px test phone differed. */
@media (max-width: 47.99rem) {
  .a3-authority--market {
    display: grid;
    grid-template-columns: auto auto minmax(0, 1fr);
    /* The menu first, so it stays in one place whether the words beside it
       read "Not signed in" or "Signed in" (the Founder, 26 September 2026:
       "it looks weird when the menu shifts there when signed in"). */
    grid-template-areas:
      "brand brand surface"
      "menu basis basket";
    align-items: center;
    column-gap: var(--a3-space-3);
  }
  .a3-authority--market .a3-authority__brand { grid-area: brand; }
  .a3-authority--market .a3-authority__surface { grid-area: surface; justify-self: end; }
  .a3-authority--market .a3-authority__basis { grid-area: basis; margin: 0; }
  .a3-authority--market .a3-menu { grid-area: menu; }
  .a3-authority--market .a3-authority__basket { grid-area: basket; justify-self: end; margin: 0; }
}
