:root { --line:#d8d3c8; --ink:#22201c; --accent:#0b5c37; }
* { box-sizing: border-box; }
body.site { margin:0; padding:0 0 3rem; font:16px/1.45 system-ui,-apple-system,"Segoe UI",sans-serif;
  color:var(--ink); background:#fbfaf7; }
/* the head is pinned (PO 2026-09-10: “把头部…永远固定在每一页的头顶”): the way back to the product
   list, the cart link and its number never scroll out of reach on a long page */
.site-head { position:sticky; top:0; z-index:5; display:flex; align-items:baseline; justify-content:space-between; gap:1rem;
  padding:.75rem 1.25rem; border-bottom:2px solid var(--ink); background:#fbfaf7; }
.site-name { font-weight:600; font-size:1.1rem; color:inherit; text-decoration:none; }
.site-nav { display:flex; gap:.9rem; }
.site-nav a { color:var(--accent); text-decoration:none; }
.site-nav a.on { color:var(--ink); font-weight:600; }
.site-main { padding:1rem 1.25rem 0; max-width:860px; }
.site-h1 { margin:.2rem 0 .4rem; font-size:1.3rem; }
.sr-only { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
  clip:rect(0 0 0 0); white-space:nowrap; border:0; }
.site-search-row { display:flex; gap:.5rem; align-items:stretch; margin:.6rem 0 .4rem; }
.site-search { flex:1 1 auto; display:block; margin:0; }
.site-search-go { flex:none; font:inherit; font-size:.95rem; min-height:2.75rem; padding:.4rem 1.1rem;
  border:1px solid var(--accent); border-radius:.6rem; background:var(--accent); color:#fff; cursor:pointer; }
.site-search input { width:100%; min-height:2.75rem; font-size:1rem; padding:.45rem .7rem;
  border:1px solid var(--line); border-radius:.55rem; background:#fff; }
.site-shelves ul { display:flex; flex-wrap:wrap; gap:.4rem; list-style:none; margin:.4rem 0; padding:0; }
.site-shelves .shelf { font:inherit; font-size:.9rem; padding:.3rem .7rem; border:1px solid var(--line);
  border-radius:1rem; background:#fff; color:var(--ink); cursor:pointer; }
.site-shelves .shelf.current { background:var(--accent); border-color:var(--accent); color:#fff; }
/* Two cards per row (PO, 2026-09-10): "一行显示两张卡片". Fixed two columns — the rule does not depend on
   the screen being wide, because the PO asked for two, not for "two if it fits". */
.cards { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:.9rem; list-style:none;
  margin:1rem 0; padding:0; }
.card { border:1px solid var(--line); border-radius:.7rem; background:#fff; padding:.7rem;
  display:flex; flex-direction:column; gap:.3rem; position:relative; }
/* THE HIDDEN LAW (PO 2026-09-10, and he was right from day one): the .card rule sets display:flex,
   an AUTHOR rule, and author rules beat the browser's built-in [hidden]{display:none} — so a card set
   to hidden kept staring back at the parent. Every filter ran, every attribute flipped, NOTHING
   vanished: a script-level test counts attributes and CANNOT see this; only computed style can.
   hidden is the one mechanism the browse filter, the sold-out fold and the empty-state share, so it
   wins over any class display, everywhere, unconditionally. */
[hidden] { display:none !important; }
.card-photo { width:100%; aspect-ratio:1/1; object-fit:contain; background:#f4f1ea; border-radius:.4rem; }
/* Every member out: grey the cover, badge it, and from piece 15 give it no href (PO point 2: 不能点入).
   The grey is the whole sentence — the badge says it in words for the parent who does not read greys. */
.card-photo-wrap { position:relative; display:block; }
.card.sold .card-photo { filter:grayscale(1); opacity:.55; }
.card-oos { position:absolute; top:.45rem; left:.45rem; background:#a33; color:#fff; font-size:.74rem;
  font-weight:600; padding:.18rem .5rem; border-radius:.35rem; }
.card > .card-oos { position:static; align-self:flex-start; margin-top:.2rem; }
.card-name { font-size:1.02rem; line-height:1.25; }
.card-price { font-weight:700; font-size:1.15rem; color:var(--accent); }
.card-avail { margin:0; font-size:.88rem; color:#3f5c3a; }
.card-avail.sold, .card.sold { color:#a33; }
.card.sold { border-style:dashed; }
.sold-fold { margin:.6rem 0; }
.empty { color:#5a564e; }

/* ---- The cart (piece 16a). A line is: tick, photo, what it is, stepper, line total. The tick first —
   it decides what the order will be (PO ruling 2026-09-10 later). */
.cart-lines { list-style:none; margin:0 0 1rem; padding:0; display:flex; flex-direction:column; gap:.6rem; }
/* two rows on a phone (PO 2026-09-10): the name owns the first row, the controls the second — a name
   must never lose letters to a stepper */
.cart-line { display:grid; align-items:center; column-gap:.6rem; row-gap:.5rem;
  grid-template-columns:88px minmax(0,1fr);
  grid-template-areas:"photo what" "photo ctrl";
  background:#fff; border:1px solid #e4ddd0;
  border-radius:.6rem; padding:.6rem .7rem; }
/* the tick floats on the photo's top-left corner (PO 2026-09-10: 移到左上角，悬浮在图片的上面，
   省一点空间，图片可以稍微大一点). It owns no column any more, and the picture grew 64px → 88px
   with the space saved; the white chip keeps it visible whatever sits under it. */
.cart-photo-wrap { grid-area:photo; position:relative; align-self:center; }
.cart-pick-wrap { position:absolute; top:-.45rem; left:-.45rem; display:flex; line-height:0;
  background:#fff; border:1px solid var(--line); border-radius:.4rem; padding:.14rem;
  box-shadow:0 1px 3px rgba(20,30,20,.14); }
.cart-pick { width:1.2rem; height:1.2rem; accent-color:#14532d; margin:0; cursor:pointer; }
.cart-photo { width:88px; height:88px; object-fit:contain; background:#f4f1ea; border-radius:.4rem; display:block; }
.cart-photo-none { border:1px dashed #ddd5c6; }
.cart-what { grid-area:what; display:flex; flex-direction:column; gap:.15rem; }
/* the controls row: stepper, then the amount pushed right, then the trash at the edge */
.cart-controls { grid-area:ctrl; display:flex; align-items:center; gap:.6rem; }
.cart-name { font-size:.98rem; }
.cart-unit { font-size:.85rem; color:#6b6459; }
.cart-gone { font-size:.82rem; color:#a33; }
.cart-shrunk { font-size:.82rem; color:#8a5a00; }
.cart-pre { font-size:.82rem; color:#14532d; }
.cart-stepper { display:flex; align-items:center; gap:.3rem; }
.cart-stepper .qty-btn { width:2rem; height:2rem; border:1px solid #d5cdbe; border-radius:.4rem; background:#fff;
  font-size:1.05rem; line-height:1; cursor:pointer; }
.cart-qty { width:3.2rem; height:2rem; text-align:center; border:1px solid #d5cdbe; border-radius:.4rem; }
.cart-line-total { margin-left:auto; min-width:4.5rem; text-align:right; font-weight:600; font-size:.95rem; }
/* the trash the PO asked for (2026-09-10 point 4): quiet grey until touched, red once you mean it */
.cart-remove { border:0; background:none; padding:.35rem; margin-left:.1rem; color:#8a8377; cursor:pointer;
  border-radius:.4rem; display:inline-flex; align-items:center; }
/* no :hover promise (PO 2026-09-10: 手机上看不出效果) — the keyboard focus still shows, touch gets the dialog */
.cart-remove:focus-visible { color:#b3261e; background:#fbeeec; }
.cart-details { display:flex; flex-direction:column; gap:.55rem; background:#fff; border:1px solid #e4ddd0;
  border-radius:.6rem; padding:.8rem .9rem; margin:0 0 1rem; }
.cart-details h2 { margin:0 0 .1rem; font-size:1.05rem; }
.cart-details label { display:flex; flex-direction:column; gap:.2rem; font-size:.9rem; }
.cart-details input, .cart-details textarea { padding:.5rem .6rem; border:1px solid #d5cdbe; border-radius:.4rem; font:inherit; }
.cart-total { display:flex; justify-content:space-between; align-items:baseline; font-size:1.05rem; margin:.2rem 0 .3rem; }
.cart-total strong { font-size:1.2rem; }
/* the checkout strip is PINNED, not floating (PO option 2, after the phone test): the Total, its one
   sentence and the Confirm door ride the bottom of the screen always; the lines and the details
   scroll above them. A strip that appears and disappears gets surprised by Safari's address bar and
   Chrome's tab bar collapsing mid-scroll; a strip that is simply always there is not surprised. */
.cart-checkout { position:fixed; left:0; right:0; bottom:0; z-index:40; background:#fff;
  border-top:1px solid var(--line); box-shadow:0 -2px 10px rgba(20,30,20,.12);
  padding:.5rem .9rem calc(.6rem + env(safe-area-inset-bottom, 0px)); }
.cart-checkout .cart-total { margin:.1rem 0; }
.cart-checkout .note { margin:.1rem 0 .45rem; font-size:.82rem; }
.cart-checkout .cart-confirm { width:100%; min-height:2.75rem; }
/* the gap the strip stands in: content scrolls, and the last line still clears the pinned block */
.cart-checkout-space { height:9.5rem; }
.nav-badge { background:#a33; color:#fff; border-radius:.7rem; padding:.02rem .45rem; margin-left:.35rem;
  font-size:.74rem; font-weight:600; }
/* Confirm and the payment page (piece 16b). The honeypot stays in the DOM where a script can find and fill
   it, and off the screen where a person cannot — the trap only works if honest users never see it. */
.hp { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }
.btn { display:inline-block; border:0; border-radius:.5rem; padding:.65rem 1.1rem; font-size:1rem; cursor:pointer; }
.btn.primary { background:#14532d; color:#fff; }
/* Cancel is a BUTTON like its neighbour, in its own colour family (PO 2026-09-10): a link among
   buttons invites the fat finger, an identical-coloured button worse. Red outline, same shape. */
.btn.danger { background:#fff; border:1px solid #a33; color:#a33; }
.btn.primary[disabled] { opacity:.55; cursor:default; }
.link { background:none; border:0; color:#14532d; cursor:pointer; text-decoration:underline; padding:0; font-size:.92rem; }
.link.danger { color:#a33; }
.cart-confirm-note { color:#a33; margin:.4rem 0 0; }
.hint { font-size:.8rem; color:#6b6459; }
.pay-panel { background:#fff; border:1px solid #e4ddd0; border-radius:.6rem; padding:.9rem 1rem; margin-top:.6rem; }
.pay-order-id { display:flex; align-items:center; gap:.5rem; margin:.3rem 0; }
.pay-order-id strong { font-size:1.1rem; }
.pay-amount { font-size:1.05rem; margin:.2rem 0; }
.pay-qr { margin:.5rem 0; text-align:center; }
.pay-qr img { max-width:220px; height:auto; }
.pay-qr figcaption { font-size:.85rem; color:#6b6459; margin-top:.2rem; }
.pay-steps { margin:.5rem 0 .8rem; padding-left:1.2rem; display:flex; flex-direction:column; gap:.25rem; }
.pay-items { margin:.4rem 0 .8rem; padding-left:1.2rem; }
.pay-upload { display:flex; flex-direction:column; gap:.3rem; font-size:.9rem; margin:.4rem 0; }
.pay-busy { display:flex; align-items:center; gap:.5rem; margin:.5rem 0; font-weight:600; color:#0f766e; }
.pay-busy[hidden] { display:none; }
.pay-spin { width:1rem; height:1rem; border:2px solid #99f6e4; border-top-color:#0f766e; border-radius:50%; display:inline-block; animation:pay-spin .8s linear infinite; }
@keyframes pay-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .pay-spin { animation-duration: 1.6s; } }
/* the receipt row, PO 2026-09-10: “选取文件按钮放左边，大一点，upload放在同一行的最右边，跟选择
   文件按钮一样大”. The native file control is never SHOWN — its words are the browser's own, so on a
   Chinese phone it said 选取文件, and no lang attribute can overrule the browser's UI. An invisible
   input sits under our English button; the chosen file's name is reported beside it, in our words. */
.pay-upload-row { display:flex; align-items:center; gap:.6rem; }
.pay-file { position:relative; overflow:hidden; flex:none; }
.pay-file input { position:absolute; inset:0; width:100%; height:100%; opacity:0; cursor:pointer; }
.pay-file-btn { display:inline-flex; align-items:center; justify-content:center; height:2.75rem;
  padding:0 1.1rem; border:1px solid var(--line); border-radius:.6rem; background:#fff; font-size:.95rem; }
.pay-file-name { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font-size:.88rem; color:#6b6459; }

.pay-buttons { display:flex; align-items:center; justify-content:space-between; gap:.8rem; margin-top:.7rem; }
.pay-result { color:#14532d; margin:.4rem 0 0; }
.pay-head { margin:.2rem 0 .5rem; }
.pay-head .site-h1 { display:flex; align-items:baseline; gap:.45rem; flex-wrap:wrap; }
span[data-role="pay-count"] { font-size:1.45rem; font-weight:800; font-variant-numeric:tabular-nums; letter-spacing:.04em; color:#b3261e; }
span[data-role="pay-count"].warn { color:#b3261e; }
.pay-due { margin:.15rem 0 0; font-weight:600; }
.pay-due.warn { color:#b3261e; }
.pay-detail-head, .pay-how-head { margin:.6rem 0 .25rem; }
.pay-paynow { margin:.25rem 0 .25rem .2rem; padding-left:1.1rem; }
.pay-paynow li { margin:.15rem 0; }
.pay-payee { margin:.3rem 0; }
.pay-upload-help { margin:.2rem 0 .4rem; }
.fineprint { font-size:.72rem; color:#6b7280; margin:1.2rem 0 .3rem; text-align:left; }
.find-form { display:flex; flex-direction:column; gap:.6rem; margin:.8rem 0 1rem; }
.find-form label { font-weight:600; }
.find-form input { width:100%; padding:.55rem .7rem; border:1px solid #cbd5e1; border-radius:8px; font-size:1rem; }
.find-form .btn { align-self:flex-start; min-height:2.75rem; }
.found-list { list-style:none; padding:0; margin:.4rem 0; display:flex; flex-direction:column; gap:.5rem; }
.found-exact { margin:.8rem 0 .4rem; }
.found-exact .found-order { border-color:#0f766e; border-width:2px; }
.found-rest { margin:.8rem 0; }
.found-h { font-size:.9rem; color:#374151; font-weight:600; margin:.6rem 0 .2rem; }
.found-order { display:grid; grid-template-columns:4.6rem 1fr auto; gap:.5rem .7rem; align-items:center; border:1px solid #e5e7eb; border-radius:10px; padding:.55rem .7rem; background:#fff; }
.found-when { color:#6b7280; font-size:.82rem; }
.found-what { display:flex; flex-direction:column; gap:.1rem; min-width:0; }
.found-items { color:#374151; font-size:.85rem; overflow-wrap:anywhere; }
.found-status { color:#6b7280; font-size:.8rem; }
.found-right { display:flex; flex-direction:column; align-items:flex-end; gap:.25rem; }
.found-total { font-weight:700; }
.btn.small { padding:.3rem .8rem; font-size:.85rem; border-radius:8px; }
/* The product page (piece 15). The photo area belongs to the CARD: a swipeable list with dots at the
   bottom-centre; the chips paint the whole button when chosen — a leading bubble reads as a test paper,
   a filled button reads as a choice (PO, mockup round). The radio stays underneath doing radio work. */
.p-back { font-size:.9rem; }
.p-photo { position:relative; width:100%; aspect-ratio:1/1; background:#f4f1ea; border-radius:.6rem; overflow:hidden; }
/* overflow-x:auto alone silently makes the OTHER axis scrollable too (CSS computes visible to auto
   beside a scrollable axis) — the photo area must only ever move sideways (PO bug 1) */
.p-track { display:flex; height:100%; overflow-x:auto; overflow-y:hidden; scroll-snap-type:x mandatory; scroll-behavior:smooth; scrollbar-width:none; }
.p-track::-webkit-scrollbar { display:none; }
.p-slide { flex:0 0 100%; height:100%; scroll-snap-align:center; }
.p-slide img { width:100%; height:100%; object-fit:contain; }
.p-dots { position:absolute; left:50%; transform:translateX(-50%); bottom:.55rem; display:flex; gap:.4rem; }
.p-dot { width:.55rem; height:.55rem; border-radius:50%; background:#c8c2b6; border:0; padding:0; cursor:pointer; }
.p-dot.on { background:#14532d; }
.p-axis { border:0; padding:0; margin:.9rem 0 0; }
.p-axis legend { font-weight:600; font-size:.92rem; margin:0 0 .3rem; padding:0; }
.p-opts { display:flex; flex-wrap:wrap; gap:.4rem .5rem; }
.p-opt { position:relative; display:inline-flex; align-items:center; gap:.3rem; font-size:.95rem;
  padding:.42rem .8rem; border:1px solid var(--line); border-radius:1.2rem; background:#fff; cursor:pointer; }
.p-opt input { position:absolute; opacity:0; width:0; height:0; }
.p-opt:focus-within { outline:2px solid #14532d; outline-offset:2px; }
.p-opt.checked { background:#14532d; border-color:#14532d; color:#fff; }
.p-opt .p-tag { font-size:.72rem; font-weight:600; border-radius:.3rem; padding:.05rem .3rem; }
.p-opt .p-tag.sold { background:#f3dcdc; color:#a33; }
.p-opt .p-tag.pre { background:#e3ecf7; color:#2b5787; }
.p-opt.checked .p-tag.sold { background:#fff; color:#a33; }
.p-opt.checked .p-tag.pre { background:#fff; color:#2b5787; }
.p-opt.unavailable { color:#8b8478; }
.p-resolved { margin:.9rem 0 0; padding:.7rem .8rem; background:#fff; border:1px solid #e4ddd0;
  border-radius:.6rem; display:flex; flex-direction:column; gap:.2rem; }
.p-price { font-size:1.25rem; font-weight:700; color:var(--accent); margin:0; }
.p-avail { margin:0; font-size:.9rem; color:#3f5c3a; }
.p-avail.sold { color:#a33; }
.p-buy { display:flex; align-items:center; justify-content:space-between; gap:.8rem; margin:.9rem 0 1.2rem; }
.p-max { font-size:.82rem; color:#6b6459; margin:.25rem 0 0; }
.p-add { min-width:10rem; }
.p-added { color:#14532d; margin:.2rem 0 1rem; }
/* the browse card is a tap target now (its link wraps the cover and the name) */
.card-link { display:flex; flex-direction:column; gap:.3rem; color:inherit; text-decoration:none; }
/* the whole card is the target (PO 2026-09-10: a name-less-photo card felt dead) — the link's ink stays
   where it is, its reach covers the card; sold-out cards have no link and stay inert by construction */
.card-link::after { content:""; position:absolute; inset:0; border-radius:.7rem; }
.site-foot { margin-top:2.5rem; padding:1rem 1.25rem 0; border-top:1px solid var(--line);
  font-size:.8rem; color:#5a564e; }
