/* Veyra — soft crystal-shop aesthetic, mobile-first (TikTok traffic) */
:root {
  --bg: #faf7f2;
  --surface: #ffffff;
  --ink: #2e2833;
  --ink-soft: #6f6675;
  --line: #e8e0d8;
  --accent: #7c5cbf;      /* amethyst */
  --accent-deep: #5a3d99;
  --gold: #c9a24b;
  --radius: 14px;
  --serif: "Cormorant Garamond", Georgia, serif;
  --sans: "Inter", -apple-system, sans-serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
/* Sticky-footer shell. Short pages (/lucky before a result, /404, /order-confirm)
   are shorter than the viewport, and without this the footer floated with dead
   space under it — measured 102px of bare background below the footer border on
   /lucky at 1440x900, which reads like the page failed to finish loading.
   Every section here pads rather than margins, so flexing the body does not
   change any existing spacing (no adjacent margins to stop collapsing). */
body { background: var(--bg); color: var(--ink); font-family: var(--sans); font-size: 15px; line-height: 1.55;
  min-height: 100dvh; display: flex; flex-direction: column; }
/* Flexing the body has one trap: on the cross axis an `auto` side margin
   suppresses stretch, so every centred block (.enc-group, .enc-jump, .lucky,
   .checkout) collapsed to its content width. /encyclopedia went from a 1120px
   6-column grid to a 227px single column and the page grew 5208 -> 11610px.
   A width basis restores it; max-width + auto margins still centre as before. */
body > header, body > nav, body > main, body > section, body > footer { width: 100%; }
.site-footer { margin-top: auto; }
img { display: block; }
a { color: var(--accent-deep); }

h1, h2, h3 { font-family: var(--serif); font-weight: 600; line-height: 1.15; }

/* header */
.site-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 20px; background: var(--surface); border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 50;
}
.logo { font-family: var(--serif); font-size: 24px; font-weight: 600; color: var(--ink); text-decoration: none; letter-spacing: .5px; }
.site-header nav { display: flex; gap: 18px; }
.site-header nav a { color: var(--ink-soft); text-decoration: none; font-size: 14px; }
.site-header nav a.active, .site-header nav a:hover { color: var(--accent-deep); }

/* buttons */
.btn { display: inline-block; border: 1px solid transparent; border-radius: 999px; padding: 12px 26px;
  font: 600 14px var(--sans); cursor: pointer; text-decoration: none; text-align: center; transition: .15s; }
.btn-primary { background: var(--accent); color: #fff; }
.btn-primary:hover { background: var(--accent-deep); }
.btn-dark { background: var(--ink); color: #fff; }
.btn-dark:hover { background: #000; }
.btn-ghost { background: transparent; border-color: var(--line); color: var(--ink); }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent-deep); }
.btn-block { width: 100%; margin-top: 10px; }
.btn-sm { padding: 7px 16px; font-size: 13px; }
.btn:disabled { opacity: .6; cursor: wait; }

/* ============ home page ============
   Rebuilt against the reference home page, whose section order is:
   hero -> curated wall -> encyclopedia -> craft -> gallery wall -> footer.
   Their measured surface tokens are #faf8f4 (page) and #f1ecef (soft band);
   ours are var(--bg) #faf7f2 and #f1ecef, so alternating bands read the same
   without abandoning our own palette. */

/* hero. Eyebrow / serif headline / two CTAs, one solid + one ghost. */
.home-hero { background: linear-gradient(165deg, #f1ecef 0%, var(--bg) 62%);
  padding: 84px 20px 74px; text-align: center; }
.hh-inner { max-width: 680px; margin: 0 auto; }
.hh-eyebrow { text-transform: uppercase; letter-spacing: 3px; font-size: 11px;
  color: var(--gold); font-weight: 700; margin-bottom: 18px; }
.home-hero h1 { font-size: clamp(34px, 6vw, 58px); }
.hh-sub { color: var(--ink-soft); margin: 20px auto 30px; max-width: 560px; }
.hh-cta { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }

/* the two card walls. Alternating background so the encyclopedia and craft
   blocks between them read as separate chapters rather than one long scroll. */
.home-wall { padding: 68px 20px; }
.home-wall-alt { background: #f1ecef; }
.hw-inner { max-width: 1120px; margin: 0 auto; text-align: center; }
.home-wall h2 { font-size: clamp(26px, 4vw, 34px); margin: 6px 0 8px; }
.hw-sub { color: var(--ink-soft); margin-bottom: 30px; }
.home-wall > .hw-inner > .btn { margin-top: 30px; }

/* design card. The strand preview is absolutely positioned on a square stage,
   so the ring keeps its shape at any column width. */
.dgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(196px, 1fr)); gap: 16px; }
.dcard { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; text-decoration: none; color: var(--ink); transition: .16s; text-align: left; }
.dcard:hover { border-color: var(--accent); transform: translateY(-3px); box-shadow: 0 12px 28px rgba(46,40,51,.09); }
.dcard-stage { position: relative; aspect-ratio: 1 / 1; background: linear-gradient(160deg, #f7f2f6, #efe8ee); }
.dcard-ring { position: absolute; inset: 0; }
.dbead { position: absolute; border-radius: 50%; transform: translate(-50%, -50%);
  box-shadow: inset -1px -1px 2px rgba(0,0,0,.22), inset 1px 1px 2px rgba(255,255,255,.55); }
.dcard-badge { position: absolute; left: 10px; bottom: 10px; background: rgba(255,255,255,.9);
  border-radius: 999px; padding: 3px 10px; font-size: 10.5px; font-weight: 600;
  letter-spacing: .04em; color: var(--accent-deep); }
.dcard-body { padding: 13px 14px 15px; }
.dcard-body h3 { font-size: 17px; margin-bottom: 4px; }
.dcard-meta { color: var(--ink-soft); font-size: 12px; }
.dcard-price { color: var(--accent-deep); font-weight: 600; font-size: 14px; margin-top: 5px; }

/* encyclopedia teaser: stone thumb + name + two-word meaning. */
.home-enc { padding: 68px 20px; }
.he-inner { max-width: 1000px; margin: 0 auto; text-align: center; }
.home-enc h2 { font-size: clamp(26px, 4vw, 34px); margin: 6px 0 8px; }
.he-sub { color: var(--ink-soft); margin-bottom: 30px; }
.he-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 16px; margin-bottom: 30px; }
.he-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 20px 14px; text-decoration: none; color: var(--ink); transition: .16s;
  display: grid; justify-items: center; gap: 4px; }
.he-card:hover { border-color: var(--accent); transform: translateY(-3px); }
.he-thumb { width: 74px; height: 74px; border-radius: 50%; display: grid; place-items: center;
  overflow: hidden; margin-bottom: 8px; }
.he-thumb img { width: 100%; height: 100%; object-fit: cover; }
.he-card strong { font-size: 15px; }
.he-blurb { color: var(--gold); font-size: 12px; }

/* craft: two trust panels. */
.home-craft { background: #f1ecef; padding: 62px 20px; }
.hc-inner { max-width: 1000px; margin: 0 auto; text-align: center; }
.hc-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px; margin-top: 22px; text-align: left; }
.hc-panel { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 26px 24px; }
.hc-kicker { color: var(--gold); font-size: 11px; font-weight: 700;
  letter-spacing: 1.5px; text-transform: uppercase; margin-bottom: 8px; }
.hc-panel h3 { font-size: 22px; margin-bottom: 10px; }
.hc-panel p { color: var(--ink-soft); font-size: 14px; }

/* footer.
   Three link columns + a brand block, same anatomy as the reference footer
   (theirs groups Shop / About / Help). Ours only lists pages that exist —
   a footer full of dead links is worse than a short footer. */
.site-footer { border-top: 1px solid var(--line); padding: 44px 20px 40px;
  color: var(--ink-soft); font-size: 13px; background: #faf7f9; }
.foot-cols { max-width: 1120px; margin: 0 auto; display: grid;
  grid-template-columns: 1.4fr repeat(3, 1fr); gap: 30px; text-align: left; }
.foot-brand strong { display: block; font: 600 21px var(--serif); color: var(--ink);
  letter-spacing: .06em; margin-bottom: 8px; }
.foot-brand p { font-size: 13px; line-height: 1.65; }
.foot-col h4 { font-size: 11px; text-transform: uppercase; letter-spacing: .13em;
  color: var(--ink); margin-bottom: 11px; }
.foot-col a { display: block; color: var(--ink-soft); text-decoration: none;
  padding: 4px 0; font-size: 13px; }
.foot-col a:hover { color: var(--accent-deep); }
.foot-small { max-width: 1120px; margin: 34px auto 0; padding-top: 18px;
  border-top: 1px solid var(--line); display: flex; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; font-size: 12px; }
.foot-note { opacity: .8; }

/* ============ CONTENT PAGES (encyclopedia / size guide / how-to) ============ */
/* Shared hero + closing CTA so the three content pages read as one family and
   are visibly a different surface from the studio. */
.page-hero { background: linear-gradient(165deg, #f1ecef 0%, var(--bg) 62%);
  padding: 62px 20px 52px; text-align: center; }
.ph-inner { max-width: 700px; margin: 0 auto; }
.page-hero h1 { font-size: clamp(30px, 5vw, 46px); }
.ph-sub { color: var(--ink-soft); margin-top: 16px; line-height: 1.7; }
.page-cta { text-align: center; padding: 62px 20px 74px; }
.pc-inner { max-width: 620px; margin: 0 auto; }
.page-cta h2 { font-size: clamp(26px, 4.4vw, 38px); margin-bottom: 12px; }
.page-cta p { color: var(--ink-soft); margin-bottom: 24px; }

/* Encyclopedia: colour-grouped stone wall. Grouped by colour rather than A-Z
   because customers shop a bracelet by palette first and stone name second. */
.enc-jump { max-width: 1120px; margin: 30px auto 0; padding: 0 20px;
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.enc-jump a { text-decoration: none; font-size: 12.5px; padding: 7px 14px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--surface);
  color: var(--ink-soft); }
.enc-jump a:hover { border-color: var(--accent); color: var(--accent-deep); }
.enc-group { max-width: 1120px; margin: 0 auto; padding: 42px 20px 0; scroll-margin-top: 70px; }
.enc-group h2 { font-size: 25px; }
.enc-legend { color: var(--ink-soft); font-size: 13px; margin: 5px 0 20px; }
.eg-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(178px, 1fr)); gap: 14px; }
.estone { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 15px; text-decoration: none; color: var(--ink); display: block; transition: .15s; }
.estone:hover { border-color: var(--accent); transform: translateY(-3px);
  box-shadow: 0 10px 24px rgba(46,40,51,.08); }
.estone-thumb { display: grid; place-items: center; width: 74px; height: 74px; margin: 0 auto 11px;
  border-radius: 50%; }
.estone-thumb img { width: 62px; height: 62px; object-fit: contain; }
.estone-name { display: block; font-weight: 600; font-size: 14.5px; text-align: center; }
.estone-meta { display: block; text-align: center; font-size: 11.5px; color: var(--ink-soft); margin-top: 4px; }
.estone-chakra { display: block; text-align: center; font-size: 11.5px; color: var(--gold); margin-top: 3px; }
.estone-tags { display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; margin-top: 10px; }
.etag-el { font-size: 10.5px; padding: 3px 9px; border-radius: 999px;
  background: #f2ecf8; color: #5c3f93; }
/* Treatment badge. Deliberately quiet for "Natural" and only mildly warm for a
   named treatment: this is disclosure, not a warning label. Making "Synthetic"
   look like an error would push customers off perfectly good glass beads we
   chose on purpose. */
.estone-treat { display: block; width: fit-content; margin: 6px auto 0; font-size: 10px;
  letter-spacing: .04em; text-transform: uppercase; padding: 2px 8px; border-radius: 999px;
  background: #f4f1ec; color: var(--ink-soft); }
.estone-treat.is-treated { background: #fdf4e3; color: #8a6a1f; }

/* Element legend at the foot of the encyclopedia. */
.el-inner { max-width: 900px; margin: 0 auto; }
.el-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; margin-top: 18px; }
.el-note { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; padding: 14px 15px; }
.el-note strong { display: block; font-size: 14px; margin-bottom: 4px; }
.el-note span { font-size: 12.5px; color: var(--ink-soft); line-height: 1.6; }

/* Size guide */
.sg-block { padding: 52px 20px 0; }
.sg-block-alt { background: #f1ecef; padding: 52px 20px; margin-top: 52px; }
.sg-inner { max-width: 860px; margin: 0 auto; }
.sg-block h2 { font-size: 27px; margin-bottom: 8px; }
.sg-note { color: var(--ink-soft); font-size: 14px; line-height: 1.7; margin-bottom: 18px; }
.sg-steps { margin: 0 0 6px; padding-left: 22px; }
.sg-steps li { margin-bottom: 9px; line-height: 1.65; }
.sg-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.sg-table { width: 100%; border-collapse: collapse; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; font-size: 14px; }
.sg-table th, .sg-table td { padding: 12px 14px; text-align: left; border-bottom: 1px solid var(--line); }
.sg-table th { background: #f6f1f5; font-size: 12px; text-transform: uppercase;
  letter-spacing: .09em; color: var(--ink); white-space: nowrap; }
.sg-table tr:last-child td { border-bottom: 0; }
.sg-table tr.is-ideal td { background: #faf5ff; }
.sg-tip { display: flex; gap: 10px; align-items: baseline; background: var(--surface);
  border: 1px solid var(--line); border-left: 3px solid var(--accent); border-radius: 10px;
  padding: 13px 15px; margin-top: 14px; font-size: 13.5px; line-height: 1.65; }

/* FAQ (shared by size guide + how-to) */
.faq { max-width: 860px; margin: 0 auto; }
.faq details { background: var(--surface); border: 1px solid var(--line);
  border-radius: 12px; padding: 14px 16px; margin-bottom: 10px; }
.faq summary { cursor: pointer; font-weight: 500; font-size: 15px; list-style: none; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; float: right; color: var(--accent-deep); font-size: 18px; line-height: 1; }
.faq details[open] summary::after { content: "−"; }
.faq details p { color: var(--ink-soft); font-size: 13.5px; line-height: 1.7; margin-top: 10px; }

/* How to design */
.htd-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 16px; }
.htd-steps > div { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 20px; }
.htd-num { display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%;
  background: #efe7fa; color: var(--accent-deep); font-weight: 600; font-size: 14px; margin-bottom: 11px; }
.htd-steps h3 { font-size: 19px; margin-bottom: 7px; }
.htd-steps p { color: var(--ink-soft); font-size: 13.5px; line-height: 1.65; }
.htd-tips { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px; }
.htd-tips > div { background: var(--surface); border: 1px solid var(--line);
  border-radius: 12px; padding: 16px 17px; }
.htd-tips strong { display: block; font-size: 14.5px; margin-bottom: 5px; }
.htd-tips span { font-size: 13px; color: var(--ink-soft); line-height: 1.65; }
.htd-starters { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 14px; }
.htd-starter { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 15px; text-decoration: none; color: var(--ink); transition: .15s; }
.htd-starter:hover { border-color: var(--accent); transform: translateY(-3px); }
.htd-strand { display: flex; gap: 3px; justify-content: center; flex-wrap: wrap; margin-bottom: 11px;
  min-height: 14px; align-items: center; }
.htd-strand span { border-radius: 50%; display: block; }
.htd-starter h3 { font-size: 16px; margin-bottom: 3px; }
.htd-starter-meta { font-size: 12px; color: var(--ink-soft); }

/* ============ CONTACT ============ */
/* Two columns: the form carries the weight, the rail holds direct channels and
   self-serve links. Reference puts a WhatsApp QR where our rail sits. */
.ct-inner { max-width: 1000px; margin: 0 auto; display: grid;
  grid-template-columns: 1.35fr 1fr; gap: 34px; align-items: start; }
.ct-form-col h2 { font-size: 27px; margin-bottom: 6px; }
.ct-lead { color: var(--ink-soft); font-size: 13.5px; margin-bottom: 18px; }
.ct-form { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 22px; }
.ct-form .btn { width: 100%; margin-top: 6px; }
.ct-small { font-size: 12px; color: var(--ink-soft); text-align: center; margin-top: 10px; }
/* Success banner. Green would read as a system toast; this stays in-brand and
   sits above the form rather than replacing it, so a second question is easy. */
.ct-ok { background: #f2f7f1; border: 1px solid #cfe3ca; border-radius: 12px;
  padding: 15px 17px; margin-bottom: 16px; }
.ct-ok strong { display: block; color: #3f6b39; margin-bottom: 4px; }
.ct-ok p { font-size: 13px; color: var(--ink-soft); line-height: 1.65; }
.ct-side h3 { font-size: 18px; margin-bottom: 12px; }
.ct-channel { display: block; background: var(--surface); border: 1px solid var(--line);
  border-radius: 12px; padding: 13px 15px; margin-bottom: 10px;
  text-decoration: none; color: var(--ink); transition: .15s; }
.ct-channel:hover { border-color: var(--accent); transform: translateY(-2px); }
.ct-channel strong { display: block; font-size: 14.5px; margin-bottom: 2px; }
.ct-channel span { font-size: 12.5px; color: var(--ink-soft); }
.ct-note { font-size: 13px; color: var(--ink-soft); line-height: 1.7;
  background: var(--surface); border: 1px dashed var(--line);
  border-radius: 12px; padding: 14px 15px; }
.ct-facts { margin-top: 22px; }
.ct-facts h4 { font-family: var(--serif); font-size: 15px; margin-bottom: 8px; }
.ct-facts ul { list-style: none; }
.ct-facts li { font-size: 13px; color: var(--ink-soft); line-height: 1.65; margin-bottom: 7px; }
.ct-facts p { font-size: 13px; color: var(--ink-soft); line-height: 1.7; }

/* ============ POLICY PAGES ============ */
/* Shipping & Returns. Long prose needs a readable measure and clear section
   breaks; scroll-margin-top keeps in-page anchors clear of the sticky header. */
.pol-block { margin-top: 24px; scroll-margin-top: 74px; }
/* Same treatment for the other two in-page anchor targets on the site: the
   footer's /size-guide#measure and the post-submit /contact#form redirect.
   Without this the 58px sticky header lands on top of the heading the link
   promised to jump to. */
#measure, #form { scroll-margin-top: 74px; }
.pol-block h3 { font-size: 17.5px; margin-bottom: 7px; }
.pol-block p { color: var(--ink-soft); font-size: 14px; line-height: 1.75; margin-bottom: 9px; }
.pol-block p:last-child { margin-bottom: 0; }
.pol-block strong { color: var(--ink); }
.pol-list { margin: 8px 0 12px; padding-left: 22px; color: var(--ink-soft); font-size: 14px; line-height: 1.75; }
.pol-list li { margin-bottom: 6px; padding-left: 3px; }
.pol-list li:last-child { margin-bottom: 0; }
.pol-list strong { color: var(--ink); }
.pol-note { background: var(--surface); border-left: 3px solid var(--accent);
  border-radius: 0 10px 10px 0; padding: 11px 14px; font-size: 13px !important; }

/* Materials: the counted-fact strip. auto-fit rather than a fixed column count
   because the number of stats is derived from the catalogue, not hardcoded. */
.mat-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px; margin: 22px 0 4px; }
.mat-stat { background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  padding: 15px 16px; }
.mat-stat strong { display: block; font: 600 26px var(--serif); color: var(--accent-deep); line-height: 1.1; }
.mat-stat span { display: block; font-size: 12.5px; color: var(--ink-soft); margin-top: 4px; }

/* Treatment disclosure rows. The count column is fixed-width so the labels line
   up in a readable column even though the counts run from 1 to 41. */
.mat-treat { margin-top: 18px; display: grid; gap: 10px; }
.mat-treat-row { background: var(--surface); border: 1px solid var(--line);
  border-radius: 12px; padding: 13px 15px; }
.mtr-head { display: flex; align-items: baseline; gap: 10px; }
.mtr-count { flex: 0 0 auto; min-width: 30px; font: 600 18px var(--serif); color: var(--accent-deep); }
.mtr-head strong { font-size: 15px; }
.mat-treat-row p { color: var(--ink-soft); font-size: 13.5px; line-height: 1.7; margin-top: 5px; }
.mtr-names { display: block; font-size: 12.5px; color: var(--ink); margin-top: 6px; }

/* Care: Do / Don't pair. Exactly two columns — this is a fixed pair, not a
   variable-length list, so auto-fit is the wrong tool: in the 860px measure it
   reserved a third empty track (computed "422px 422px 0px"). Harmless visually
   but it means the grid is describing a layout that does not exist. */
.care-cols { display: grid; grid-template-columns: 1fr 1fr;
  gap: 16px; margin-top: 20px; }
.care-col { border-radius: 12px; padding: 17px 18px; border: 1px solid var(--line); }
.care-col h3 { font-size: 17px; margin-bottom: 10px; }
.care-col ul { list-style: none; display: grid; gap: 8px; }
.care-col li { font-size: 13.5px; color: var(--ink-soft); line-height: 1.6;
  padding-left: 22px; position: relative; }
.care-col li::before { position: absolute; left: 0; top: 0; font-weight: 600; }
.care-do { background: #f2f7f3; border-color: #d9e7dd; }
.care-do h3 { color: #2f6b45; }
.care-do li::before { content: "✓"; color: #3f8459; }
.care-dont { background: #fbf3f4; border-color: #eddadd; }
.care-dont h3 { color: #96404d; }
.care-dont li::before { content: "✕"; color: #b0505e; }

/* ============ DESIGNER ============ */
/* One-screen app shell, matching the reference designer exactly. Measured off
   their live page: .diy3d-app is height:100vh + overflow:hidden, a flex column
   whose header is 49px and whose main row eats the remaining 699px; the canvas
   is 762x699 — i.e. it fills the FULL main-row height. Their centre column
   holds the canvas and nothing else, and both side rails scroll internally.
   Ours used to stack 6 blocks in the centre column (rail 281 + heading 70 +
   canvas 583 + stats 71 + note 32 + energy 126 = 1242px), which is why the
   page had to scroll. Now: page never scrolls, rails scroll, canvas takes
   every pixel the stats bar does not.
   The lock must start at html/body — a sticky 66px header on a 100vh main
   still overflows the viewport. */
/* BODY is the flex column, not just the main element. Measured bug from the
   first attempt: `.designer-page { height: 100vh }` plus a 58px static header
   in normal flow above it totals 806px on a 748px viewport, so the stats bar
   was clipped 44px below the fold. The header can only be "subtracted" if it
   is a flex ITEM of the same column — so the column starts at body, and the
   app takes `flex: 1` of whatever the header leaves.
   `100dvh` (not vh) because mobile browsers report vh as the tallest possible
   viewport, ignoring the URL bar — with vh the phone layout is always taller
   than the visible area. */
body:has(.designer-page) { height: 100dvh; overflow: hidden; display: flex; flex-direction: column; }
.designer-page { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; padding: 0; }
body:has(.designer-page) .site-header { flex: 0 0 auto; position: static; padding: 10px 20px; }
body:has(.designer-page) .site-footer { display: none; }
.designer { flex: 1 1 auto; min-height: 0;
  display: grid; grid-template-columns: 320px minmax(420px, 1fr) 300px; gap: 18px;
  padding: 14px 18px; max-width: 1440px; margin: 0 auto; width: 100%; }
.designer-mobile-head, .designer-steps, .designer-bottom-bar, .mobile-review-sheet { display: none; }
.section-kicker { color: var(--gold); font-size: 10px; font-weight: 700; letter-spacing: 1.7px; text-transform: uppercase; }
.lib-heading, .stage-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; }
.lib-heading { margin-bottom: 14px; }
.lib-heading h2 { font-size: 24px; }
.lib-heading > span { color: var(--ink-soft); font-size: 11px; }
.stage-heading { padding: 8px 4px 0; }
.stage-heading h1 { font-size: clamp(28px, 3vw, 40px); }
.stage-heading > p { max-width: 190px; color: var(--ink-soft); font-size: 12px; text-align: right; }

/* library — a grid cell that fills its row and scrolls INSIDE, like the
   reference app's left rail. The old `height: calc(100vh - 100px)` +
   `position: sticky` fought the new 100vh flex shell: the fixed height did not
   match the row height, so the rail either overflowed the shell or left a gap.
   `height: 100%` + `min-height: 0` lets the grid row be the single source of
   truth. */
.lib { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px;
  height: 100%; min-height: 0; overflow-y: auto; position: static;
  display: flex; flex-direction: column; }
.lib-tabs { display: flex; gap: 8px; margin-bottom: 12px; }
.lib-tab { flex: 1; padding: 9px; border: 1px solid var(--line); border-radius: 10px; background: transparent;
  font: 600 13px var(--sans); color: var(--ink-soft); cursor: pointer; }
.lib-tab.active { background: var(--ink); color: #fff; border-color: var(--ink); }
.lib-colors { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.chip { display: inline-flex; align-items: center; gap: 5px; border: 1px solid var(--line); border-radius: 999px;
  padding: 5px 11px; font-size: 12px; background: transparent; cursor: pointer; color: var(--ink-soft); }
.chip i { width: 12px; height: 12px; border-radius: 50%; display: inline-block; border: 1px solid rgba(0,0,0,.08); }
.chip.active { border-color: var(--accent); color: var(--accent-deep); background: #f5f0fc; }
.lib-theme select { width: 100%; padding: 9px; border: 1px solid var(--line); border-radius: 10px;
  font: 14px var(--sans); background: var(--surface); margin-bottom: 14px; }
/* Product tiles follow the reference designer's own metrics exactly:
   3-column grid, 6px gutters, 12px radius, 65px ROUND thumb (a bead is round —
   a square thumb was one of the reasons the library did not read as jewellery),
   11px regular name, italic grey size, bold price. */
/* [hidden] must be restated here. The `hidden` attribute only carries the UA
   stylesheet's `display: none`, which ANY author `display` declaration beats —
   so `grid.hidden = true` left the bead grid laid out at its full 6135px while
   invisible, pushing the Designs rail ~6500px down the page. That is why the
   templates looked "gone": they rendered fine (14 cards, correct prices) but
   sat far below the viewport. `.tpl-rail` already guards against this on the
   line above its own rule; this one was missing it. */
.lib-grid[hidden] { display: none; }
.lib-grid { display: grid; grid-template-columns: repeat(3, 1fr); align-content: start; gap: 6px; }
.bead-card { display: flex; flex-direction: column; align-items: center; gap: 3px;
  border: 1px solid #eee; border-radius: 12px; background: var(--surface); padding: 6px 4px 7px;
  cursor: pointer; text-align: center; font-family: var(--sans); position: relative; min-width: 0;
  transition: border-color .2s, background .2s, transform 50ms; }
.bead-card:hover { background: #f7f7f7; border-color: #ddd; }
.bead-card:active { transform: scale(.95); }
.bead-card.is-dragging { opacity: .55; border-color: var(--accent); }
.bead-thumb { width: 65px; height: 65px; border-radius: 50%; overflow: hidden;
  display: flex; align-items: center; justify-content: center; pointer-events: none; }
.bead-thumb img { width: 97%; height: 97%; object-fit: contain; }
.bead-name { font-size: 11px; font-weight: 400; line-height: 1.1; color: #333; word-break: break-word; }
.bead-meta { display: flex; align-items: baseline; justify-content: center; gap: 3px; margin-top: 1px; }
.bead-size { font-size: 10px; font-style: italic; color: #999; }
.bead-price { font-size: 10px; font-weight: 700; color: #333; }
.bead-add { position: absolute; right: 5px; top: 5px; width: 20px; height: 20px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.55); backdrop-filter: blur(6px) saturate(140%);
  color: rgba(0,0,0,.42); font-size: 14px; line-height: 1; box-shadow: 0 1px 3px rgba(0,0,0,.08);
  transition: background .18s, color .18s; }
.bead-card:hover .bead-add { background: rgba(255,255,255,.85); color: rgba(0,0,0,.7); }
/* Fallback swatch when a variant has no thumbnail yet. */
.bead-noimg { width: 86%; height: 86%; border-radius: 50%; box-shadow: inset -3px -4px 7px rgba(0,0,0,.22), inset 3px 4px 8px rgba(255,255,255,.55); }
/* Bead that follows the finger during touch drag (no HTML5 DnD on mobile). */
.bead-ghost { position: fixed; z-index: 200; width: 58px; height: 58px; margin: -29px 0 0 -29px;
  border-radius: 50%; overflow: hidden; pointer-events: none; opacity: .92;
  box-shadow: 0 10px 24px rgba(46,40,51,.28); transition: transform 90ms ease-out; }
.bead-ghost img { width: 100%; height: 100%; object-fit: contain; }
.empty { color: var(--ink-soft); font-size: 13px; grid-column: 1/-1; text-align: center; padding: 20px 0; }

/* stage — a height-driven flex column. The canvas wrap takes every pixel the
   stats bar leaves, so the strand is as large as the viewport allows and the
   page never needs to scroll. min-height:0 is what lets the wrap shrink
   inside the grid row instead of pushing the row taller than the screen. */
.stage { display: flex; flex-direction: column; gap: 10px; min-width: 0; min-height: 0; }

/* Ready-made template rail. It now lives INSIDE the library column as the
   third tab, so it is no longer a wide horizontal carousel: at 320px the old
   `overflow-x: auto` row showed 2 cards and hid the rest behind a scrollbar.
   In the rail it is a 2-up vertical grid that scrolls with the column. */
.tpl-rail[hidden] { display: none; }
.tpl-rail { background: transparent; border: 0; border-radius: 0; padding: 0; }
.tpl-rail-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.tpl-rail-head h2 { font: 600 19px var(--serif); margin: 2px 0 0; }
.tpl-rail-head span { font-size: 11px; color: var(--ink-soft); }
.tpl-filters { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 10px; }
.tpl-chip { border: 1px solid var(--line); background: #fff; color: var(--ink-soft); border-radius: 999px;
  padding: 5px 11px; font-size: 11px; cursor: pointer; transition: .15s; }
.tpl-chip:hover { border-color: var(--accent); color: var(--accent-deep); }
.tpl-chip.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.tpl-scroll { display: grid; grid-template-columns: repeat(2, 1fr); align-content: start; gap: 8px; padding: 0 0 8px; }
.tpl-card { width: auto; min-width: 0; text-align: left; cursor: pointer;
  border: 1px solid var(--line); border-radius: 14px; background: #fff; padding: 9px; transition: .16s; }
.tpl-card:hover { border-color: var(--accent); box-shadow: 0 4px 14px rgba(124,92,191,.14); transform: translateY(-2px); }
.tpl-card.active { border-color: var(--accent-deep); box-shadow: 0 0 0 2px rgba(124,92,191,.18); }
/* Real-proportion swatch: each dot is sized from the bead's actual mm, so a
   6mm mono strand and a 20mm statement charm are distinguishable at a glance.
   Equal-width bands made every template look identical. */
.tpl-swatch { display: flex; align-items: center; justify-content: center; gap: 1px; height: 30px;
  border-radius: 8px; overflow: hidden; margin-bottom: 7px; padding: 0 4px;
  background: linear-gradient(180deg, #faf7fd, #f2ecf8); }
.tpl-swatch i { display: block; flex: 0 0 auto; border-radius: 50%;
  width: var(--d, 9px); height: var(--d, 9px);
  box-shadow: inset -1px -1px 2px rgba(0,0,0,.18), inset 1px 1px 2px rgba(255,255,255,.55); }
.tpl-name { font: 600 13px var(--serif); color: var(--ink); line-height: 1.25; }
.tpl-tag { font-size: 10px; color: var(--ink-soft); margin-top: 2px; min-height: 24px; }
.tpl-meta { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; margin-top: 5px; }
.tpl-price { font: 600 13px var(--serif); color: var(--accent-deep); }
.tpl-count { font-size: 10px; color: var(--ink-soft); }
.tpl-loading { font-size: 12px; color: var(--ink-soft); padding: 12px 0; }
/* Stage backdrop. The user's current 1:1 target is the Crystafy product shot:
   a pure white, slightly cool field with NO ceramic dish. The dish below is
   kept (commented-out ready state via .ring-wrap.with-dish) as an option for
   the Stone LAB look, but the default stage is plain #f8f9fc. The WebGL
   canvas above is alpha-transparent, so bead contact shadows land directly
   on this white. */
/* Stage geometry, measured off the reference app rather than off their product
   photo. Their canvas is 762x699 (ratio 1.09) and fills the ENTIRE main row —
   it is not a square. An earlier round forced aspect-ratio:1 here to match the
   square product shot; inside a one-screen flex column that fights the height
   lock (a square wide enough for the column is taller than the row, so either
   the page scrolls or the box deforms). Filling the remaining space is both
   simpler and what the reference actually does: fitCamera() frames the strand
   on the SHORT axis, so a 1.0-1.15 ratio costs nothing visually. */
.ring-wrap { background: #f8f9fc; flex: 1 1 auto; min-height: 0; width: 100%;
  border: 1px solid var(--line); border-radius: 24px; position: relative; overflow: hidden; display: grid; place-items: center; touch-action: none; user-select: none; }
.ring-canvas { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2; display: block; cursor: grab; }
.ring-wrap.is-orbiting .ring-canvas,
.ring-wrap.is-dragging .ring-canvas { cursor: grabbing; }
.ring { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; opacity: 0; }
/* The ceramic dish, now OPT-IN: add .with-dish to .ring-wrap for the Stone
   LAB look. Default stage (the Crystafy target) has no dish at all. */
.ring-wrap::before { content: none; }
.ring-wrap.with-dish::before { content: ""; position: absolute; left: 50%; top: 50%; translate: -50% -50%;
  width: min(92%, 640px); max-height: 94%; aspect-ratio: 1; border-radius: 50%; z-index: 1; pointer-events: none;
  background:
    radial-gradient(circle closest-side, rgba(139,124,106,0) 0 78%, rgba(139,124,106,.08) 88%, rgba(139,124,106,.14) 95%, rgba(255,255,255,.7) 98%, rgba(120,106,90,.30) 100%),
    radial-gradient(circle closest-side, #f1eee9 0 55%, #eeeae4 75%, #e9e4dc 100%);
  box-shadow: 0 14px 44px rgba(96,84,70,.11), 0 4px 14px rgba(96,84,70,.07); }
/* Brand watermark at the stage centre, like the reference plate logo.
   Measured off the Crystafy target: their wordmark is a SOLID light pink
   (#fbd2dd on white), not a faint alpha grey — our previous
   rgba(148,158,178,.20) composited to (228,231,234), effectively invisible.
   Solid pastel pink is what makes theirs readable; we go one step deeper
   (#f0bcd2) because the user explicitly asked for more presence.
   The watermark is hidden on the empty stage because the empty state now shows
   its own large wordmark (.ring-hint below) in that exact spot; two pieces of
   centred type in one band is what the previous version got wrong. It fades in
   with the first bead. */
.ring-wrap::after { content: var(--brand-name, ""); position: absolute; inset: 0; z-index: 1; display: grid; place-items: center;
  font: 600 23px var(--serif); letter-spacing: .35em; color: #f0bcd2; pointer-events: none;
  opacity: 0; transition: opacity .45s ease; }
.ring-wrap.has-beads::after { opacity: 1; }
/* Empty-stage centre mark.
   Re-measured off the reference's blank canvas (DOM probe + pixel probe):
   there is NO hint text in their empty stage at all — a DOM scan of the canvas
   area returns only the top filter pills and the bottom zoom/Saved controls,
   and no ::before/::after. The single centred line the pixels show is a LARGE,
   very light neutral wordmark drawn at the circle's centre: cap height 31px,
   run width 182px, mean grey 233 (chroma 0).
   Our previous version put a 15px uppercase instruction there (cap height
   11px, grey 217) — roughly a third of the reference's size, which read as a
   form placeholder rather than a brand plate. The instruction moved to the tip
   capsule at the bottom, where instructions belong, and the centre now carries
   the wordmark at the measured scale.
   CASE: the reference's wordmark is ALL CAPS. The tell is in the per-glyph
   heights: theirs come back 30,30,30,30,32,32 — uniform, i.e. every glyph is a
   capital — over exactly 8 components, which is "CRYSTAFY". Ours were
   14,14,16,22,22,24 (mixed x-height/ascender/descender), i.e. title case. That
   difference alone changes the whole texture of the mark, so it is set in caps
   here rather than relying on the source string.
   SIZE is normalised against the canvas short side, since the two canvases
   differ (746 vs 699), and measured on the LEADING CAP only. Full-run bbox
   height is not usable: at grey ~233 the antialiased edges sit right on the
   ink threshold, so the run bbox measured 45px at 44px type but 24px at 32px —
   not self-consistent. A single capital has no ascender/descender ambiguity.
     reference lead cap 25px / 699 = 0.0358
     ours at 32px      lead cap 22px / 746 = 0.0295  => 0.824x, too small
     cap/em for this serif = 22/32 = 0.6875
     target cap = 0.0358 * 746 = 26.7px  =>  26.7 / 0.6875 = 39px
   Grey measured 235 on both sides, so the colour was already right — only the
   case and the scale were off. Run width is deliberately NOT matched: their
   name is 8 letters and ours is 10, so cap height is the honest invariant. */
.ring-hint { position: absolute; inset: 0; display: grid; place-items: center;
  color: #e9e9e9; font: 400 39px var(--serif); letter-spacing: .16em;
  text-transform: uppercase;
  text-align: center; padding: 0 40px; pointer-events: none; z-index: 3;
  transition: opacity .45s ease; }
.ring-tips { position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%); z-index: 4; pointer-events: none;
  max-width: calc(100% - 28px); padding: 7px 12px; border-radius: 999px; background: rgba(255,255,255,.82);
  border: 1px solid rgba(124,92,191,.14); color: var(--ink-soft); font-size: 11px; text-align: center; line-height: 1.35;
  box-shadow: 0 8px 20px rgba(90,61,153,.08); }
.ring-tips.is-alert { background: rgba(255,251,246,.95); border-color: rgba(196,110,90,.34); color: #a2503a; font-weight: 600; }
.ring-wrap.has-beads .ring-hint { display: none; }
/* Wearability warning inside the canvas, top-centre. Only rendered when the
   size is actually unwearable, so it never competes with the tip capsule at
   the bottom. Present on every viewport — the right rail (which carries the
   full fit note) is hidden on phones. */
.fit-pill[hidden] { display: none; }
.fit-pill { position: absolute; left: 50%; top: 12px; transform: translateX(-50%); z-index: 4;
  max-width: calc(100% - 28px); padding: 6px 13px; border-radius: 999px;
  background: rgba(255,251,246,.96); border: 1px solid rgba(196,110,90,.34);
  color: #a2503a; font: 600 11.5px var(--sans); text-align: center; line-height: 1.35;
  box-shadow: 0 6px 18px rgba(160,52,66,.12); pointer-events: none; }
/* stage highlight while a catalog bead is dragged over it */
.ring-wrap.is-drop-target { border-color: var(--accent); box-shadow: inset 0 0 0 2px rgba(124,92,191,.28), 0 10px 30px rgba(90,61,153,.12); }
.ring-wrap.is-drop-target .ring-hint { color: var(--accent-deep); }
.ring-bead-fallback { display: none; }
/* Single-line stats bar. On the locked one-screen shell every pixel this bar
   takes is a pixel the canvas loses, so the stats went from stacked
   (value over label, 71px tall) to inline (label · value, 44px) and the bar
   must NOT wrap — a wrapped second row would silently eat 30 more px of
   bracelet. Measured: 71 -> 44px gives the canvas +27px, i.e. a visibly
   larger strand at the same viewport. */
.stage-bar { display: flex; align-items: center; gap: 20px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 8px 16px; flex-wrap: nowrap; }
.stage-stat { display: flex; align-items: baseline; gap: 6px; min-width: 0; white-space: nowrap; }
.stage-stat.price { margin-left: auto; }
.stage-stat span { font: 600 16px var(--serif); }
.stage-stat label { font-size: 10px; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 1px; }
.stage-tools { display: flex; gap: 7px; }
.stage-tools .btn { margin: 0; }

/* wearable-size feedback under the stage stats */
.fit-note[hidden] { display: none; }
.fit-note { margin: 8px 0 0; font-family: var(--sans); font-size: 11.5px; line-height: 1.4;
  padding: 7px 11px; border-radius: 9px; border: 1px solid transparent; }
.fit-note.is-good { background: #f1f8f2; border-color: #cfe6d4; color: #2f6b3d; }
.fit-note.is-snug,
.fit-note.is-loose { background: #fdf6ec; border-color: #f0e0c4; color: #7a5a20; }
.fit-note.is-short,
.fit-note.is-long { background: #fdf0f1; border-color: #f2d2d5; color: #a03442; font-weight: 600; }

/* checkout blocked by an unwearable size — visible, not hidden */
.btn.is-blocked { opacity: .5; cursor: not-allowed; }

/* Ready-made starters, in the slot the AI reading panel used to occupy.
   Rows, not cards: this column is 290px wide and already carries the order
   summary plus two checkout buttons, so a swatch grid here would push
   "Review & Checkout" under the fold. Name + bead count + price is enough to
   choose from — the visual browser is the library's "Designs" tab.
   The panel is only rendered while the design is empty (see
   updateQuickTemplates), so it never competes with a finished piece. */
.quick-tpl { background: #f6f1fb; border: 1px solid #e3d7f5; border-radius: var(--radius); padding: 16px 16px 12px; }
.quick-tpl[hidden] { display: none; }
.quick-tpl-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.quick-tpl-head .section-kicker { margin: 0; }
#btnAllTemplates { border: 0; background: none; padding: 0; cursor: pointer;
  font: 500 12px var(--sans); color: var(--accent-deep); text-decoration: underline; }
.quick-tpl h3 { font-size: 19px; color: var(--accent-deep); margin: 2px 0 10px; }
.quick-tpl-list { display: grid; gap: 6px; }
.quick-tpl-row { display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 8px;
  width: 100%; text-align: left; cursor: pointer; padding: 9px 11px;
  border: 1px solid #e3d7f5; border-radius: 10px; background: var(--surface);
  font-family: var(--sans); transition: border-color .18s, box-shadow .18s, transform 60ms; }
.quick-tpl-row:hover { border-color: var(--accent); box-shadow: 0 3px 12px rgba(124,92,191,.14); }
.quick-tpl-row:active { transform: scale(.985); }
.qt-name { font-size: 13.5px; font-weight: 500; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.qt-meta { font-size: 11px; color: var(--ink-soft); }
.qt-price { font-size: 13px; font-weight: 600; color: var(--accent-deep); }

/* today's fortune.
   Visually the quieter sibling of .quick-tpl: same radius and rhythm, but a
   neutral surface with a dashed rule instead of the lilac card, because this is
   ambient content and must not compete with the starters (an action) or the
   checkout buttons above it. */
.fortune { margin-top: 14px; padding-top: 14px; border-top: 1px dashed var(--line); }
.fortune[hidden] { display: none; }
.fortune-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.fortune-head .section-kicker { margin: 0; }
.fortune-date { font-size: 11px; color: var(--ink-soft); letter-spacing: .04em; }
.fortune-line { font: 500 13.5px/1.6 var(--sans); color: var(--ink); margin: 6px 0 9px; }
.fortune-stones { display: flex; flex-wrap: wrap; gap: 6px; }
/* Chips are buttons: tapping one places that stone. Styled as affordances
   rather than tags so the click target does not read as decoration. */
.fortune-stone { font: 500 11.5px var(--sans); padding: 5px 11px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line); color: var(--accent-deep);
  cursor: pointer; transition: .15s; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fortune-stone:hover { border-color: var(--accent); background: #f8f4fd; }
.fortune-stone:active { transform: scale(.96); }

/* summary */
.summary { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px;
  height: 100%; min-height: 0; overflow-y: auto; position: static; }
.summary h3 { font-size: 22px; margin-bottom: 14px; }
.sum-row { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px dashed var(--line); }
.sum-row img { width: 34px; height: 34px; object-fit: contain; }
.sum-name { flex: 1; font-size: 13px; font-weight: 500; }
.sum-name em { color: var(--ink-soft); font-style: normal; font-size: 12px; }
.sum-qty { font-size: 12px; color: var(--ink-soft); }
.sum-price { font-size: 13px; font-weight: 600; }
.summary-total { display: flex; justify-content: space-between; align-items: baseline; padding: 14px 0 6px; }
.summary-total strong { font: 600 24px var(--serif); color: var(--accent-deep); }
.summary-note { font-size: 11.5px; color: var(--ink-soft); margin-bottom: 12px; }
.share-link { margin-top: 12px; font-size: 12.5px; word-break: break-all; background: #f0ebf8;
  padding: 10px 12px; border-radius: 8px; }
.share-link em { color: var(--accent-deep); }

/* ============ CHECKOUT ============ */
.checkout, .confirm, .lucky { max-width: 980px; margin: 0 auto; padding: 40px 20px 70px; }
.checkout h1, .confirm h1, .lucky h1 { font-size: 38px; margin-bottom: 10px; }
.co-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 24px; }
.co-summary, .co-form { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 22px; }
.co-summary h2 { font-size: 20px; margin-bottom: 6px; }
.co-wrist { font-size: 13px; color: var(--ink-soft); margin-bottom: 14px; }
.co-wrist .warn { color: #b03a2e; }
.co-money { margin-top: 14px; }
.co-money > div { display: flex; justify-content: space-between; padding: 6px 0; font-size: 14px; }
.co-total { font-weight: 700; font-size: 17px !important; border-top: 1px solid var(--line); margin-top: 6px; padding-top: 12px !important; }
.plan-cards { display: grid; gap: 10px; margin-bottom: 20px; }
.plan-card { display: block; border: 2px solid var(--line); border-radius: 12px; padding: 14px 16px; cursor: pointer; }
.plan-card:has(input:checked) { border-color: var(--accent); background: #f8f4fd; }
.plan-card.single { border-color: var(--accent); background: #f8f4fd; cursor: default; }
.plan-card input { margin-right: 8px; }
.plan-card strong { font-size: 15px; }
.plan-price { display: block; color: var(--accent-deep); font-weight: 600; margin-top: 4px; }
.plan-detail { display: block; font-size: 12px; color: var(--ink-soft); margin-top: 2px; }
.frow { margin-bottom: 12px; }
.frow label { display: block; font-size: 13px; font-weight: 500; }
.frow input, .frow textarea { width: 100%; margin-top: 5px; padding: 10px 12px; border: 1px solid var(--line);
  border-radius: 10px; font: 14px var(--sans); background: #fff; }
.frow input:focus, .frow textarea:focus { outline: 2px solid #d9c9f2; border-color: var(--accent); }
.frow-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }
.form-error { background: #fdecea; color: #b03a2e; border-radius: 8px; padding: 10px 14px; font-size: 13px; margin-bottom: 12px; }
.co-secure { text-align: center; font-size: 12px; color: var(--ink-soft); margin-top: 10px; }
/* Deliberately quiet: it sits under the pay button and must not read as a
   second call to action. Underlined rather than button-shaped so it is still
   obviously a link at this size. */
.co-edit { text-align: center; font-size: 13px; margin-top: 12px; }
.co-edit a { color: var(--ink-soft); }
.co-edit a:hover { color: var(--accent-deep); }

/* confirm */
.confirm { text-align: center; }
.confirm-lead { color: var(--ink-soft); max-width: 520px; margin: 0 auto 26px; }
.confirm-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 24px; max-width: 520px; margin: 0 auto 24px; text-align: left; }
.confirm-card h2 { font-size: 20px; margin-bottom: 12px; }
.confirm-reading { max-width: 520px; margin: 0 auto 26px; background: #f6f1fb; border: 1px solid #e3d7f5;
  border-radius: var(--radius); padding: 22px; text-align: left; }
.confirm-reading h3 { color: var(--accent-deep); margin-bottom: 10px; }
.confirm-reading p { white-space: pre-line; font-size: 14px; line-height: 1.7; }
.confirm .btn { margin: 0 6px; }

/* lucky */
.lucky { text-align: center; max-width: 620px; }
.lucky-sub { color: var(--ink-soft); margin-bottom: 26px; }
.lucky-form { display: grid; gap: 12px; max-width: 380px; margin: 0 auto 30px; }
.lucky-form input, .lucky-form select { padding: 12px; border: 1px solid var(--line); border-radius: 10px; font: 14px var(--sans); }
/* Three controls now (month / day / wrist), not two. Wrist size is not
   cosmetic: the server sizes the strand off it, so leaving it out of the form
   meant every reading returned the same 16cm bracelet. */
.lucky-date { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.lucky-date select:last-child { grid-column: 1 / -1; }
.lucky-result h2 { font-size: 30px; color: var(--accent-deep); margin-bottom: 12px; }
.lucky-text { text-align: left; line-height: 1.75; margin-bottom: 18px; white-space: pre-line; }
.lucky-stones { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-bottom: 22px; }
/* Was .etag, which was deleted with the energy panel — these tags were
   rendering unstyled until now. */
.lucky-tag { font-size: 12px; padding: 5px 12px; border-radius: 999px;
  background: #e2f0e6; color: #2e6b45; font-weight: 500; }
.loading { color: var(--ink-soft); }

/* The built strand. This card is the whole point of the page now: the reading
   used to end at three stone names and a link to an EMPTY designer, so the
   customer had to hunt down and place 21 beads by hand. */
.lucky-build { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 22px 20px; text-align: left; }
.lucky-build h3 { font-size: 21px; margin: 2px 0 16px; }
.lucky-strand { display: flex; flex-wrap: wrap; gap: 3px; justify-content: center;
  margin-bottom: 14px; }
.lucky-bead { width: 17px; height: 17px; border-radius: 50%;
  box-shadow: inset 0 -2px 3px rgba(0,0,0,.22), inset 0 2px 3px rgba(255,255,255,.5); }
.lucky-meta { font-size: 13px; color: var(--ink-soft); text-align: center; margin-bottom: 16px; }
.lucky-items { list-style: none; display: grid; gap: 7px; margin-bottom: 18px; }
.lucky-items li { display: flex; align-items: center; gap: 9px; font-size: 13.5px; }
.lucky-items i { width: 13px; height: 13px; border-radius: 50%; flex: 0 0 auto; }
.lucky-items span { flex: 1; }
.lucky-items b { color: var(--ink-soft); font-weight: 500; }
.lucky-tweak { font-size: 12px; color: var(--ink-soft); text-align: center; margin-top: 9px; }

/* 404 */
.fourofour { text-align: center; padding: 90px 20px; }
.fourofour p { color: var(--ink-soft); margin: 12px 0 26px; }

/* admin */
.admin { padding: 30px; }
.admin-meta { color: var(--ink-soft); font-size: 13px; margin-top: 6px; }
.admin-empty { color: var(--ink-soft); margin-top: 18px; padding: 22px; background: #fff; border: 1px solid var(--line); border-radius: 10px; }
/* An eleven-column table cannot fit a phone. Scroll the table itself rather
   than letting it widen the document — a page-level horizontal scrollbar drags
   the shared header and footer sideways with it. */
.admin-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.admin-table { width: 100%; border-collapse: collapse; font-size: 13px; background: #fff; margin-top: 16px; }
.admin-table th, .admin-table td { border: 1px solid var(--line); padding: 8px 10px; text-align: left; }
.admin-table th { background: #f4effa; }
.st { padding: 2px 9px; border-radius: 999px; font-size: 11px; background: #eee; }
.st-paid_full, .st-paid_deposit { background: #dff0e3; color: #2e6b45; }
.st-pending_payment { background: #fdf0dc; color: #8a6420; }
.st-shipped, .st-done { background: #dde9f8; color: #2c5b8f; }

/* admin order detail (fulfilment view) */
.admin-back { font-size: 13px; margin-bottom: 10px; }
/* Red, filled, and full width. This is the one warning on the page that costs
   real money if it is skimmed past, so it does not share the muted styling the
   rest of the admin chrome uses. */
.admin-warn { margin: 16px 0 0; padding: 12px 14px; border-radius: 10px;
  background: #fdeaea; border: 1px solid #e9b4b4; color: #8f2c2c; font-size: 14px; }
/* Two columns on desktop, stacked below. minmax(280px, 1fr) rather than
   1fr 1fr so a long address never squeezes the payment block into a sliver. */
.admin-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin-top: 18px; }
.admin-block { background: var(--surface); border: 1px solid var(--line); border-radius: 10px; padding: 18px 20px; margin-top: 16px; }
.admin-grid .admin-block { margin-top: 0; }
.admin-block h2 { font-size: 17px; margin-bottom: 4px; }
.admin-block h3 { font-size: 14px; font-family: var(--sans); font-weight: 600; }
/* Postal block: normal weight, real line breaks, selectable as one unit so it
   can be copied straight into a courier form. font-style:normal because
   <address> is italic by default in every browser and an italic address label
   reads like a quotation. */
.admin-address { font-style: normal; margin-top: 12px; padding: 12px 14px; background: var(--bg);
  border: 1px solid var(--line); border-radius: 8px; line-height: 1.7; user-select: all; }
.admin-dl { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; margin-top: 14px; font-size: 14px; }
.admin-dl dt { color: var(--ink-soft); }
.admin-dl dd { margin: 0; }
/* Stripe ids are long opaque strings — monospace and break-all so they wrap
   inside the card instead of stretching it. */
.admin-dl-mono dd { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; word-break: break-all; }
.admin-notes { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.admin-notes p { margin-top: 4px; font-size: 14px; }
/* Numbered stringing order. The counter is the content here, so it stays
   visible at the left edge and the row aligns to it. */
.admin-seq { list-style: none; counter-reset: bead; margin-top: 14px; }
.admin-seq li { counter-increment: bead; display: flex; align-items: center; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.admin-seq li:last-child { border-bottom: 0; }
.admin-seq li::before { content: counter(bead); flex: none; width: 24px; text-align: right;
  color: var(--ink-soft); font-size: 12px; font-variant-numeric: tabular-nums; }
.admin-swatch { flex: none; width: 18px; height: 18px; border-radius: 50%;
  border: 1px solid rgba(0,0,0,.18); box-shadow: inset 0 0 4px rgba(255,255,255,.55); }
.admin-seq-name { flex: 1; }
.admin-seq-size { flex: none; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.admin-seq-sku { flex: none; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; color: var(--ink-soft); }
.admin-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.admin-reading { margin-top: 10px; font-size: 14px; line-height: 1.65; color: var(--ink-soft); }
.admin-del { display: inline; margin-left: 10px; }
.admin-del button { background: none; border: 1px solid var(--line); border-radius: 6px; padding: 2px 8px; font-size: 12px; color: #b42318; cursor: pointer; }
.admin-del button:hover { border-color: #b42318; background: #fef3f2; }
.admin-del-block { display: block; margin: 18px 0 0; }
.admin-del-block button { padding: 8px 14px; font-size: 13px; }

/* ============ mobile ============ */
@media (max-width: 1080px) {
  .designer-page { padding-bottom: 86px; }
  body:has(.designer-page) .site-header, body:has(.designer-page) .site-footer { display: none; }
  /* Slim single-row app bar. It carries the title AND the Undo/Reset tools,
     because on a locked-height phone screen every strip of chrome is height
     stolen from the canvas: the old head (61px) + steps bar (36px) + in-stage
     stats bar (63px) + bottom bar (62px) left the canvas only 225px. The
     steps bar was pure decoration and the stats bar duplicated the numbers
     already shown in the bottom bar, so both are gone and the two tool
     buttons they hosted moved up here. */
  .designer-mobile-head { display: grid; grid-template-columns: 34px 1fr auto; align-items: center; gap: 8px;
    padding: 6px 12px; background: rgba(255,255,255,.96); border-bottom: 1px solid var(--line);
    flex: 0 0 auto; z-index: 60; backdrop-filter: blur(12px); }
  .designer-mobile-head > a { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 50%; text-decoration: none; color: var(--ink); background: #f5f1f8; font-size: 24px; line-height: 1; }
  .designer-mobile-head > div { text-align: center; line-height: 1.15; min-width: 0; }
  .designer-mobile-head strong { display: block; font: 600 15px var(--serif); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .designer-mobile-head span { color: var(--ink-soft); font-size: 9.5px; }
  .mobile-head-tools { display: flex; gap: 4px; }
  .designer-mobile-head button { border: 1px solid var(--line); border-radius: 999px; background: #fff;
    color: var(--accent-deep); font: 600 11px var(--sans); padding: 5px 10px; cursor: pointer; }
  /* Phone shell: the reference app locks 100vh here too — measured on their
     390x844 viewport the root is a flex COLUMN with the canvas block at
     flex:3 and the catalogue at flex:2, both inside `overflow:hidden`, so the
     page itself never scrolls and only the catalogue scrolls internally.
     Ours used to be a plain stack that ran ~1900px tall. */
  .designer-page { padding-bottom: 0; }
  .designer { display: flex; flex-direction: column; gap: 0; padding: 0; max-width: 760px;
    flex: 1 1 auto; min-height: 0; overflow: hidden; }
  /* Canvas block 3 : catalogue 2, exactly the reference split. */
  .stage { order: 1; flex: 3 1 0; min-height: 0; gap: 0; display: flex; flex-direction: column; }
  .lib { order: 2; flex: 2 1 0; min-height: 0; padding: 12px 14px 14px;
    position: static; height: auto; border-radius: 0; border-width: 1px 0 0; overflow-y: auto; }
  .summary { display: none; }
  /* On phones the canvas gets the WHOLE stage block. The stats bar is hidden
     here, not restyled: its three numbers (bead count, wrist fit, price) are
     already in the bottom bar, so keeping it cost 63px of canvas to show the
     same data twice. Undo/Reset moved to the app bar. */
  .ring-wrap { order: 1; flex: 1 1 auto; }
  .stage-bar { display: none; }
  .fit-note { display: none; }
  .tpl-rail { border-radius: 0; border-width: 0 0 1px; padding: 12px 14px 14px; }
  .tpl-rail-head h2 { font-size: 21px; }
  .tpl-rail-head > span { display: none; }
  .tpl-scroll { margin: 0 -14px; padding: 2px 14px 8px; }
  .tpl-card { width: 138px; }
  .tpl-filters { flex-wrap: nowrap; overflow-x: auto; margin: 0 -14px 8px; padding: 0 14px 6px; scrollbar-width: none; }
  .tpl-filters::-webkit-scrollbar { display: none; }
  .tpl-chip { flex: 0 0 auto; }
  .stage-heading { display: none; }
  /* Stage skin on phones. NO aspect-ratio here: in the locked column the
     canvas height comes from `flex: 1 1 auto` above, and an aspect-ratio
     would fight it (the box would size itself from its width and either
     overflow the shell or leave dead space). Only the inset + skin lives
     here; height is the flex row's business. */
  .ring-wrap { border: 0; width: calc(100% - 20px); margin: 8px 10px; border-radius: 20px;
    box-shadow: 0 2px 14px rgba(46,40,51,.05); }
  .ring { max-width: 540px; }
  .ring-tips { bottom: 10px; font-size: 10px; white-space: normal; max-width: calc(100% - 20px); }
  .stage-bar { border-width: 1px 0; border-radius: 0; padding: 10px 14px; gap: 16px; }
  .stage-stat { flex: 1; min-width: 0; text-align: center; }
  .stage-stat.price { margin: 0; text-align: center; }
  .stage-stat span { font-size: 17px; }
  .stage-tools { width: 100%; justify-content: center; border-top: 1px solid #f0ebf2; padding-top: 8px; }
  .stage-tools .btn { flex: 1; max-width: 150px; }
  .lib-heading { align-items: center; }
  .lib-heading h2 { font-size: 25px; }
  /* Sticky to the TOP OF THE RAIL, not of the page: the rail is now its own
     scroll container, so the old `top: 105px` (measured against the page)
     parked the tabs 105px down inside the rail and clipped the grid. */
  .lib-tabs { position: sticky; top: 0; z-index: 20; background: var(--surface); padding: 4px 0 8px; }
  .lib-colors { flex-wrap: nowrap; overflow-x: auto; padding: 1px 1px 8px; margin: 0 -14px 8px; padding-left: 14px; scrollbar-width: none; }
  .lib-colors::-webkit-scrollbar { display: none; }
  .chip { flex: 0 0 auto; }
  .lib-theme select { margin-bottom: 12px; }
  .lib-grid { grid-template-columns: repeat(4, 1fr); gap: 8px; }
  /* Static, not fixed: in the locked 100vh shell a fixed bar floats ON TOP of
     the catalogue and permanently hides its last row (with no page scroll left
     to reveal it). As the shell's last flex item it reserves its own height. */
  .designer-bottom-bar { display: grid; grid-template-columns: 1fr minmax(144px, .8fr); align-items: center; gap: 10px; flex: 0 0 auto; z-index: 70; padding: 9px 12px calc(9px + env(safe-area-inset-bottom)); background: rgba(255,255,255,.97); border-top: 1px solid var(--line); box-shadow: 0 -8px 30px rgba(46,40,51,.09); backdrop-filter: blur(12px); }
  .designer-bottom-bar .btn { width: 100%; padding: 12px 15px; }
  .bottom-design-info { border: 0; background: transparent; text-align: left; color: var(--ink); font-family: var(--sans); cursor: pointer; min-width: 0; }
  .bottom-design-info span { display: block; color: var(--ink-soft); font-size: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .bottom-design-info strong { display: block; font: 600 22px var(--serif); color: var(--accent-deep); }
  /* Bottom-sheet motion.
     The old version animated on insert only (animation: sheet-up .18s), which
     meant there was no exit animation at all — panel and dark backdrop both
     vanished in a single frame — and the backdrop appeared at full 38% black
     instantly. Both directions are now state-driven off .is-open, so open and
     close are symmetrical and the panel can be dragged down to dismiss.
     Easing: opening uses an expo-out curve, which decelerates hard at the
     end. That is what reads as "springy" without a real overshoot; a true
     overshoot would lift the panel off the bottom edge mid-flight and a gap
     under a bottom sheet looks like a bug, not a bounce. */
  .mobile-review-sheet[hidden] { display: none; }
  .mobile-review-sheet { display: block; position: fixed; inset: 0; z-index: 100; }
  .sheet-backdrop { position: absolute; inset: 0; width: 100%; border: 0; background: rgba(30,24,34,.38);
    opacity: 0; transition: opacity .26s ease; }
  .mobile-review-sheet.is-open .sheet-backdrop { opacity: 1; }
  .mobile-review-sheet > section { position: absolute; left: 0; right: 0; bottom: 0; max-height: 78vh; overflow-y: auto;
    background: #fff; border-radius: 22px 22px 0 0; padding: 8px 18px calc(20px + env(safe-area-inset-bottom));
    box-shadow: 0 -16px 50px rgba(30,24,34,.2); will-change: transform;
    /* 101%, not 100%: at exactly 100% the 50px-blur shadow still bleeds a
       grey band across the bottom bar while the sheet is parked off-screen. */
    transform: translateY(101%);
    transition: transform .42s cubic-bezier(.16,1,.3,1); }
  .mobile-review-sheet.is-open > section { transform: translateY(0); }
  /* Closing runs faster than opening: a slow dismissal reads as lag, a slow
     entrance reads as deliberate. */
  .mobile-review-sheet.is-closing > section { transition: transform .24s cubic-bezier(.3,0,.8,.6); }
  .mobile-review-sheet.is-closing .sheet-backdrop { transition-duration: .18s; }
  /* While a finger is down the panel tracks it 1:1 — easing here would make
     the drag feel rubbery and laggy. */
  .mobile-review-sheet.is-dragging > section { transition: none; }
  /* touch-action: none so a vertical drag on the handle is ours and does not
     get claimed by the sheet's own overflow scrolling. */
  .sheet-handle { position: relative; width: 38px; height: 4px; border-radius: 99px; background: #d8d0dc;
    margin: 2px auto 12px; touch-action: none; cursor: grab; }
  .mobile-review-sheet.is-dragging .sheet-handle { cursor: grabbing; }
  /* Invisible 140x34 grab pad around the 4px pill: the visual handle is far
     too thin to hit reliably with a thumb. */
  .sheet-handle::before { content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
    width: 140px; height: 34px; }
  .sheet-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
  .sheet-head h3 { font-size: 26px; }
  .sheet-head button { border: 0; background: transparent; color: var(--accent-deep); font-weight: 600; }
  body.sheet-open { overflow: hidden; }
  .co-grid { grid-template-columns: 1fr; }
  /* Contact drops to one column here for the same reason checkout does: below
     ~1080 the 300px aside squeezes the form fields past comfortable typing
     width. Form first, side channels after — the form is the working channel. */
  .ct-inner { grid-template-columns: 1fr; gap: 30px; }
  .ct-side { position: static; }
}
@media (max-width: 560px) {
  .lib-grid { grid-template-columns: repeat(3, 1fr); }
  .bead-card { padding: 8px 3px; }
  .bead-card img { width: 52px; height: 52px; }
  /* No aspect-ratio: in the locked one-screen shell the stage height comes
     from the flex row (.stage flex:3 → .ring-wrap flex:1). An aspect-ratio
     here would size the box from its width and break the lock. */
  .frow-3 { grid-template-columns: 1fr; }
  /* Do / Don't stack here rather than at 1080: inside the 860px prose column
     two columns are still 422px each at 1080, which reads fine. They only get
     cramped once the column itself is phone width. */
  .care-cols { grid-template-columns: 1fr; }
  /* Six nav links in a nowrap flex row need ~478px once the logo and padding
     are counted, so on a 390px phone the header was pushing the whole document
     to 458px wide. The browser's response is to shrink the initial containing
     block to fit, which scales EVERY page down to ~0.85x — smaller body text
     site-wide, and a 100dvh body resolving to 991px so short pages showed a
     ~116px blank strip under the footer. Allowing the nav to wrap fixes all of
     it at the source. Wrapping rather than a horizontal-scroll strip on
     purpose: a scroller hides links behind a gesture nobody is told about,
     and WhatsApp is the support channel — it cannot be the item that scrolls
     out of sight. */
  .site-header { padding: 12px 14px; align-items: flex-start; }
  .site-header nav { flex-wrap: wrap; justify-content: flex-end; column-gap: 12px; row-gap: 7px; }
  .site-header nav a { font-size: 13px; }
}
