/* ============================================================
   YLG Bullion  — PHP/MySQL style sheet
   Replicates the original AI-Studio React design system.
   ============================================================ */

/* Faces taken from www.ylgbullion.trade (the React/Tailwind build):
     Cinzel  display / headings and the logo medallion
     Inter   body text
     Padauk  Burmese -- neither Cinzel nor Inter carries Burmese glyphs, so those
             pages were previously relying on an implicit system fallback.

   Plus Jakarta Sans is no longer loaded. It was the face of the earlier
   prototype, not the live site's. */
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@600;700;800&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Padauk:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap');

:root {
  /* Palette lifted from the live site's Tailwind config. Its utility values are
     recoverable from the shipped stylesheet: #b87f28 and #d99b26/#df9926 for
     the gold, the emerald-700 -> emerald-800 pair (#1b7a4b -> #125333) for buy,
     #c93232 for sell, and slate-900 #0f172a for ink.

     The live site renders on a dark surface; this app stays light, per the
     decision to change the palette without changing the background. That is
     why --surface and --card below are light while the accents are the live
     site's: its dark neutrals read as muddy against a white page. */

  /* Neutrals. Stone here, slate in the live file -- both are warm/cool greys
     and only --ink is referenced by name in the markup. */
  --stone-50:  #fafaf9;
  --stone-100: #f5f5f4;
  --stone-200: #e7e5e4;
  --stone-300: #d6d3d1;
  --stone-500: #78716c;
  --stone-600: #57534e;
  --stone-700: #44403c;
  --stone-900: #1c1917;

  /* Gold. The live site's gold is deeper and less yellow than the old
     #e5a93c/#b8860b pair: --gold is now its mid tone and --gold-dark its
     #b87f28 border tone. */
  --amber-100: #fff9ed;
  --amber-500: #e6a824;
  --amber-600: #d99b26;
  --amber-700: #b87f28;
  --amber-800: #8a631a;

  /* The live site has no bright green: buy is a deep #1b7a4b falling to
     #125333. -500 is kept a step lighter so the button gradient still reads as
     a gradient rather than one flat fill. Hover DARKENS on hover (emerald-800),
     where the old bright green used to lighten to #34d399. */
  --emerald-500: #218a55;
  --emerald-600: #1b7a4b;
  --emerald-700: #125333;
  --emerald-800: #0d3d26;
  --red-600: #c93232;
  --red-700: #a32727;

  /* Tints for the status pills and banners (approved / rejected / pending).
     These are Tailwind's emerald-50/200/800, red-50/100/200 and orange-50/700.
     They were pasted as bare hexes into eleven separate rules, which is why the
     pill backgrounds drifted apart from the .market-icon tints that were meant
     to match them. Note --emerald-800 above is the BUTTON hover stop and is a
     different colour from the --ok-ink used for text on a green tint. */
  --ok-bg: #ecfdf5;
  --ok-border: #a7f3d0;
  --ok-ink: #065f46;
  --bad-bg: #fef2f2;
  --bad-border: #fecaca;
  --bad-soft: #fee2e2;
  --warn-bg: #fff7ed;
  --warn-ink: #c2410c;
  --warn-border: #fde68a;

  /* Tailwind's default gray ramp. This is a SECOND neutral ramp alongside the
     --stone-* set above, and it was previously present only as ~50 bare hexes
     spread through the file. Both are kept because --stone-* is what the
     rewritten component rules reference by name while the gray values came in
     with the original markup; collapsing them to one ramp is a larger change
     than a restyle and would shift every border and placeholder. What matters
     here is that both are now named, so a value is changed in one place. */
  --gray-50: #f9fafb;
  --gray-100: #f3f4f6;
  --gray-300: #d1d5db;
  /* Tailwind's #6b7280 is 4.47:1 on --surface, a hair under the 4.5 that 11-12px
     hint text needs. Nudged one step darker; it is visually indistinguishable
     from #6b7280 and clears the bar on both --surface and white cards.

     There is deliberately no --gray-400. The live site uses Tailwind's
     #9ca3af for placeholders and timestamps, which is only 2.5:1 on white --
     the muted text there is decorative to the point of being hard to read.
     Every --gray-400 use in this file was text (placeholders, market-sub,
     list-date, empty-hint), so they all moved up to --gray-500 instead of
     keeping a token that cannot pass. */
  --gray-500: #68707f;
  --gray-600: #4b5563;
  --gray-700: #374151;
  --gray-800: #1f2937;
  --gray-900: #111827;

  /* Warm neutral family. Same roles as before (chrome, tint, border) but the
     chroma has been cut from the live site's #e2a893 / #ebb5a0 / #faece8 set.
     Hue is unchanged (~16deg); only saturation is down 20-38%. At full
     saturation the peach read too pink sitting next to the deeper gold, and it
     was the loudest thing on holdings.php. Desaturation also lightens these
     values, which is why every FOREGROUND use moved to --rose-ink below. */
  --rose: #d9ac9c;
  --rose-light: #e3b9a8;
  --rose-bg: #f7eeeb;
  --rose-border: #efdad2;
  /* Mid stop for the three-stop rose washes (.card-rose, .holding-row,
     .vault-promo). Previously hardcoded as #f7d6cd in each rule, which is how
     they drifted apart. This is the darkest stop of the wash, so it is the
     binding backdrop for any glyph sitting on one of those cards. */
  --rose-mid: #f2e1db;

  /* The foreground half of the rose family. --rose is a FILL; using it as text
     or as a glyph on --rose-bg gave 1.78:1, i.e. effectively invisible, and as
     the .input:focus ring on white gave 1.78:1 against a 3:1 requirement. This
     is the same relationship the gold already has (--gold / --gold-dark /
     --gold-ink) and is the value every rose foreground should use:
       on --rose-bg  4.30:1   on --rose-mid 3.87:1   on white 4.91:1 */
  --rose-ink: #a35f45;

  /* Neutral ink for small brand text that predates the warm family. The
     medallion tagline is the only user; it was #333 before the restyle and was
     picked up by the sweep and moved to --brown, which tinted a 4px line of
     uppercase type. Back to neutral. */
  --ink-soft: #333333;

  /* Gold, semantic step. The --amber-* ramp above holds the low-contrast
     background tones; these two are the ones components actually fill with.
     Together with --gold-ink below they form the three-step gold scale. */
  --gold: #d99b26;
  --gold-dark: #b87f28;
  /* The live site's #b87f28 is a border tone, not a text colour: on white it
     lands at 3.44:1, short of the 4.5:1 that body-sized text needs. This is the
     same hue stepped down until the label passes -- 4.64:1 on white, 4.80:1
     under --brown on gold. Used for gold-coloured TEXT; --gold-dark is still
     what the borders use. */
  --gold-ink: #9c6b18;
  --gold-soft: #f1d79a;
  --gold-leaf: #c79a3c;
  --red: #c93232;

  /* Dark label for text sitting ON a gold fill. Gold is a light colour, so the
     live site's white-on-gold buttons cannot pass; this is the darkest of the
     app's browns and clears 4.5:1 against every gold stop from #d99b26 down to
     #b87f28. */
  --on-gold: #2d1a18;

  /* Semantic aliases. Components reference these, not the raw ramps, so that
     swapping a ramp value moves every consumer at once. --support is one step
     lighter than --buy on purpose: it is a quieter accent (the send button). */
  --sell: var(--red-600);
  --buy: var(--emerald-600);
  --support: var(--emerald-500);

  /* Page surfaces. The app stays on a LIGHT background by decision, so these
     are all light; the live site's equivalents are dark slate and only its
     accents were borrowed. --brown is the warm ink for text sitting on the
     peach family, which is why it is a brown and not a neutral. */
  --charcoal: #2d3748;
  --charcoal-dark: #1a202c;
  --surface: #f8f6f0;
  --cream: #fff9ed;
  --brown: #4a2e2b;
  /* The live site's ink is slate-900 (#0f172a), darker and cooler than the old
     #1e293b. This drives .input and .text-ink. */
  --ink: #0f172a;

  --blue-input-bg: #f0f6ff;
  --blue-input-border: #d8e6f8;
  /* Link colour on the auth panel. That panel is the dark rose header, so this
     has to be a light blue that passes on #4a2e2b; it is not the same role as
     --blue-input-* above, which are for field fills on a light page. */
  --link-on-dark: #a5d8ff;

  /* Inactive tab / chip fill. Cooler than the --stone-* ramp and the only
     neutral in the file that is not either stone or gray. */
  --pill-idle: #eef2f6;

  /* Error border on an invalid field. Deliberately lighter than --red-600: the
     border sits next to a red message, and a full-strength red rule at that
     size reads as a second error. */
  --red-soft: #f87171;

  /* The chart skeleton shimmer. Two stops so the sweep is visible; kept as a
     pair because the gradient is meaningless if only one of them is changed. */
  --shimmer-from: #fafbfc;
  --shimmer-to: #f1f3f5;

  /* Medallion face and the near-white option/row fills. Bare #fff reads as a
     hole next to a tinted card, so these are fractionally warm. */
  --medallion-face: #fffbf0;
  --paper-warm: #fffdfd;

  /* Corner radii, from the live file's Tailwind scale, so card/button radii
     land on values the live site actually uses rather than ad-hoc pixels. */
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 10px;
  --radius-xl: 12px;
  --radius-2xl: 16px;
  /* The app's row/card radius. It is NOT on the live site's Tailwind scale --
     it was an ad-hoc pixel value here -- but it is used in ten places, so it is
     tokenised rather than rounded to the nearest step, which would visibly
     change every list row and market tile. */
  --radius-card: 14px;
  --radius-pill: 999px;

  --font-display: 'Cinzel', 'Padauk', Georgia, serif;
  --font-body: 'Inter', 'Padauk', system-ui, -apple-system, sans-serif;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  font-family: var(--font-body);
  background: var(--surface);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

/* Cinzel for anything that reads as a heading or the brand. Scoped to real
   heading elements and the explicit display classes rather than applied
   globally: Cinzel has no Burmese or Thai glyphs, so a blanket rule would drop
   those pages back to a system fallback mid-word. Padauk is listed first in
   --font-display precisely so a Burmese or Thai heading still renders. */
h1, h2, h3, h4, h5, h6,
.logo-medallion {
  font-family: var(--font-display);
  font-weight: 700;
}
/* Inter's own numerals are shorter than Plus Jakarta Sans's, which makes the
   balance and price figures read smaller than the labels around them. Nudge
   the weight rather than the size. */
.font-mono { letter-spacing: -.01em; }

/* clip, not just hidden: hidden turns body into a scroll container, which can
   silently defeat the sticky .header-rose (sticky needs to stick to the
   viewport). clip cuts horizontal overflow without creating one. hidden stays
   first as the fallback for browsers without clip support — there the header
   simply scrolls away, exactly as it always did on phones. */
body { overflow-x: hidden; overflow-x: clip; }

.material-symbols-outlined {
  font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
  vertical-align: middle;
  user-select: none;
}

.app { min-height: 100vh; display: flex; flex-direction: column; }

.hidden { display: none !important; }

/* An open dialog owns the viewport: stop the page behind it from scrolling. */
body.modal-open { overflow: hidden; }

/* Keyboard focus must be visible on every interactive element. The UA default
   ring is removed against a coloured header and pale cards, so restore an
   explicit one that works on both. */
:focus-visible {
  outline: 2px solid var(--amber-600);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.btn:focus-visible, .chip:focus-visible, .nav-link:focus-visible,
.ribbon-item:focus-visible {
  outline: 2px solid var(--stone-700);
  outline-offset: 2px;
}
/* On the dark order-modal header the stone ring has almost no contrast against
   the gradient, so the ring flips to light there. */
#orderModal :focus-visible { outline-color: #fff; }

/* The page wrapper. Deliberately NOT .app — header.php already opens one, and
   nesting a second min-height:100vh flex column inflated the scroll height. */
.page-shell { width: 100%; }

/* ---------- Buttons ---------- */
.btn {
  display: flex; align-items: center; justify-content: center; gap: .4rem;
  border: none; cursor: pointer; text-align: center;
  font-family: inherit; transition: all .15s ease; user-select: none;
}
.btn:active { transform: scale(.98); }
.btn:disabled { opacity: .6; cursor: not-allowed; }

/* Full-width action buttons.
   .w-full was used throughout the markup (Buy, Sell, Deposit, Withdraw,
   Donate, and every modal's submit) but was never defined here. .btn is
   display:flex, so as a flex item it sized to its content instead of filling
   the row — the primary CTAs rendered short. In a flex row, two width:100%
   children with the default flex-shrink split the row evenly, which is what
   the Buy/Sell pair wants. */
.w-full { width: 100%; }
.row > .w-full { min-width: 0; }

/* Gold is a light colour, so these carry a dark label rather than white:
   white on --gold (#d99b26) is only 2.42:1. --on-gold clears 4.5:1 on every
   gold stop used here. */
.btn-gold {
  background: var(--gold); color: var(--on-gold); font-weight: 600;
  border-radius: var(--radius-pill); padding: .65rem 1.5rem; box-shadow: 0 2px 6px rgba(0,0,0,.12);
}
.btn-gold:hover { background: var(--gold-dark); }

.btn-outline-gold {
  background: rgba(255,255,255,.92); color: var(--gold-ink);
  border: 2px solid var(--gold); border-radius: var(--radius-pill); padding: .65rem 1.5rem;
  font-weight: 600; box-shadow: 0 2px 6px rgba(0,0,0,.12);
}
.btn-outline-gold:hover { background: var(--gold); color: var(--on-gold); }

/* Buy/sell CTAs. Gradients and the heavier weight come from the prototype's
   execution button. app.js:522 rewrites confirmBtn.className to one of these
   two class names when the ticket direction flips, so the pair has to stay
   interchangeable -- same box, only the gradient direction changes. */
.btn-buy {
  background: linear-gradient(135deg, var(--emerald-600), var(--emerald-500));
  color: #fff; border-radius: var(--radius-xl); padding: .75rem 1rem;
  font-weight: 700; box-shadow: 0 4px 12px rgba(27,122,75,.28);
}
/* Darkens on hover, matching the live site's emerald-800 hover stop rather than
   lightening to a mint that is no longer in the palette. */
.btn-buy:hover  { background: linear-gradient(135deg, var(--emerald-500), var(--emerald-800)); }
.btn-sell {
  background: linear-gradient(135deg, var(--red-700), var(--red-600));
  color: #fff; border-radius: var(--radius-xl); padding: .75rem 1rem;
  font-weight: 700; box-shadow: 0 4px 12px rgba(201,50,50,.28);
}
.btn-sell:hover { background: linear-gradient(135deg, var(--red-600), var(--red-700)); }

.btn-rose {
  background: var(--rose); color: var(--brown); border-radius: var(--radius-pill);
  padding: .65rem 1rem; font-weight: 600; box-shadow: 0 2px 6px rgba(0,0,0,.12);
}
.btn-rose:hover { background: var(--rose-light); }

.btn-charcoal { background: var(--charcoal); color: #fff; border-radius: var(--radius-pill); padding: .65rem 1rem; font-weight: 500; }
.btn-charcoal:hover { background: var(--charcoal-dark); }

/* Donate. The prototype's DonateYieldPage.tsx is an empty file, so there is no
   donate design to copy and this is derived from the same amber/stone language
   as the rest of the re-skin. Scoped by id rather than reusing a .btn-* class:
   .btn-charcoal also carries Withdraw (trade.php:243, balance_modals.php:147),
   and donation should not repaint an unrelated action. */
#donateModal .modal-head { border-bottom-color: var(--amber-500); }
/* The live site's gold gradient is #d99b26 -> #df9926 (via #f59e0b), i.e. much
   closer together than the old #f59e0b -> #d97706. The label is --on-gold, not
   white: white on #b87f28 is 3.44:1, and --on-gold clears 4.5:1 on both stops. */
#confirmDonateBtn {
  background: linear-gradient(135deg, var(--amber-600), var(--amber-700));
  color: var(--on-gold); border-radius: var(--radius-xl); padding: .75rem 1rem;
  font-weight: 700; box-shadow: 0 4px 12px rgba(184,127,40,.3);
}
#confirmDonateBtn:hover { background: linear-gradient(135deg, var(--amber-500), var(--amber-600)); }
#confirmDonateBtn:disabled { opacity: .6; cursor: not-allowed; }

/* The Donate CTA in the donations card (trade.php:315) opens that same modal, so
   it gets the amber treatment too -- otherwise the entry point and the confirm
   button would be different colours. Keyed on .js-open-donate for the same
   reason as above: the markup keeps .btn-charcoal for the base button style. */
.js-open-donate {
  background: linear-gradient(135deg, var(--amber-600), var(--amber-700));
  color: var(--on-gold); border-radius: var(--radius-xl); font-weight: 700;
  box-shadow: 0 4px 12px rgba(184,127,40,.3);
}
.js-open-donate:hover { background: linear-gradient(135deg, var(--amber-500), var(--amber-600)); }

.btn-yellow {
  background: var(--gold); color: var(--on-gold); border-radius: var(--radius-pill);
  padding: .65rem 1rem; font-weight: 600; box-shadow: 0 1px 3px rgba(0,0,0,.12);
}
.btn-yellow:hover { background: var(--gold-dark); }

/* ---------- Inputs ---------- */
.input {
  width: 100%; border: none; outline: none; font-family: inherit;
  border-radius: var(--radius-pill); padding: .65rem 1rem; font-size: 14px; color: var(--ink);
  transition: all .15s ease;
}
.input:focus { box-shadow: 0 0 0 2px var(--rose-ink); }

.input-blue { background: var(--blue-input-bg); border: 1px solid var(--blue-input-border); }
.input-blue::placeholder { color: var(--gray-500); }

.input-rose {
  background: var(--rose-bg); border: 1px solid var(--rose-border);
  border-radius: var(--radius-pill); color: var(--gray-600);
}
.input-rose::placeholder { color: var(--gray-500); }
.input-rose.invalid { border-color: var(--red-soft); }

.input-soft {
  background: var(--stone-50); border: 1px solid var(--stone-300); border-radius: var(--radius-lg);
  padding: .65rem .85rem; color: var(--stone-900); outline: none; font-family: inherit; font-size: 14px;
}
.input-soft:focus { border-color: var(--amber-500); box-shadow: 0 0 0 2px rgba(245,158,11,.2); }
.input-soft.invalid { border-color: var(--red-600); }

select.select-rose { background: var(--rose-bg); border: 1px solid var(--rose-border); }
select.input { appearance: none; }

.field-label {
  display: block; font-size: 12px; font-weight: 500; color: var(--gray-600); margin-bottom: .3rem;
}
.field-label.center { text-align: center; color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.4); }

.field-error { font-size: 12px; color: var(--red-600); margin-top: .3rem; padding: 0 .25rem; }
.field-error:empty { display: none; }
.field-hint { font-size: 11px; color: var(--gray-500); margin-top: .3rem; display: flex; align-items: center; gap: .4rem; }

/* ---------- Cards ---------- */
.card {
  background: #fff; border: 1px solid var(--stone-200); border-radius: var(--radius-2xl);
  padding: .9rem; box-shadow: 0 1px 2px rgba(0,0,0,.05);
}
.card-rose {
  background: linear-gradient(to right, var(--rose-bg), var(--rose-mid), var(--rose-bg));
  border: 1px solid var(--rose-border); border-radius: var(--radius-card);
  padding: .9rem; box-shadow: 0 1px 2px rgba(0,0,0,.05);
}
.section-head {
  display: flex; align-items: center; gap: .5rem;
  padding-bottom: .3rem; border-bottom: 1px solid var(--rose);
  margin-bottom: .6rem;
}
.section-head h3 { font-size: 14px; font-weight: 700; color: var(--brown); }
.section-head-plain {
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: 1px solid var(--stone-200); padding-bottom: .3rem; margin-bottom: .6rem;
}
.section-head-plain h3 { font-size: 14px; font-weight: 700; color: var(--gray-700); }

/* ---------- Header ---------- */
.header-rose {
  width: 100%;
  background: linear-gradient(to right, var(--rose-light), var(--rose), var(--rose-light));
  box-shadow: 0 1px 3px rgba(0,0,0,.08);
  /* Fixed at every size: the whole bar (logo row + ribbons) stays pinned to
     the top of the viewport while content scrolls under it. Sticky, not
     fixed, so it keeps its place in the flow and the content below needs no
     top offset. z-index stays under the bottom bar (50), modals (55) and the
     toast (60). */
  position: sticky; top: 0; z-index: 30;
}
.header-rose .header-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: .75rem 1rem;
}

/* Centred variant of the header row, used by contact.php. The default row is
   space-between with margin-left:auto on the right-hand item, which parks the
   logo immediately after the back arrow and leaves it visibly off-centre.

   Three equal columns (1fr auto 1fr) instead: the middle cell lands on the
   page's centre line no matter how wide the language menu renders on the right,
   which flexbox cannot guarantee because the two side items are unequal. Each
   side item is pinned to its own edge, and the medallion is centred *within*
   the middle cell so it stays on the line even though the title text under it
   is wider than the mark.

   Scoped to .header-rose so it outranks the two-class specificity of
   .header-rose .header-row above. */
.header-rose .header-row-center {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}
.header-row-center > :first-child { justify-self: start; }
.header-row-center > :last-child { justify-self: end; }
.header-center {
  display: flex; flex-direction: column; align-items: center; gap: .25rem;
  text-align: center; min-width: 0;
}

/* The compact brand badge (.logo-medallion, used in the small page headers).
   Sized by width --badge-w (180px default) at the logo's own aspect ratio
   (176:143). No border-radius clipping: the logo is already an oval emblem with
   a gold rim, so clipping was cutting its edge — the artwork now shows whole. */
.logo-medallion {
  width: var(--badge-w, 180px);
  aspect-ratio: 176 / 143;
}
.logo-medallion .inner {
  width: 100%; height: 100%;
  /* `contain` guarantees the whole emblem is always visible, never cropped.
     The URL comes from --site-logo, which includes/header.php sets from the
     uploaded logo; the old hardcoded ../img/logo.png no longer exists, which is
     why these headers were blank. */
  background: transparent var(--site-logo, url('../img/logo.png')) center / contain no-repeat;
}
.logo-medallion .inner .ylg,
.logo-medallion .inner .bullion { display: none; }
/* The wordmark is deliberately NOT the live site's display face. It was
   originally `font-family: serif` in weight 900 and var(--red); the restyle
   moved it to Cinzel + --gold-dark to match the headings, and that read as
   wrong next to the peach header, so it has been put back. The heading rule
   (h1-h6 use --font-display) is unaffected -- this is scoped to the medallion
   only, and `serif` is left unquoted deliberately: it is the original generic,
   not a named face, so it renders in whatever serif the OS has. */
.logo-medallion .ylg {
  font-family: serif; font-weight: 900; letter-spacing: .05em; color: var(--red); line-height: 1;
}
.logo-medallion .bullion {
  font-size: 4px; font-weight: 700; letter-spacing: -.02em; color: var(--ink-soft);
  text-transform: uppercase; line-height: 1.1; margin-top: 1px;
}
/* .sm / .xs used to shrink the box; the mark now renders at the one fixed oval
   size everywhere. The .ylg/.bullion tweaks below only matter for the hidden
   text fallback. */
.logo-medallion.xs .ylg { font-size: 11px; }
.logo-medallion.xs .bullion { font-size: 3.5px; }

/* The banner brand mark (login, register, trade, holdings, profile) is shown at
   a fixed width --logo-w (240px default) with the height following the image's
   own ratio. There is no border-radius clipping: the logo is already an oval
   emblem with a gold rim, so the ellipse was cutting its edge — now it shows
   whole, at a fixed size on every page. */
.site-logo {
  display: block;
  width: var(--logo-w, 240px);
  height: auto;
  max-width: 100%;
  margin: 0 auto;
}

/* Trade page brand header (the original app's layout): a slim rose band on
   top, then the logo BIG and centred on a white strip. The band overrides
   the base sticky positioning, so the tabs stay pinned without pinning a
   150px logo. */
.header-rose.header-rose-brand { position: relative; }
.brand-center {
  background: #fff; display: flex; justify-content: center;
  padding: .75rem 1rem;
}

/* Fused Buy|Sell pill, rebuilt to the prototype component: BOTH halves are
   diagonally clipped (buy keeps 87% at the seam, sell starts at 13% and
   overlaps by 1rem), flat #1a7e53 / #c93535, white bold text-only labels,
   rounded-2xl container with a hairline border. */
.split-cta {
  display: flex; height: 54px; margin: .5rem 0;
  border-radius: 1rem; overflow: hidden;
  border: 1px solid var(--stone-200); box-shadow: 0 4px 6px rgba(0,0,0,.1);
}
.split-cta > .btn {
  flex: 1 1 0; min-width: 0; border-radius: 0; box-shadow: none;
  font-size: 16px; font-weight: 700; color: #fff;
}
.split-cta > .btn-buy {
  background: #1a7e53;
  clip-path: polygon(0 0, 100% 0, 87% 100%, 0 100%);
}
.split-cta > .btn-buy:hover { background: #156e47; }
.split-cta > .btn-sell {
  background: #c93535;
  clip-path: polygon(13% 0, 100% 0, 100% 100%, 0 100%);
  margin-left: -1rem;
}
.split-cta > .btn-sell:hover { background: #b52a2a; }

/* ============================================================
   TRADE PAGE — prototype styling (trade.php carries .trade-page)
   Dark-red circle buttons with amber trim, VIP badge, tinted links
   pill, gold serif section titles with a double underline.
   ============================================================ */
.circle-btn {
  width: 32px; height: 32px; border-radius: 50%; flex: 0 0 auto;
  background: #8c382a; color: #fcd9a8; border: 1px solid #e8b04b;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.circle-btn:hover { background: #8c382a; filter: brightness(1.12); }

/* The language menu replaces the inline MY|EN|TH bar on the trade header.
   footer.php toggles .hidden on .lang-dropdown. */
.lang-menu { position: relative; padding: 0; background: none; backdrop-filter: none; border-radius: 0; }
.lang-menu .lang-toggle { font-family: inherit; }
.lang-dropdown {
  position: absolute; left: 0; top: calc(100% + 8px); width: 9.5rem; z-index: 60;
  background: #fff; border: 1px solid var(--stone-200); border-radius: var(--radius-xl);
  box-shadow: 0 12px 28px rgba(0,0,0,.18); padding: .25rem 0;
}
.lang-dropdown a {
  display: block; padding: .5rem .75rem; font-size: 12px;
  color: var(--stone-700); text-decoration: none;
}
.lang-dropdown a:hover { background: var(--stone-100); color: var(--stone-900); }
.lang-dropdown a.active { font-weight: 700; color: var(--gold-ink); background: #fdf3dd; }

/* Amber gradient tier pill beside the profile circle. */
.vip-badge {
  padding: .12rem .55rem; border-radius: var(--radius-pill);
  background: linear-gradient(90deg, #f59e0b, #b45309); color: #fff;
  font-size: 10px; font-weight: 800; border: 1px solid #fcd34d;
  box-shadow: 0 1px 2px rgba(0,0,0,.12);
}

/* Links pill rows, prototype tint. */
.trade-page .ribbon {
  background: #f2dfd6; border: 1px solid #e2c7ba; color: #4e271e;
  backdrop-filter: none;
}
.trade-page .ribbon-item:hover { color: #2d1a18; background: rgba(255,255,255,.55); }
.trade-page .ribbon-item.active { background: #fff; color: #2d1a18; }

/* Section titles: heavy serif, muted gold, double gold underline — the
   container's hairline border goes away so only the text is underlined. */
.trade-page .section-head,
.trade-page .section-head-plain { border-bottom: none; }
.trade-page .section-head h3,
.trade-page .section-head-plain h3 {
  font-family: var(--font-display); font-size: 20px; font-weight: 900;
  color: #96793b; letter-spacing: .02em;
  border-bottom: 3px double #96793b; padding-bottom: 2px;
}

.avatar-badge {
  width: 40px; height: 40px; border-radius: 50%; background: rgba(255,255,255,.92);
  box-shadow: 0 1px 3px rgba(0,0,0,.08); display: flex; align-items: center;
  justify-content: center; color: var(--brown); font-weight: 700; font-size: 13px;
  cursor: pointer; text-decoration: none; flex: 0 0 auto;
}
.avatar-badge:hover { background: #fff; }

.icon-badge {
  width: 36px; height: 36px; border-radius: 50%; background: rgba(255,255,255,.4);
  display: flex; align-items: center; justify-content: center; color: var(--brown);
}

/* ---------- Ribbon ----------
   The single strip of five mixed items overflowed the 28rem column once the
   labels were Burmese or Thai. Navigation and actions are now separate rows,
   and each scrolls horizontally rather than clipping. */

.ribbon {
  display: flex; align-items: center; gap: .15rem;
  background: rgba(255,255,255,.82); backdrop-filter: blur(6px);
  border-radius: var(--radius-pill); padding: .25rem .4rem; font-size: 12px; font-weight: 500; color: var(--brown);
  overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
  -webkit-overflow-scrolling: touch;
}
.ribbon::-webkit-scrollbar { display: none; }
.ribbon > * { flex: 0 0 auto; }

.ribbon-item {
  cursor: pointer; transition: color .15s, background .15s; background: none; border: none;
  font-family: inherit; font-size: 12px; color: inherit; white-space: nowrap;
  min-height: 36px; padding: 0 .6rem; border-radius: var(--radius-pill);
  display: inline-flex; align-items: center;
}
.ribbon-item:hover { color: var(--ink); background: rgba(255,255,255,.7); }
.ribbon-item.active { color: var(--ink); font-weight: 700; background: rgba(255,255,255,.95); box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.ribbon-item.pill { display: flex; align-items: center; gap: .2rem; padding: .2rem .55rem; border-radius: var(--radius-pill); }
.ribbon-item.pill.active { background: var(--rose); font-weight: 700; box-shadow: 0 1px 2px rgba(0,0,0,.1); }


/* ---------- Bottom navigation ----------
   Every destination is an "icon box": the glyph sits in a rounded tile that
   fills with the theme accent once the item is active. That reads as a
   selected button rather than a plain colour swap, which is easy to miss on a
   phone in daylight. Tile sizes are fixed so five items still fit a 320px
   screen without the row wrapping. */
.bottom-nav {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 50;
  background: rgba(255,255,255,.94);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-top: 1px solid var(--gray-100);
  box-shadow: 0 -6px 22px rgba(28,25,23,.09);
  /* Clears the home indicator on notched devices. */
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.bottom-nav .wrap {
  display: flex; justify-content: space-around; align-items: center;
  height: 66px; max-width: 28rem; margin: 0 auto; padding: 0 .35rem;
}
.nav-link {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; min-width: 56px; padding: 6px 4px; margin: 0;
  color: var(--gray-500); text-decoration: none; font-weight: 600;
  background: none; border: none; font-family: inherit; cursor: pointer;
  border-radius: 12px; -webkit-tap-highlight-color: transparent;
  transition: color .18s ease, transform .18s ease;
}
.nav-link:hover { color: var(--gray-900); }
.nav-link:active { transform: translateY(1px); }
/* The tile. It sits behind a translucent bar, so the resting fill is a flat
   grey rather than a tint -- a tint would show the page scrolling underneath. */
.nav-link .ico {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 30px; font-size: 21px; line-height: 1;
  border-radius: 11px; background: var(--gray-100);
  transition: background .18s ease, color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.nav-link .lbl { font-size: 10px; letter-spacing: .01em; }
.nav-link.active { color: var(--rose-ink); }
.nav-link.active .ico {
  background: var(--rose-ink); color: #fff; transform: translateY(-1px);
  box-shadow: 0 5px 12px rgba(163, 95, 69, .35);
}
.nav-link.active .lbl { font-weight: 700; }

/* Custom "pot of gold" glyph for the Holdings nav item. Drawn in the same
   outline stroke style as the Material Symbols neighbours, inheriting the
   tile colour (rose accent on active). */
.nav-svg-ico { width: 21px; height: 21px; display: block; }

/* ---------- Toast ---------- */
.toast {
  position: fixed; top: 1.25rem; left: 50%; transform: translateX(-50%) translateY(-140%);
  z-index: 60; background: var(--buy); color: #fff; padding: .6rem 1.25rem;
  border-radius: var(--radius-pill); box-shadow: 0 10px 20px rgba(0,0,0,.2);
  display: flex; align-items: center; gap: .4rem; font-size: 12px; font-weight: 600;
  transition: transform .25s ease; max-width: 90vw;
}
.toast.show { transform: translateX(-50%) translateY(0); }
.toast.info { background: var(--brown); }
.toast .ico { font-size: 18px; }

/* ---------- Modals ---------- */
.modal-overlay {
  position: fixed; inset: 0; z-index: 55; background: rgba(0,0,0,.55);
  display: flex; align-items: center; justify-content: center; padding: 1rem;
  animation: fadeIn .18s ease;
}
.modal {
  width: 100%; max-width: 24rem; background: #fff; border-radius: var(--radius-2xl);
  padding: 1.25rem; box-shadow: 0 20px 40px rgba(0,0,0,.25);
  animation: zoomIn .18s ease;
}

.modal-head { display: flex; align-items: center; justify-content: space-between; padding-bottom: .5rem; border-bottom: 1px solid var(--stone-200); margin-bottom: .9rem; gap: .5rem; }
.modal-head h4 { font-size: 16px; font-weight: 700; color: var(--gray-800); }
/* 44px hit area around a 20px glyph. */
.modal-close {
  background: none; border: none; color: var(--gray-500); cursor: pointer;
  width: 44px; height: 44px; margin: -10px -10px -10px 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; flex: 0 0 auto;
}
.modal-close:hover { color: var(--gray-700); background: var(--gray-100); }

/* Order ticket modal. The screen the buy/sell style is really about, so it gets
   the prototype's treatment: edge-to-edge gradient header bar carrying the
   direction, square-ish corners, hairline stone border.

   This block MUST stay after the generic .modal-head / .modal-close rules
   above. Those are single-class selectors, so they tie on specificity and only
   lose by source order -- placing this first let them silently restore the
   margin-bottom and border that break the full-bleed bar.

   There is no `is-buy`/`is-sell` class on the overlay itself -- app.js:528-530
   only sets them on #orderModalType and #modalPrice -- so the bar colour is
   driven off the ticket badge with :has(). Where :has() is unsupported
   (Chrome <105, Safari <15.4, Firefox <121) the header falls back to the dark
   stone bar below; the ticket still opens and prices exactly as before. */
#orderModal .modal {
  padding: 0; overflow: hidden;
  border: 1px solid var(--stone-200);
  box-shadow: 0 25px 50px rgba(28,25,23,.28);
}
#orderModal .modal-head {
  margin: 0; padding: .9rem 1.25rem;
  border-bottom: none;
  background: var(--stone-900); color: #fff;
}
#orderModal .modal-head h4 { color: #fff; }
#orderModal .modal-close { color: rgba(255,255,255,.75); margin: -10px -12px -10px 0; }
#orderModal .modal-close:hover { color: #fff; background: rgba(255,255,255,.15); }
/* Body content needs the padding back -- the modal's own is zeroed so the
   header bar can run edge to edge. */
#orderModal .modal > .stack { padding: 1.1rem 1.25rem 1.25rem; }

#orderModal:has(.order-ticket.is-buy) .modal-head {
  background: linear-gradient(90deg, var(--emerald-700), var(--emerald-600) 45%, var(--emerald-700));
}
#orderModal:has(.order-ticket.is-sell) .modal-head {
  background: linear-gradient(90deg, var(--red-700), var(--red-600) 45%, var(--red-700));
}

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes zoomIn { from { opacity: 0; transform: scale(.95); } to { opacity: 1; transform: scale(1); } }

/* ---------- Chips / pills ----------
   min-height keeps every chip at a 44px tap target. At the previous .4rem
   padding the rendered height was ~26px, which is well under the 44px
   minimum and made the amount and cycle chips fiddly to hit one-handed. */
.chip {
  border: 1px solid var(--stone-200); cursor: pointer; font-family: inherit; border-radius: var(--radius-md);
  padding: .45rem .7rem; font-size: 12px; background: var(--stone-100); color: var(--stone-700);
  transition: all .15s; min-height: 44px; display: inline-flex;
  align-items: center; justify-content: center;
}
.chip:hover { background: var(--stone-200); }
.chip.active {
  background: var(--amber-500); color: var(--stone-900);
  font-weight: 700; border-color: var(--amber-500); box-shadow: 0 1px 2px rgba(0,0,0,.1);
}
.chip.rose.active { background: var(--rose); color: var(--brown); }

.tab-pill {
  border: none; background: var(--pill-idle); color: var(--gray-500); border-radius: var(--radius-pill);
  padding: .5rem 1.25rem; font-size: 12px; font-weight: 500; cursor: pointer; font-family: inherit;
}
.tab-pill.active { background: var(--rose); color: var(--brown); box-shadow: 0 1px 2px rgba(0,0,0,.1); }

/* ---------- Layout helpers ---------- */
.main-content { flex: 1; width: 100%; margin: 0 auto; padding: .75rem 1rem; }
.stack { display: flex; flex-direction: column; gap: .75rem; }
.row { display: flex; align-items: center; }
.between { justify-content: space-between; }
.pb-20 { padding-bottom: 5rem; }

.text-gold { color: var(--gold); }
.font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
.font-bold { font-weight: 700; }
.bg-surface { background: var(--surface); }
.bg-cream  { background: var(--cream); }

/* ============================================================
   SIGN-IN / AUTH
   ============================================================ */
.auth-screen { min-height: 100vh; display: flex; flex-direction: column; position: relative; }
/* On the sign-in page the body must not paint an opaque background: .auth-bg is
   a fixed z-index:-1 layer, and a negative-z-index descendant paints *behind*
   an ancestor's in-flow background (html,body{background:var(--surface)} plus
   .bg-cream would otherwise hide the photo entirely). Keeping the body
   transparent lets the root canvas sit behind, and the photo above it. */
body.auth-body { background: transparent; }
/* Full-page photo behind the sign-in screen. login.php renders this layer and
   can override the image inline with an admin-uploaded login_bg; the bundled
   default is assets/img/login-bg.webp. Fixed positioning keeps the photo still
   while the form scrolls. */
.auth-bg {
  position: fixed; inset: 0; z-index: -1; background-size: cover; background-position: center;
  background-image: url('../img/login-bg.webp');
}
.auth-bg::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(0,0,0,.35), rgba(0,0,0,.2) 40%, rgba(0,0,0,.55));
}
/* login.php shows one continuous full-page photo, so the banner drops its own
   photo and shadow and lets the fixed .auth-bg show through behind the logo. */
.auth-login .reg-banner { box-shadow: none; }
.auth-login .reg-banner .bg { background-image: none; }
.auth-login .reg-banner .bg::after { display: none; }
.auth-top { padding: .5rem 1rem; display: flex; justify-content: flex-start; }

.lang-switch {
  display: flex; align-items: center; gap: .5rem; padding: .25rem .75rem;
  border-radius: var(--radius-pill); background: rgba(0,0,0,.25); backdrop-filter: blur(8px);
  color: #fff; font-size: 12px; font-weight: 600;
}
.lang-switch a { background: none; border: none; color: rgba(255,255,255,.8); text-decoration: none; cursor: pointer; font-family: inherit; font-size: 12px; font-weight: 600; }
.lang-switch a:hover { color: #fff; }
.lang-switch a.active { color: var(--gold-soft); font-weight: 700; }
.lang-switch .sep { color: rgba(255,255,255,.4); }

/* Open/close ("collapsible") language bar: a globe-icon-only toggle pill sits
   on top, and the full MY|EN|TH|MS|AR list drops below it when opened. The
   toggle reuses .js-disclosure, so no extra JS is needed. Both pieces are
   sized up from the base .lang-switch so the bar reads as a fixed control. */
.lang-collapsible { display: flex; flex-direction: column; align-items: flex-end; gap: .45rem; }
.lang-collapsible .js-disclosure { cursor: pointer; user-select: none; width: auto; }
.lang-collapsible .lang-toggle { padding: .55rem .65rem; }
.lang-collapsible .lang-toggle .material-symbols-outlined { font-size: 24px; }
/* The opened list stacks vertically, one language per line; the "|"
   separators only make sense in the horizontal bar, so they are hidden. */
.lang-collapsible .lang-menu { flex-direction: column; align-items: flex-start; gap: .45rem; padding: .6rem 1.1rem; }
.lang-collapsible .lang-menu a { font-size: 15px; }
.lang-collapsible .lang-menu .sep { display: none; }
/* trade.php + holdings.php + history.php park the language pill on the left of
   the banner, so their dropdown menus are anchored to the left edge instead of
   opening off-screen leftwards. */
.trade-page .lang-collapsible,
.holdings-ref .lang-collapsible,
.history-page .lang-collapsible { align-items: flex-start; }

.lang-switch.light { background: rgba(255,255,255,.65); backdrop-filter: blur(6px); }
.lang-switch.light a { color: rgba(74,46,43,.75); }
.lang-switch.light a:hover { color: var(--brown); }
.lang-switch.light a.active { color: var(--gold-dark); font-weight: 700; }
.lang-switch.compact { padding: .15rem .55rem; gap: .35rem; }
.lang-switch.compact a { font-size: 10px; }

.auth-center { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 1rem 1.5rem; width: 100%; max-width: 24rem; margin: 0 auto; z-index: 1; }
.auth-form { width: 100%; display: flex; flex-direction: column; gap: 1rem; }

.check-row { display: flex; align-items: center; justify-content: space-between; font-size: 12px; padding: .25rem .5rem; }
.check-row .remember { display: flex; align-items: center; gap: .4rem; color: rgba(255,255,255,.92); cursor: pointer; }
.check-row input[type=checkbox] { accent-color: var(--rose); width: 14px; height: 14px; cursor: pointer; }
.check-row a { color: var(--link-on-dark); text-decoration: none; }
.check-row a:hover { text-decoration: underline; }

.auth-footer { margin-top: 2rem; text-align: center; color: rgba(255,255,255,.7); font-size: 12px; }

/* "Contact support" link shared by login.php (dark photo) and register.php
   (cream). The dark variant uses the same light blue as the other auth links;
   .auth-support-light swaps it for the gold ink so it reads on a light page. */
.auth-support { margin: .5rem 0 0; text-align: center; font-size: 12px; }
.auth-support a { color: var(--link-on-dark); font-weight: 600; text-decoration: none; }
.auth-support a:hover { text-decoration: underline; }
.auth-support-light { margin-top: 1.25rem; }
.auth-support-light a { color: var(--gold-ink); }

/* login.php pins the link to the top-left of the screen instead of the
   centered footer, so it sits outside the sign-in card. */
.auth-support.auth-support-corner {
  position: fixed; left: 1rem; top: 1.1rem;
  margin: 0; text-align: left; z-index: 3;
}
.auth-support.auth-support-corner a { display: inline-flex; align-items: center; gap: .3rem; }
.auth-support.auth-support-corner .material-symbols-outlined {
  font-size: 16px; font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 20;
}
@media (max-width: 480px) {
  .auth-support.auth-support-corner { left: .75rem; top: 1rem; }
}

.auth-error {
  background: rgba(220, 38, 38, .18); border: 1px solid rgba(248, 113, 113, .5);
  color: #fff; font-size: 12px; text-align: center; padding: .55rem .8rem; border-radius: var(--radius-xl);
}

.auth-success {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  background: rgba(27, 135, 84, .95); border: 2px solid rgba(167, 243, 208, .9);
  color: #fff; font-size: 13px; font-weight: 700; text-align: center;
  padding: .75rem 1rem; border-radius: var(--radius-xl); margin-bottom: .85rem;
  box-shadow: 0 6px 18px rgba(0,0,0,.35);
}

.auth-success-fixed {
  position: fixed; top: .6rem; left: 50%; transform: translateX(-50%);
  z-index: 60; width: min(94%, 460px); margin: 0;
  animation: auth-banner-in .35s ease both;
}
@keyframes auth-banner-in {
  from { opacity: 0; transform: translate(-50%, -16px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

/* ============================================================
   REGISTER
   ============================================================ */
/* The logo is an oval sized by width (default 240px) with the height following
   the image's own ratio (~195px tall at that width), so the banner must be tall
   enough to show it whole (bottom:8px + 195px) without `overflow:hidden`
   clipping the top of the mark. */
.reg-banner { position: relative; width: 100%; height: 220px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,.15); }
.reg-banner .bg {
  position: absolute; inset: 0; background-size: cover; background-position: center;
  background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuD4c-apUExIkV9YAJlAoPI8P8RFHkxWDWwGhUEIL4v_0XOd2X55C5HIeHs1Yz071CE-1yfo7FzjnEztuclLuEBO7NDN3sEZs_nYOuI7EWtsW6xKJrFoNfV8O9C9Jfb_NjIEEUFYFuFpaHy6lODAzrEr0IhLy6sbC09EZMKt0r9gs_P_NkuFOXKTc0ZIZkmxJ1zz7eNnfyMFmoaxr44NDAmp8S6KKk18dJ7hwc7p0wFYQ2OkpoVmG9CFdQ');
}
.reg-banner .bg::after { content: ''; position: absolute; inset: 0; background: rgba(0,0,0,.25); }
.reg-banner .back {
  position: relative; z-index: 2; padding: .9rem 1rem; display: flex; align-items: center;
}
.btn-round {
  width: 40px; height: 40px; border-radius: 50%; border: none; background: transparent;
  color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.btn-round:hover { background: rgba(0,0,0,.2); }
.reg-banner .medallion-wrap { position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%); z-index: 2; }

.reg-steps { display: flex; align-items: center; justify-content: center; gap: .75rem; margin-bottom: 1.5rem; }
.success-banner {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  background: var(--ok-bg); border: 1px solid var(--ok-border); color: var(--ok-ink);
  font-size: 12px; font-weight: 500; text-align: center; padding: .75rem; border-radius: var(--radius-xl);
  margin-bottom: 1rem;
}

/* ============================================================
   TRADE PAGE
   ============================================================ */

/* Promo: the copy column was pinned to 65% and set at 12px/10px, so the
   heading was smaller than the tag above it and the sentence wrapped to five
   cramped lines. Flexible column, real type scale. */
.promo-card { position: relative; overflow: hidden; background: #fff; border-radius: var(--radius-card); box-shadow: 0 1px 2px rgba(0,0,0,.05); border: 1px solid var(--stone-200); padding: .85rem; }
.promo-text { flex: 1; min-width: 0; }
.promo-title { font-size: 14px; font-weight: 700; color: var(--gray-900); margin-top: .4rem; line-height: 1.4; }
.promo-desc { font-size: 12px; color: var(--gray-500); margin-top: .2rem; line-height: 1.45; }
/* The description is a Google Maps short link: rendered inside .promo-desc it
   would otherwise fall back to the browser's default link blue on a white
   card, which is off-palette and unreadable under the dark theme. Gold-ink is
   the theme-aware TEXT gold (rose and blue themes redefine it), and the URL is
   allowed to break mid-string so a long link never widens the card. */
.promo-desc a { color: var(--gold-ink); font-weight: 600; text-decoration: none; word-break: break-all; }
.promo-desc a:hover { text-decoration: underline; }
.promo-art { width: 72px; height: 60px; flex: 0 0 auto; position: relative; }
.promo-art img { width: 100%; height: 100%; object-fit: contain; }
.promo-tag { display: inline-flex; align-items: center; gap: .25rem; padding: .15rem .5rem; border-radius: var(--radius-pill); background: var(--amber-100); color: var(--amber-800); font-size: 10px; font-weight: 700; }

.announce-bar { display: flex; align-items: center; gap: .5rem; background: #fff; padding: .6rem .75rem; border-radius: var(--radius-lg); border: 1px solid var(--stone-200); box-shadow: 0 1px 2px rgba(0,0,0,.05); }
.announce-bar p { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--gray-600); }

/* Advertising strip. Same shape as .announce-bar but tinted gold so it reads as
   marketing copy rather than a service notice. The text WRAPS (unlike the
   announcement's single clipped line) because the admin writes it and it may
   run long; the icon stays put. */
.ad-bar { display: flex; align-items: center; gap: .5rem; background: var(--amber-100); padding: .6rem .75rem; border-radius: var(--radius-lg); border: 1px solid var(--warn-border); box-shadow: 0 1px 2px rgba(0,0,0,.05); }
.ad-bar .material-symbols-outlined { color: var(--gold-dark); }

/* Marquee. The track holds two identical copies of the ad line side by side;
   sliding by -50% brings the second copy exactly where the first began, so the
   scroll loops seamlessly. The duration is set inline from the text length. */
.ad-marquee { flex: 1; min-width: 0; overflow: hidden; }
.ad-marquee-track { display: inline-flex; white-space: nowrap; will-change: transform; animation: ad-scroll 20s linear infinite; }
.ad-half { display: inline-flex; align-items: center; gap: 1.5rem; padding-right: 1.5rem; }
.ad-seg { font-size: 12px; font-weight: 600; color: var(--amber-800); }
.ad-sep { color: var(--gold-dark); opacity: .6; }
@keyframes ad-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .ad-marquee-track { animation: none; } }

/* Live TradingView chart embed. The frame is width:100% inside a column capped
   at 28rem, so width is already handled — height is the only thing worth
   switching. 560px is the base measure; the tablet step in the responsive shell
   grows it to 620px, and short landscape phones pull it back to 200px.

   This rule is the single source of truth for the height: the advanced-chart
   widget wants a pixel height in its config, and app.js passes the frame's
   measured height rather than hardcoding 380 in two places. */
.tv-frame { position: relative; width: 100%; height: 560px; border: 1px solid var(--stone-200); border-radius: var(--radius-md); overflow: hidden; background: #fff; }
.tv-frame iframe { display: block; width: 100%; height: 100%; border: 0; }

/* Two embeds share the frame: the advanced-chart script (primary) injects its
   own iframe into .tv-widget-host, and .tv-fallback is our own iframe for when
   that script is blocked. Exactly one is mounted at a time. These rules target
   .tv-fallback by class rather than with a bare [hidden], because a descendant
   `iframe { display: none }` would also hide the iframe TradingView injects. */
.tv-frame.is-script .tv-fallback { display: none; }
.tv-frame.is-iframe .tv-widget-host { display: none; }
.tv-widget-host { width: 100%; height: 100%; }

/* The embed is cross-origin, so there is no load signal from the chart itself.
   A skeleton over the frame means "still loading" instead of "empty box". */
.chart-skeleton {
  position: absolute; inset: 0; z-index: 2; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: .5rem;
  background: linear-gradient(100deg, var(--shimmer-from) 30%, var(--shimmer-to) 50%, var(--shimmer-from) 70%);
  background-size: 220% 100%; animation: chart-shimmer 1.4s linear infinite;
  font-size: 12px; color: var(--gray-500);
}
@keyframes chart-shimmer { from { background-position: 140% 0; } to { background-position: -40% 0; } }
.spin { animation: chart-spin 1.6s linear infinite; }
@keyframes chart-spin { to { transform: rotate(360deg); } }

/* The feed strip that stood here (instrument name on the left, pulsing live dot
   on the right) has been removed; its instrument name is shown once in the
   chart card head by .chart-symbol. */
@media (prefers-reduced-motion: reduce) {
  .chart-skeleton { animation: none; }
  .spin { animation: none; }
}

/* The quote row that stood here is gone. It held the instrument ticker, the
   Live/Delayed badge and the 24h change percentage, so these rules went with it:
     .quote-row / .quote-meta / .quote-label / .chart-change / .live-badge
   The #chartUpdated stamp under the chart (styled by .chart-stamp) has since
   been removed as well, so that rule is gone with it.

   .market-up / .market-down further down are kept even though their last two
   callers are gone too, because restoring the market row needs them. */

/* Instrument ticker badge in the chart card head (e.g. XAUUSD). Pushed to the
   right of the title by margin-left:auto, since .section-head is a flex row. */
.chart-symbol {
  margin-left: auto; flex: 0 0 auto;
  font-size: 12px; font-weight: 800; letter-spacing: .06em;
  color: #96793b; background: rgba(214, 180, 90, .14);
  border: 1px solid #e6d9b8; border-radius: 999px;
  padding: .15rem .55rem; white-space: nowrap;
}

.donor-scroll {
  max-height: 16rem;
  overflow: hidden;
  position: relative;
  border-radius: var(--radius-lg);
  border: 1px solid var(--gray-100);
  background: #fff;
}

.donor-scroll::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2.5rem;
  pointer-events: none;
  background: linear-gradient(to top, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
}

.donor-scroll::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 2.5rem;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
  z-index: 1;
}

.donor-scroll-track {
  display: flex;
  flex-direction: column;
  will-change: transform;
  animation: donor-marquee 24s linear infinite;
}

/* The track is duplicated: one full set of rows followed by the same full set.
   Height is twice the original block; moving by -50% makes the second copy slide
   up into the viewport exactly as the first leaves, creating an infinite loop. */
@keyframes donor-marquee {
  0% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(-50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .donor-scroll-track {
    animation: none;
    transform: none;
  }
}
/* Table base. Every .table-head/.table-row across the member/public pages sets
   its own inline grid-template-columns; these shared rules are what make them
   actual grid rows. The cells are <span>s, so without display:grid they fell
   back to loose inline text and the columns drifted/action cells detached. */
.table { display: flex; flex-direction: column; }
.table-head,
.table-row {
  display: grid;
  align-items: center;
  gap: .35rem;
  border-bottom: 1px solid var(--stone-100);
}
.table-row.alt { background: var(--stone-50); }
.table-row:last-child { border-bottom: 0; }
/* Grand-total footer on the Charity Donations table. Sits after the last data
   row, so .table-row:last-child already drops its border -- this just needs a
   heavier top edge and bolder text to read as a summary, not another donor. */
.table-row.total-row {
  background: var(--stone-50);
  border-top: 2px solid var(--stone-300);
  font-weight: 700;
  color: var(--stone-900);
}
.table-row.total-row .cell { padding-top: .6rem; padding-bottom: .6rem; }
/* The member's own tier, so the table answers "which row is mine?". */
.table-row.is-current { background: var(--rose-bg); box-shadow: inset 3px 0 0 var(--rose); }
.tier-flag {
  display: inline-block; margin-left: .35rem; padding: .05rem .35rem; border-radius: var(--radius-pill);
  background: var(--rose); color: var(--brown); font-size: 9px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .02em; vertical-align: middle;
}
.cell { padding: .55rem .5rem; }
.cell.center { text-align: center; }
.cell.right { text-align: right; }

/* trade.php Financial Products card: the whole table reads larger (per the size
   request); financial.php keeps its own size. The amount column gets the extra
   room and its figures turn bold so the wider package prices still fit at the
   larger size without wrapping. */
#productsCard .table { font-size: 17px; }
#productsCard .table-head .cell,
#productsCard .table-row .cell { padding: .7rem .3rem; }
#productsCard .table-head .cell { font-weight: 600; color: var(--gray-600); }
#productsCard .table-row .cell.font-mono { font-weight: 700; color: var(--gray-900); }
#productsCard .tier-flag { font-size: 10px; padding: .08rem .4rem; }
#productsCard .table .cell a.text-link { font-size: 13px; font-weight: 700; }
@media (max-width: 360px) {
  #productsCard .table { font-size: 16px; }
  #productsCard .table-head .cell,
  #productsCard .table-row .cell { padding: .62rem .2rem; }
}

.market-quote { display: flex; flex-direction: column; align-items: flex-end; flex: 0 0 auto; text-align: right; }
.market-icon { width: 36px; height: 36px; border-radius: var(--radius-lg); background: var(--rose-bg); display: flex; align-items: center; justify-content: center; color: var(--gold-dark); flex: 0 0 auto; }
.market-icon.is-in { background: var(--ok-bg); color: var(--emerald-700); }
.market-up { color: var(--emerald-600); }
.market-down { color: var(--sell); }

/* History rows */
.history-row { display: flex; align-items: center; justify-content: space-between; gap: .5rem; background: #fff; border-radius: var(--radius-card); border: 1px solid var(--stone-200); box-shadow: 0 1px 2px rgba(0,0,0,.05); padding: .75rem; }
.status-pill { font-size: 11px; padding: .1rem .45rem; border-radius: var(--radius-pill); background: var(--ok-bg); color: var(--emerald-700); font-weight: 600; display: inline-block; margin-top: 2px; }
.status-pending { background: var(--warn-bg); color: var(--warn-ink); }
.status-approved { background: var(--ok-bg); color: var(--emerald-700); }
.status-rejected { background: var(--bad-bg); color: var(--red-700); }

/* Support inbox (contact.php). The .pill-* classes live in the admin
   stylesheet, so the member side needs its own status chip. */
.ct-pill { font-size: 11px; padding: .18rem .55rem; border-radius: var(--radius-pill); font-weight: 600; display: inline-block; white-space: nowrap; }
.ct-pill-open   { background: var(--ok-bg); color: var(--emerald-700); }
.ct-pill-closed { background: var(--gray-100); color: var(--gray-500); }

/* Message bubbles in the support thread. The member's own messages sit on the
   right in the app's green, support's on the left in grey — the convention every
   chat client uses, which is what makes a two-sided conversation readable at a
   glance. The sender line follows the side of its bubble; the sender is
   no longer repeated inside the bubble itself. Both contact.php and support.php
   show only the sender — the per-message timestamp was replaced by the day
   divider below. */
.msg-row { display: flex; flex-direction: column; align-items: flex-start; max-width: 100%; }

/* ?id=N is only an anchor now that every conversation renders on the page.
   Reserve room for the sticky header so the target thread is not parked
   underneath it. */
.ct-thread { scroll-margin-top: 5.5rem; }
.ct-thread:target { border-color: var(--gold-soft); box-shadow: 0 0 0 3px var(--gold-soft); }
.msg-row-mine { align-items: flex-end; }

.msg-meta { display: flex; align-items: baseline; gap: .45rem; margin-bottom: .25rem; }
.msg-name { font-size: 12px; font-weight: 700; color: #111827; margin: 0; }

/* Day divider (contact.php and support.php): one label above the first message
   of each day, standing in for the per-message timestamp that was dropped. */
.msg-day { display: flex; justify-content: center; }
.msg-day span {
  font-size: 11px; font-weight: 600; color: var(--gray-500);
  background: var(--gray-100); border-radius: var(--radius-pill);
  padding: .2rem .7rem;
}

.msg-bubble {
  max-width: min(32rem, 80%);
  padding: .6rem .85rem;
  border-radius: 14px;
  font-size: 13px;
  line-height: 1.5;
  word-break: break-word;
}
/* One corner stays square on the side the bubble is anchored to, which is what
   makes the pair read as speech rather than as two stacked cards. */
.msg-row-mine .msg-bubble {
  background: var(--ok-bg);
  border: 1px solid #a7f3d0;
  color: var(--emerald-700);
  border-bottom-right-radius: 4px;
}
.msg-row-theirs .msg-bubble {
  background: var(--gray-100);
  border: 1px solid var(--stone-200);
  color: var(--gray-900);
  border-bottom-left-radius: 4px;
}

/* ============================================================
   CONTACT SUPPORT — full-screen conversation (contact.php)
   The member inbox used to be a 44rem column pinned to the top of a tall page,
   so on a wide screen the support box floated in cream with dead space above
   the bottom bar. contact.php now carries .contact-page, which sizes the whole
   page to the viewport: the shell is a min-height:100vh flex column, the
   conversation claims every pixel between the sticky header and the fixed
   bottom bar, and only the message list scrolls while the composer stays
   pinned to the bottom of the box.

   Scoped to .contact-page because .gold-page (holdings.php) and the shared
   .ct-grid / .ct-messages markup on support.php must keep their own layout. */
.contact-page {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 100vh;
}

/* Full width: drop the app's 74rem desktop measure (.main-content) and the
   44rem chat measure (.ct-grid), and re-stack the card column that the 1180px
   rule turns into a grid. The desktop 2.5rem padding-bottom on .main-content
   would lift the box back off the bottom bar, so it drops to the phone value;
   .pb-20 on the shell already clears the bar. */
.contact-page .ct-grid {
  max-width: none;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding-bottom: .75rem;
}

/* The 1180px rule sets align-items:start, which would stop the conversation
   from filling its column. */
.contact-page .ct-grid > * { align-self: stretch; }

/* The last conversation soaks up the leftover height so nothing is left blank
   under it. With one thread — or the empty state — that is the whole box; with
   several, the earlier cards keep their content height and only the final card
   grows, which stops a long inbox from being split into unreadably short
   strips. */
.contact-page .ct-grid > .ct-thread:last-child { flex: 1 1 auto; }

/* Message history: grows to fill its card and scrolls inside it, so the
   composer stays visible at the bottom edge. */
.contact-page .ct-messages {
  margin-top: .6rem;
  display: flex;
  flex-direction: column;
  gap: .75rem;
  flex: 1 1 auto;
  min-height: 7rem;
  overflow-y: auto;
  padding: .4rem .2rem;
}

/* Every card except the stretching last one keeps the phone-sized history cap,
   so a long inbox stays a list of tidy cards rather than a wall of bubbles. */
.contact-page .ct-grid > .ct-thread:not(:last-child) .ct-messages { max-height: 55vh; }

/* Plain message view: the coloured speech bubbles are dropped, so a message is
   bare text on the card — no fill, no border, no tinted type. The sides stay
   (member right, support left) because that is the only thing telling the two
   speakers apart once the bubble colour is gone; the square corner and the
   in-bubble padding therefore go away with the bubble. Scoped to .contact-page
   so support.php keeps the bubble treatment. */
.contact-page .msg-row-mine .msg-bubble,
.contact-page .msg-row-theirs .msg-bubble {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  color: var(--gray-900);
}

/* No conversations yet: the hint takes the free space, which pushes the
   composer to the bottom of the full-height box instead of leaving it stranded
   under the text. `first-child` keeps the closed-thread notice (also an
   .empty-hint, but not the first child) out of this rule. */
.contact-page .ct-thread > .empty-hint:first-child {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Shared small list/label primitives used across the trade panels. */
.list-lead { font-size: 12px; font-weight: 700; text-transform: capitalize; }
.list-lead.is-in { color: var(--emerald-700); }
.list-amount { font-size: 12px; color: var(--gray-900); font-weight: 600; }
.list-sub { font-size: 11px; color: var(--gray-500); margin: 0; }
.stat-label { font-size: 12px; color: var(--gray-500); }
.stat-value { font-size: 15px; font-weight: 700; color: var(--gray-900); }
.empty-hint { font-size: 12px; color: var(--gray-500); text-align: center; padding: .75rem .5rem; margin: 0; }
.text-link { font-size: 12px; color: var(--rose-ink); text-decoration: none; font-weight: 600; }
.text-link:hover { text-decoration: underline; }
.note-block {
  background: var(--amber-100); border: 1px solid var(--warn-border); border-radius: var(--radius-lg);
  padding: .65rem .75rem; font-size: 12px; color: var(--amber-800); margin: 0; line-height: 1.5;
}
.balance-hint { font-size: 11px; color: var(--gray-500); }

/* One live position at a time: the countdown shown inside the order ticket while
   a pending order runs. Same amber "attention" surface as .note-block, tightened
   up because it sits inside the modal. */
.ticket-lock-note {
  display: flex; align-items: center; gap: .4rem; margin: 0;
  padding: .55rem .7rem; border-radius: var(--radius-lg);
  background: var(--amber-100); border: 1px solid var(--warn-border);
  font-size: 12px; color: var(--amber-800); line-height: 1.4;
}
/* The clock sits in a circle: a round badge rather than a bare glyph, so the
   icon reads as a dial next to the countdown. */
.ticket-lock-note .material-symbols-outlined {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; flex-shrink: 0; border-radius: 50%;
  background: #fff; border: 1px solid var(--warn-border);
  font-size: 15px; color: var(--amber-800);
}

/* Settled outcome, printed inside the ticket where the countdown sat: green on a
   win, red on a lose, so the end of a cycle is shown without leaving the
   Buy/Sell button. Mirrors the history page's win/lose colours. */
.ticket-lock-note.is-win { background: #d9f2e4; border-color: #a7ddc2; color: #1b6b4a; }
.ticket-lock-note.is-lose { background: #fbdcdc; border-color: #f0b4b4; color: #a11616; }

/* Full-screen countdown for the member's one live position (trade.php
   #ticketCountdownScreen). It covers the whole viewport -- above the modals,
   which sit at z-index 55 -- for the duration of the cycle: a dark stage with a
   circular clock, the label, and the seconds at full-screen size. app.js paints
   .js-ticket-count here and settles the ticket in place when it reaches zero. */
.ticket-countdown-screen {
  position: fixed; inset: 0; z-index: 95;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .85rem; padding: 2rem 1.25rem;
  background: rgba(6, 6, 6, .95);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  color: #fff; text-align: center;
}
.ticket-countdown-screen .tcs-clock {
  display: inline-flex; align-items: center; justify-content: center;
  width: 92px; height: 92px; border-radius: 50%;
  background: rgba(255, 255, 255, .05); border: 2px solid var(--gold);
  font-size: 44px; color: var(--gold);
}
.ticket-countdown-screen .tcs-label {
  font-size: 13px; letter-spacing: .16em; text-transform: uppercase;
  color: rgba(255, 255, 255, .6);
}
.ticket-countdown-screen .tcs-count {
  font-size: clamp(4rem, 22vw, 9rem); font-weight: 800; line-height: 1;
  letter-spacing: -.02em; color: var(--gold);
  text-shadow: 0 0 28px rgba(255, 175, 26, .35);
}
/* The overlay owns the viewport, so the page behind it must not scroll. */
body.ticket-lock-screen-open { overflow: hidden; }

/* Settled outcome on the full-screen stage: green on a win, red on a lose, with
   the result and a Continue button in place of the countdown. The stage keeps
   its full-screen shape so the end of the cycle lands where the countdown was. */
.ticket-countdown-screen.is-win  { background: rgba(4, 26, 17, .96); }
.ticket-countdown-screen.is-lose { background: rgba(28, 6, 6, .96); }
.ticket-countdown-screen.is-win  .tcs-clock { border-color: #10b981; color: #10b981; background: rgba(16, 185, 129, .08); }
.ticket-countdown-screen.is-lose .tcs-clock { border-color: #ef4444; color: #ef4444; background: rgba(239, 68, 68, .08); }
.ticket-countdown-screen.is-win  .tcs-outcome { color: #6ee7b7; }
.ticket-countdown-screen.is-lose .tcs-outcome { color: #fca5a5; }
.ticket-countdown-screen .tcs-result {
  font-size: clamp(1.5rem, 7vw, 2.75rem); font-weight: 800; line-height: 1.15;
  letter-spacing: -.01em; color: #fff; max-width: 22ch;
}
.ticket-countdown-screen .tcs-continue { margin-top: .75rem; min-width: 12rem; }

/* Order ticket badge: the direction of the ticket, and the price colour has to
   follow it. A buy ticket showing a red quote reads as a price move. */
.order-ticket {
  display: inline-flex; align-items: center; padding: .2rem .55rem; border-radius: var(--radius-pill);
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
}
.order-ticket.is-buy { background: var(--ok-bg); color: var(--emerald-700); }
.order-ticket.is-sell { background: var(--bad-bg); color: var(--red-700); }

/* Inside the order modal the badge sits on the dark gradient bar, so the tinted
   pill loses its edge. Give it a translucent light chip that reads on any of
   the header colours, in both the :has() and the no-:has() fallback case. */
#orderModal .order-ticket {
  background: rgba(255,255,255,.2); color: #fff;
  border: 1px solid rgba(255,255,255,.35);
}
.modal-price { font-size: 15px; font-weight: 700; }
.modal-price.is-buy { color: var(--emerald-700); }
.modal-price.is-sell { color: var(--red-700); }


/* Support chat FAB */
.chat-fab {
  position: fixed; bottom: 84px; right: 1rem; z-index: 40; width: 48px; height: 48px;
  border-radius: 50%; border: none; background: var(--support); color: #fff; cursor: pointer;
  box-shadow: 0 10px 20px rgba(0,0,0,.25); display: flex; align-items: center; justify-content: center;
  transition: transform .1s;
}
.chat-fab:active { transform: scale(.95); }
.msg { background: var(--gray-50); border-radius: var(--radius-lg); padding: .5rem .75rem; font-size: 12px; color: var(--gray-700); max-width: 85%; line-height: 1.5; }
.msg.mine { background: var(--rose-bg); color: var(--brown); align-self: flex-end; }

/* ============================================================
   HOLDINGS PAGE
   ============================================================ */
.balance-card .balance-text { font-size: 26px; font-weight: 800; font-family: ui-monospace, monospace; }
.holding-row {
  display: flex; align-items: center; justify-content: space-between; cursor: pointer;
  background: linear-gradient(to right, var(--rose-bg), var(--rose-mid), var(--rose-bg));
  border: 1px solid var(--rose-border); border-radius: var(--radius-card); padding: .75rem;
  transition: filter .15s;
}
.holding-row:hover { filter: brightness(.96); }
.holding-row .ico { width: 32px; height: 32px; border-radius: var(--radius-lg); background: rgba(255,255,255,.8); display: flex; align-items: center; justify-content: center; color: var(--rose-ink); }
.holding-row .val { font-size: 12px; font-weight: 700; font-family: ui-monospace, monospace; color: var(--brown); }

.setting-row {
  display: flex; align-items: center; justify-content: space-between; background: #fff;
  border: 1px solid var(--gray-100); border-radius: var(--radius-card); padding: .75rem; cursor: pointer;
  box-shadow: 0 1px 2px rgba(0,0,0,.05); transition: background .15s;
}
.setting-row:hover { background: var(--gray-50); }
/* Static rows (e.g. the admin-only password entries) are not actionable: no
   pointer, no hover highlight, muted label. */
.setting-row-static { cursor: default; }
.setting-row-static:hover { background: #fff; }
/* Rows that go somewhere are real anchors, so they are focusable and respond
   to Enter. Reset the browser's link styling and give them a focus ring. */
.setting-row-link { text-decoration: none; color: inherit; }
.setting-row-link:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.setting-ic { width: 28px; height: 28px; border-radius: 50%; background: var(--rose-bg); display: flex; align-items: center; justify-content: center; color: var(--rose-ink); }
.setting-status { font-size: 11px; font-weight: 600; color: var(--emerald-700); background: var(--ok-bg); padding: .15rem .5rem; border-radius: var(--radius-pill); }

/* Open/close disclosure (currently the MSB licence row). It expands in place
   rather than navigating, so it is a div with role=button instead of an anchor
   like the rows that go to a real page. The chevron rotates to match state. */
.js-disclosure { width: 100%; font: inherit; text-align: left; }
.js-disclosure-chevron { transition: transform .2s ease; }
.js-disclosure[aria-expanded="true"] .js-disclosure-chevron { transform: rotate(180deg); }
.disclosure-panel {
  margin: .3rem 0 .2rem; padding: .75rem; background: var(--gray-50);
  border: 1px solid var(--gray-100); border-radius: var(--radius-card);
}

.logout-row {
  width: 100%; margin-top: .75rem; background: var(--bad-bg); border: 1px solid var(--bad-border);
  border-radius: var(--radius-card); padding: .9rem; display: flex; align-items: center; justify-content: space-between;
  cursor: pointer; color: var(--red-700); font-family: inherit; transition: background .15s;
}
.logout-row:hover { background: var(--bad-soft); }

/* form grid: phone code + number */
.split { display: flex; gap: .5rem; }
.split > * { flex: 1; min-width: 0; }

.vault-promo { background: linear-gradient(to right, var(--rose-bg), var(--rose-mid), var(--rose-bg)); border: 1px solid var(--rose-border); border-radius: var(--radius-card); padding: 1rem; text-align: center; }

.report-bar { height: 6px; background: var(--gray-100); border-radius: var(--radius-pill); overflow: hidden; }
.report-bar > div { height: 100%; background: var(--gold); border-radius: var(--radius-pill); }
.pull-list { padding-top: .5rem; display: flex; flex-direction: column; gap: .6rem; }

/* ============================================================
   HOLDINGS — GOLD THEME (holdings.php only)
   holdings.php puts .gold-page on its .page-shell; every rule below
   is scoped to it so the rose theme survives on the other pages.
   The palette follows the original app's holding screen: a deep-to-
   bright gold header, gold ribbon pills with a white active tab,
   gold CTA buttons, and orange-to-gold gradient rows with white
   values. #e5a93c/#b8860b are the live site's own gold pair (see
   the palette notes at the top of this file).
   ============================================================ */
.gold-page .header-rose {
  background: linear-gradient(to right, #b8860b, #e5a93c 50%, #b8860b);
}
/* The ribbon bar itself disappears so the pills sit straight on the
   gold header, as in the reference; the active pill turns white. */
.gold-page .ribbon { background: transparent; backdrop-filter: none; }
.gold-page .ribbon-item.pill { background: var(--gold); color: #fff; }
.gold-page .ribbon-item.pill:hover { background: #e5a93c; color: #fff; }
.gold-page .ribbon-item.pill.active { background: #fff; color: var(--gold-ink); }

/* Donate page: the only gold header whose band can carry an uploaded photo
   (Admin → Frontend CMS → Page Background Photos → donate_bg). The photo sits
   behind the header content; a dark wash keeps the ribbon pills, badge and
   title legible over an arbitrary image, and the title/subtitle flip to white.
   With no photo the .donate-header-bg layer is transparent, so the gold
   gradient above is what shows. */
.donate-header .donate-header-bg {
  position: absolute; inset: 0; z-index: 0;
  background-size: cover; background-position: center;
}
.donate-header.has-bg .donate-header-bg::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(43, 26, 24, .42), rgba(43, 26, 24, .62));
}
.donate-header .header-row,
.donate-header > div:last-child { position: relative; z-index: 1; }
.donate-title { font-size: 13px; font-weight: 700; color: #4a2e2b; }
.donate-sub { font-size: 10px; color: #4a2e2b; text-transform: uppercase; letter-spacing: .02em; }
.donate-header.has-bg .donate-title,
.donate-header.has-bg .donate-sub { color: #fff; }

.gold-page .btn-rose {
  background: linear-gradient(135deg, #e5a93c, var(--gold));
  color: var(--on-gold);
}
.gold-page .btn-rose:hover { background: linear-gradient(135deg, var(--gold), var(--gold-dark)); }

.gold-page .holding-row {
  background: linear-gradient(to right, #e09217, #f2bd45);
  border-color: transparent;
}
.gold-page .holding-row .val { color: #fff; }

/* Icons: one treatment page-wide — a white glyph on a solid gold chip.
   Chips that sit on the gold header itself use --gold-dark so they do
   not blend into the gradient. The logout row keeps its inline red
   chip: the colour is the meaning there, and inline styles win. */
.gold-page .holding-row .ico,
.gold-page .market-icon,
.gold-page .setting-ic { background: var(--gold); color: #fff; }
.gold-page .icon-badge,
.gold-page .avatar-badge { background: var(--gold-dark); color: #fff; }
/* Bare inline glyphs inside cards get the chip drawn around them. */
.gold-page .card .text-gold,
.gold-page .field-hint .text-gold {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--gold); color: #fff; border-radius: var(--radius-md); padding: 4px;
}
.gold-page .vault-promo .material-symbols-outlined {
  display: inline-flex; background: var(--gold); color: #fff;
  border-radius: var(--radius-lg); padding: 8px;
}

/* Forms, vault and settings: the rose tints become gold tints. */
.gold-page .section-head { border-bottom-color: var(--gold); }
.gold-page select.select-rose { background: #fdf3dd; border-color: #eed9a8; }
.gold-page .vault-promo,
.gold-page .card-rose {
  background: linear-gradient(to right, #fdf3dd, #f7e3b5, #fdf3dd);
  border-color: #eed9a8;
}
.gold-page .tab-pill.active { background: var(--gold); color: var(--on-gold); }
.gold-page .nav-link.active { color: var(--gold-ink); }
.gold-page .nav-link.active .ico {
  background: var(--gold); color: var(--on-gold);
  box-shadow: 0 5px 12px rgba(217, 155, 38, .40);
}

/* The theme now runs on every member page (trade/contact/donate carry
   .gold-page too). These are the page-specific stragglers that would
   otherwise stay rose or dark-red: the trade header's circle buttons. Where
   .trade-page and .gold-page tie on specificity, this block wins — it is
   later in the file. */
.gold-page .circle-btn {
  background: var(--gold-dark); color: #fff; border-color: var(--gold-soft);
}

/* Contact page: the accents that were still rose against the gold theme —
   the compose/reply textareas, the focus ring, and text links. */
.gold-page .input-rose { background: #fdf3dd; border-color: #eed9a8; }
/* contact.php: put the message composer back to its original rose "box". The
   gold tint above is kept for the trade page's quote field, but the compose/
   reply textareas return to the rose background, border and focus ring. */
.gold-page .ct-composer .input-rose { background: var(--rose-bg); border-color: var(--rose-border); }
.gold-page .ct-composer .input:focus { box-shadow: 0 0 0 2px var(--rose-ink); }
.gold-page .input:focus { box-shadow: 0 0 0 2px var(--gold-dark); }
.gold-page .text-link { color: var(--gold-ink); }

/* ============================================================
   ORDER HISTORY + HOLDINGS — MOCKUP RESTYLE (history.php, holdings.php)
   The reference is a near-white screen whose gold banner carries a round YLG
   medallion hanging over the list, above roomy #fcebe7 cards. history.php lays
   each card out as three justified rows: instrument + profit, price +
   side/amount, open + close. Applied as a visual skin only: the rows keep the
   live data (side, price, amount, admin outcome, timestamps) and the pages keep
   their bottom nav, i18n and the language/profile controls, parked in the
   banner. .history-page and .holdings-ref sit alongside .gold-page on the same
   element; the banner + medallion classes are unique to these two pages, so
   those rules are shared, while the card rules stay page-specific. This block
   is deliberately last, so its equal-specificity rules beat the .gold-page ones
   above.
   ============================================================ */
/* Gold page surface shared by the holdings/profile/history flow -- matches the
   Holding.jpg reference (gold header, gold rows, white values). */
.holdings-ref,
.profile-ref,
.history-page { flex: 1; background: linear-gradient(180deg, #f7e8bf 0%, #f4dd9f 55%, #eed08a 100%); }
.history-page .orders { padding: 5px 7.5% 20px; }

/* Gold banner for history.php. The gold gradient is the base/fallback; the
   uploaded header photo (.history-banner-bg) paints over it. */
.history-banner {
  position: relative;
  /* width:100% is required: with an aspect-ratio + max-height and a width of
     auto, the box sized itself to the ratio (900px) instead of filling the
     page, which left the band short and pinned to the left on desktop. */
  width: 100%;
  /* Height follows the header photo's own 900x480 ratio, so the image shows
     whole on phone/tablet instead of being cropped into a fixed 105px strip.
     It stops growing at the photo's natural height (480px), which is reached at
     ~900px wide; past that the cover crop in .history-banner-bg takes over. */
  height: auto;
  aspect-ratio: 900 / 480;
  max-height: 480px;
  background: linear-gradient(135deg, #8a6508, #d9a537 45%, #f0c65f 55%, #b8860b);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .10);
}
/* Page header photo, painted over the gold gradient above (which stands in if
   the file is missing). The band's height tracks the photo's ratio, so cover
   shows the whole image until the 480px cap; the ::after wash darkens the top
   edge so the light language/profile controls stay legible over the image. */
.history-banner-bg {
  position: absolute; inset: 0; z-index: 0;
  background-size: cover; background-position: center;
}
.history-banner-bg::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(43, 26, 24, .45), rgba(43, 26, 24, .05) 55%);
}
.history-banner-tools {
  position: absolute; top: .5rem; left: .6rem; right: .6rem; z-index: 3;
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
}
.history-banner .avatar-badge {
  background: rgba(255, 255, 255, .92); color: #4a2e2b;
}
/* Brand mark on the gold band: the uploaded site logo, the same .site-logo the
   login / register / trade / holdings headers use. It replaces the old round
   "YLG CORPORATION CO., LTD." text badge but keeps that badge's footprint and
   hanging position -- centred and 24px below the band, which the first card's
   30px top margin clears. No white circle or gold rim: the login logo is the
   emblem image on its own, and it is already an oval with its own rim. */
.history-banner .medallion-wrap {
  position: absolute; left: 50%; bottom: -24px; transform: translateX(-50%);
  z-index: 2;
}
.history-banner .site-logo {
  width: 96px;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .18));
}

/* Cards: three justified rows each. The medallion hangs 24px into the list,
   so the first card is pushed down to clear it. */
.history-page .order-card {
  background: linear-gradient(180deg, #fbeec9, #f5dfa2);
  border-radius: 20px;
  padding: 18px 15px;
  margin-bottom: 15px;
  min-height: 120px;
}
.history-page .order-card:first-child { margin-top: 30px; }
.history-page .order-card .row { justify-content: space-between; gap: 10px; }
.history-page .order-card .row + .row { margin-top: 14px; }

.history-page .symbol { color: #a67f10; font-size: 14px; }
.history-page .profit { font-size: 13px; font-weight: 700; white-space: nowrap; }
.history-page .profit.is-win { color: #27805a; }
.history-page .profit.is-lose { color: #b91c1c; }
.history-page .profit.is-pending { color: #9b7c76; font-weight: 600; }
.history-page .price { font-size: 13px; color: #444; font-family: ui-monospace, monospace; white-space: nowrap; }
/* The row's price follows the outcome, like the profit beside it: green on a
   win, red on a lose, and the neutral #444 above while pending. */
.history-page .price.is-win { color: #27805a; }
.history-page .price.is-lose { color: #b91c1c; }

/* Outcome badge beside the instrument: green WIN / red LOSE, so the result reads
   at a glance even before the profit figure is parsed. */
.history-page .result-badge {
  display: inline-flex; align-items: center; padding: .1rem .45rem; border-radius: 999px;
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  border: 1px solid transparent;
}
.history-page .result-badge.is-win { color: #1b6b4a; background: #d9f2e4; border-color: #a7ddc2; }
.history-page .result-badge.is-lose { color: #a11616; background: #fbdcdc; border-color: #f0b4b4; }
.history-page .amount { font-size: 12px; color: #966b00; text-align: right; }
.history-page .date { font-size: 12px; color: #454545; font-family: ui-monospace, monospace; }

@media (max-width: 400px) {
  .history-page .orders { padding-left: 6%; padding-right: 6%; }
  .history-page .order-card { padding: 18px 13px; }
  .history-page .date { font-size: 11px; }
}

/* holdings.php recoloured gold to match the Holding.jpg reference: gold page
   surface, gold gradient rows with white values, warm-gold cards. The
   reg-banner keeps its controls + hanging logo, its photo replaced by the
   deep-to-bright gold gradient. */
.holdings-ref .main-content { padding-top: 2.5rem; }
/* Gold gradient is the FALLBACK for an absent photo only, so it is set as the
   background-image longhand and nothing else. It must not go through the
   `background:` shorthand: the shorthand also reset background-size/position/
   repeat that .reg-banner .bg sets, so an uploaded photo (holdings_bg) painted
   at its natural 900x480 size pinned to the top-left corner and was cropped to
   the 220px strip instead of covering the banner. With the longhand, a photo's
   inline background-image wins over this rule while .reg-banner .bg's
   cover/center still sizes it; with no photo the gradient shows instead. */
.holdings-ref .reg-banner .bg {
  background-image: linear-gradient(135deg, #8a6508, #d9a537 45%, #f0c65f 55%, #b8860b);
}
.holdings-ref .reg-banner .bg::after { background: rgba(60, 40, 0, .12); }
.holdings-ref .section-head { border-bottom-color: #dfb95c; }
.holdings-ref .holding-row {
  background: linear-gradient(to right, #dfa21f, #f0bd4c);
  border: 0; border-radius: 20px; padding: .85rem .9rem;
  box-shadow: 0 1px 2px rgba(120, 80, 10, .18);
}
.holdings-ref .holding-row .ico { background: rgba(255, 255, 255, .85); color: #966b00; }
.holdings-ref .holding-row .val { color: #fff; }
.holdings-ref .card { background: linear-gradient(180deg, #fbeec9, #f5dfa2); border-color: transparent; border-radius: 20px; }

/* profile.php now wears the same gold skin as the holding page: gold reg-banner
   (its photo replaced by the deep-to-bright gradient), gold surface, warm-gold
   cards -- so holdings -> profile -> history read as one gold flow. */
.profile-ref .reg-banner .bg {
  background-image: none;
  background: linear-gradient(135deg, #8a6508, #d9a537 45%, #f0c65f 55%, #b8860b);
}
.profile-ref .reg-banner .bg::after { background: rgba(60, 40, 0, .12); }
.profile-ref .card { background: linear-gradient(180deg, #fbeec9, #f5dfa2); border-color: transparent; border-radius: 20px; }

/* Support chat photo attachments: the in-bubble image and the pre-send
   thumbnail with its remove button. */
.msg-photo {
  display: block; width: 100%; max-width: 220px;
  border-radius: var(--radius-lg); margin-bottom: .35rem;
}
.photo-preview { position: relative; display: inline-block; }
.photo-preview img {
  display: block; max-width: 140px;
  border-radius: var(--radius-lg); border: 1px solid var(--stone-200);
}
.photo-preview-x {
  position: absolute; top: -8px; right: -8px; width: 24px; height: 24px;
  border-radius: 50%; border: none; background: var(--charcoal); color: #fff;
  cursor: pointer; font-size: 14px; line-height: 1;
}

.text-caption { font-size: 12px; color: var(--gray-500); text-align: center; padding-top: .6rem; margin: 0; }
.footer-copy { text-align: center; font-size: 12px; color: var(--gray-500); padding: .5rem 0 1.25rem; margin: 0; }

@media (max-width: 400px) {
  .modal { max-width: 100%; }
}

/* ============================================================
   RESPONSIVE SHELL
   The app was phone-only: a 28rem column centred in the viewport
   with a fixed bottom bar. On a desktop that reads as a small
   phone mock-up floating in grey, with ~1000px of dead space
   either side and the primary actions marooned in the middle.

   Two sizes, one fixed measure:
     < 900px    phone/tablet portrait — fluid column, fixed header + bottom bar
     >= 900px   one fixed 74rem column with constant 1.5rem side gutters on
                every page; resizing the window no longer changes the page size
     >= 1180px  two-up card grid inside the same 74rem column
   The header sticks from the base rule at every width.
   ============================================================ */

/* ---------- >= 900px: the fixed 74rem measure ---------- */
@media (min-width: 900px) {
  /* One fixed page size for tablet through desktop. There is deliberately no
     intermediate 46rem step: two caps meant the page visibly changed width as
     the window crossed 1180px, and no two pages ever quite agreed. */
  .main-content { max-width: 74rem; margin: 0 auto; padding: 1rem 1.5rem 2.5rem; }

  /* History keeps the fixed 74rem measure like every other page: the banner
     stays full-bleed (as the other page headers do) but the card column is
     centred and swaps the mockup's percentage gutters for the app's 1.5rem. */
  .history-page .orders { max-width: 74rem; margin: 0 auto; padding: 5px 1.5rem 2.5rem; }

  /* The chart earns more height once the column is not a phone. */
  .tv-frame { height: 620px; }

  /* The bottom bar stays the only nav, so this page still needs clearing
     (.pb-20). The bar is fixed and FULL-BLEED at every size — so centring
     does not clear the support bubble; only a bottom offset above the bar's
     64px height does. Dropping .chat-fab to bottom:1.5rem put it at 24..72px
     from the floor, of which the bar covers 0..64px: it sat behind the bar
     (z-index 50 over 40) with an 8px sliver showing. Nudge .chat-fab right
     only and leave the vertical offsets inherited from the base rules, which
     already clear it. */
  .chat-fab { right: 1.5rem; }

  .modal { max-width: 27rem; }
}

/* ---------- >= 1180px: two-up content ----------
   Below this the second column would be ~265px, too narrow for the
   balance/donation chip rows. The column itself is already the fixed 74rem
   from the 900px step — only the card arrangement changes here. */
@media (min-width: 1180px) {

  /* Wide content pairs with wide content: the chart and the product table
     take the roomy column, the compact cards stack in the narrow one. No item
     spans rows — a spanning item makes the browser distribute its surplus
     height across the rows it covers, which opens a gap in the second column. */
  .js-panel[data-panel="trade"] {
    display: grid;
    grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr);
    gap: 1rem;
    align-items: start;
  }
  #chartCard    { grid-column: 1; grid-row: 1; }
  #productsCard { grid-column: 1; grid-row: 2; }
  #donateCard   { grid-column: 1; grid-row: 3; }
  #promoCard    { grid-column: 2; grid-row: 1; }
  .footer-copy  { grid-column: 1 / -1; grid-row: 4; }

  /* Matched-height row: Financial Products and Charity Donations share one
     height so the two read as a set.

     Grid cannot do this on its own here. Row stretching only aligns items that
     occupy the SAME row, and these two sit in different rows AND different
     columns (products in the wide 1.7fr track, charity in the narrow 1fr). So
     the shared min-height below is what actually equalises them; `align-items:
     start` stays as it is, since stretch here would pad the chart and promo too.

     22rem is sized for the products table, a fixed 5 tiers, which is the taller
     of the two at typical content. It is a floor, not a hard lock -- a member
     with more donor rows than the 10 the query returns pushes their own card
     taller, and the button stays pinned to the bottom so the card still reads
     as a unit.

     Deliberately desktop-only: on phones these are stacked full-width, and a
     shared height there would just add dead space under the shorter one. */
  #productsCard, #donateCard { min-height: 22rem; }

  /* Both cards are already flex columns (.card.stack), so margin-top:auto pins
     each CTA to the bottom edge instead of leaving it stranded mid-card when
     the sibling is taller. */
  #productsCard > .btn:last-child,
  #donateCard   > .btn:last-child { margin-top: auto; }

  /* Every tab panel shares the same fixed 74rem column. Market and history
     used to be capped at 40rem, which made the whole page visibly change
     width when switching tabs — the exact inconsistency the fixed-measure
     contract exists to remove. */

  /* Contact is a stack of conversation cards, one per thread, plus the new-
     message box. Single column at every width: laying chat boxes side by side
     would make the member read thread A and thread B as one conversation.

     Also narrower than the 74rem the rest of the app uses. Chat bubbles are
     capped at 32rem, so a 74rem card leaves ~40rem of empty paper to the side
     of every message; 44rem is the measure that fits the content. Centered so
     the column does not drift to the left edge on a wide screen. */
  .ct-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1rem;
    align-items: start;
    max-width: 44rem;
    margin-inline: auto;
  }
  .ct-grid > * { align-self: start; }
}

/* No 1600px step: desktop is 74rem at every width >= 900px. A wider cap here
   would break that contract, and it also contradicted its own comment — "stop
   the column growing without limit" describes capping the column, whereas 82rem
   is looser than the 74rem it replaced. */

/* Short landscape phones: the chart would push the buy/sell pair off screen. */
@media (max-height: 520px) and (max-width: 899px) {
  .tv-frame { height: 200px; }
}

/* ============================================================
   DEPOSIT — method picker and screenshot box
   ============================================================ */

.method-picker { display: flex; gap: .4rem; }
.method-opt {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: .4rem;
  min-height: 44px; padding: .5rem .6rem;
  border: 1px solid var(--rose-border); border-radius: var(--radius-lg);
  background: #fff; color: var(--brown);
  font-family: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
}
.method-opt .material-symbols-outlined { font-size: 18px; }
.method-opt:hover { background: var(--rose-bg); }
.method-opt.active {
  background: var(--gold); border-color: var(--gold); color: var(--on-gold); font-weight: 700;
}

.shot-box {
  border: 1px dashed var(--rose-border); border-radius: var(--radius-lg);
  padding: .6rem; background: var(--paper-warm);
}
.shot-input {
  width: 100%; font-size: 12px; color: var(--brown);
  /* The native control renders a small button; give it a real tap target. */
  min-height: 40px; padding: .35rem;
}
.shot-input::file-selector-button {
  font-family: inherit; font-size: 12px; font-weight: 600;
  min-height: 34px; margin-right: .6rem; padding: 0 .8rem;
  border: 1px solid var(--rose-border); border-radius: var(--radius-md);
  background: #fff; color: var(--brown); cursor: pointer;
}
.shot-hint { font-size: 11px; color: var(--gray-500); margin-top: .4rem; line-height: 1.5; }
.shot-preview {
  display: block; width: 100%; max-height: 220px; object-fit: contain;
  margin-top: .5rem; border: 1px solid var(--rose-border); border-radius: var(--radius-lg); background: #fff;
}
/* `hidden` must actually hide. The rule above sets display:block, and an author
   declaration beats the UA `[hidden] { display: none }`, so without this a
   .shot-preview rendered as an empty bordered box until a file was picked. */
.shot-preview[hidden] { display: none; }
/* Compact variant for the ID front/back previews in the verification sheet:
   a small thumbnail rather than a full-width 220px image, so the submit button
   stays on screen. */
.shot-preview.shot-preview-sm { width: auto; max-width: 100%; max-height: 120px; }

/* Identity verification sheet: the two ID upload boxes are the tallest part of
   the form and read as oversized next to the plain name/number fields. Keep the
   control tappable but make the whole shot-box compact. Scoped to #verifyModal
   so the deposit sheet (which uses .modal-wide) keeps its own sizing. */
#verifyModal .shot-box {
  /* Cleaner than the shared dashed rose rule: a single light solid hairline and
     a rounder corner. Size (padding) stays as set above. */
  border: 1px solid var(--stone-200);
  border-radius: var(--radius-md);
  padding: .25rem .35rem;
}
#verifyModal .shot-input { font-size: 10px; min-height: 26px; padding: .1rem; }
#verifyModal .shot-input::file-selector-button {
  font-size: 10px; font-weight: 600;
  min-height: 22px; margin-right: .35rem; padding: 0 .45rem;
}
#verifyModal .shot-hint { font-size: 9px; margin-top: .15rem; line-height: 1.35; }
#verifyModal .shot-preview.shot-preview-sm { max-height: 64px; }

/* The card panels are revealed by script. Without JS they stay hidden, which is
   the safe direction: no card box appears that nothing can submit. */

/* ---------- Larger deposit sheet ----------
   .modal is capped at 24rem (27rem on wide screens) because most of them are
   short single-field forms. The deposit sheet is not one: it carries a method
   picker, an amount, two card fields and a screenshot box, so at 24rem the
   controls sat in a narrow column with the field labels wrapping. This modifier
   widens only that sheet and scales its type, leaving every other modal alone.

   28rem, not 34rem: 34rem read as too big on a phone, where the sheet plus its
   margins ate most of the viewport. 28rem still clears the 24rem complaint while
   leaving the form readable without a scroll on an ordinary handset. */
.modal-wide { max-width: 28rem; padding: 1.25rem; }
.modal-wide .field-label { font-size: 12px; margin-bottom: .3rem; }
.modal-wide .input-soft { font-size: 15px; padding: .7rem .85rem; }
/* 15px is below the 16px iOS needs to suppress its focus zoom, so this input
   does zoom on an iPhone. The cost is a viewport that stays magnified after
   blur; the alternative, 14px, was the app-wide default and zoomed too. Raise
   both to 16px if that trade reads worse than a slightly larger control. */
.modal-wide .method-opt { font-size: 13px; min-height: 46px; padding: .55rem .7rem; }
.modal-wide .method-opt .material-symbols-outlined { font-size: 19px; }
/* Screenshot box kept compact: the rest of the sheet is scaled up for
   readability, but the upload control only needs to be tappable, and a
   220px preview pushed the submit button off a phone screen. */
.modal-wide .shot-box { padding: .4rem; }
.modal-wide .shot-input { font-size: 12px; min-height: 34px; padding: .25rem; }
.modal-wide .shot-input::file-selector-button { font-size: 12px; min-height: 28px; padding: 0 .6rem; margin-right: .45rem; }
.modal-wide .shot-hint { font-size: 11px; }
.modal-wide .shot-preview { max-height: 120px; }
.modal-wide .btn-rose, .modal-wide .btn-charcoal { padding: .7rem 1rem; font-size: 14px; }
.modal-wide .modal-head h4 { font-size: 16px; }
.modal-wide .stack { gap: .7rem; }

@media (min-width: 900px) {
  .modal-wide { max-width: 30rem; }
}

/* Phones: the sheet fills the width, but a card form plus a screenshot box is
   taller than a short viewport. Cap the height and let the sheet scroll rather
   than letting the submit button fall off the bottom. */
@media (max-width: 599px) {
  .modal-wide { max-width: 100%; padding: 1.1rem; }
  .modal-wide .modal-body { max-height: min(72vh, 34rem); overflow-y: auto; -webkit-overflow-scrolling: touch; }
}

/* ============================================================
   FINANCIAL PRODUCTS (financial.php)
   One card per VIP plan. The yield is the loudest element and the buy button
   is full width, so the tap target is the whole card's action, not a link.
   ============================================================ */
.plan-grid { display: grid; grid-template-columns: 1fr; gap: .6rem; }
.plan-card {
  background: #fff; border: 1px solid var(--stone-200); border-radius: var(--radius-2xl);
  padding: .85rem; box-shadow: 0 1px 2px rgba(0,0,0,.05);
  display: flex; flex-direction: column; gap: .6rem;
}
.plan-top { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.plan-name { display: flex; align-items: center; gap: .5rem; font-size: 15px; font-weight: 800; color: var(--brown); }
.plan-name .material-symbols-outlined { font-size: 20px; color: var(--gold); }
.plan-yield { text-align: right; line-height: 1.05; }
.plan-yield b { display: block; font-size: 20px; color: var(--gold-ink); }
.plan-yield span { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--gray-500); font-weight: 600; }
.plan-meta { display: flex; justify-content: space-between; gap: .5rem; font-size: 12px; color: var(--gray-600); }
.plan-meta b { color: var(--stone-900); }

/* Investment status on financial.php. .pill-* lives in admin.css and is not
   loaded on member pages, so these are the member-side equivalents. */
.inv-pill { font-size: 11px; padding: .15rem .5rem; border-radius: var(--radius-pill); font-weight: 700; display: inline-block; white-space: nowrap; }
.inv-active  { background: var(--amber-100); color: var(--amber-800); }
.inv-matured { background: var(--ok-bg); color: var(--emerald-700); }

/* "VIP Member Only" lock badge shown in place of the Buy button on rows the
   member's account tier does not unlock. */
.vip-only-badge {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  color: var(--gray-500); background: var(--stone-100); border: 1px solid var(--stone-200);
  border-radius: var(--radius-pill); padding: .2rem .5rem; white-space: nowrap;
  display: inline-flex; align-items: center; gap: .25rem;
}
.vip-only-badge .material-symbols-outlined { font-size: 13px; }

@media (min-width: 1180px) {
  .plan-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
