/* ==========================================================================
   BaiSed LINE MINI App — design system
   --------------------------------------------------------------------------
   Every value here is LIFTED from the Flutter app, not designed fresh. The
   requirement is that the web surface reads as the same product.

   Sources:
     lib/theme/theme.dart                              colours, fonts, radii
     lib/features/homePage/baisedCampaign.dart:4068    the card recipe
     lib/features/homePage/lockable_campaign_card.dart the lock badge
     lib/features/brandsSpecific/brand_lucky_draw_page.dart:6380
                                                       the entries pill

   Fonts are the app's own files, already served from public/fonts/. Switzer is
   declared in public/fonts.css but Anuphan is NOT, so this file declares both
   rather than editing that shared stylesheet.
   ========================================================================== */

/* ---- Latin: Switzer ---------------------------------------------------- */
@font-face { font-family:'Switzer'; src:url('../../fonts/Switzer-Regular.otf') format('opentype');   font-weight:400; font-display:swap }
@font-face { font-family:'Switzer'; src:url('../../fonts/Switzer-Medium.otf') format('opentype');    font-weight:500; font-display:swap }
@font-face { font-family:'Switzer'; src:url('../../fonts/Switzer-Semibold.otf') format('opentype');  font-weight:600; font-display:swap }
@font-face { font-family:'Switzer'; src:url('../../fonts/Switzer-Bold.otf') format('opentype');      font-weight:700; font-display:swap }
@font-face { font-family:'Switzer'; src:url('../../fonts/Switzer-Extrabold.otf') format('opentype'); font-weight:800; font-display:swap }
@font-face { font-family:'Switzer'; src:url('../../fonts/Switzer-Black.otf') format('opentype');     font-weight:900; font-display:swap }

/* ---- Thai fallback: Anuphan (theme.dart _thaiFontFallback) ------------- */
@font-face { font-family:'Anuphan'; src:url('../../fonts/Anuphan-Regular.ttf') format('truetype');  font-weight:400; font-display:swap }
@font-face { font-family:'Anuphan'; src:url('../../fonts/Anuphan-Medium.ttf') format('truetype');   font-weight:500; font-display:swap }
@font-face { font-family:'Anuphan'; src:url('../../fonts/Anuphan-SemiBold.ttf') format('truetype'); font-weight:600; font-display:swap }
@font-face { font-family:'Anuphan'; src:url('../../fonts/Anuphan-Bold.ttf') format('truetype');     font-weight:700; font-display:swap }
/* Anuphan ships no 800/900 face. Mapping them onto Bold keeps the w800/w900
   text in the app (entries pill) from synthesising a smeared faux-bold. */
@font-face { font-family:'Anuphan'; src:url('../../fonts/Anuphan-Bold.ttf') format('truetype');     font-weight:800; font-display:swap }
@font-face { font-family:'Anuphan'; src:url('../../fonts/Anuphan-Bold.ttf') format('truetype');     font-weight:900; font-display:swap }

:root{
  /* theme.dart */
  --primary:#F6BE3B;            /* _baisedYellow */
  --ink:#000000;                /* _baisedBlack */
  --white:#FFFFFF;
  --bg:#F6F7F9;                 /* header gradient end stop / scaffold wash */
  --brand-red:#F23540;
  --brand-orange:#FF5400;
  --brand-yellow:#FEB600;
  --heat:#FA6A15;               /* baisedBrandHeatBlend */
  --heat-grad:linear-gradient(135deg,#FB7A35,#F06318);
  --pastel-grad:linear-gradient(135deg,#FF6B6B,#FFBD71,#EEDC82);
  --header-grad:linear-gradient(180deg,#FF6B6B 0%,#FFBD71 40%,#EEDC82 70%,#F6F7F9 100%);

  --muted:rgba(0,0,0,.60);
  --faint:rgba(0,0,0,.45);
  --line:rgba(0,0,0,.08);
  --grey50:#FAFAFA;             /* Colors.grey.shade50, card image bg */

  /* geometry, from the widgets */
  --r-card:16px;                /* card radius */
  --r-img:18px;                 /* card image radius */
  --r-pill:14px;                /* entries pill radius */
  --r-btn:8px;                  /* elevatedButtonTheme */
  --r-badge:20px;               /* lock badge */
  --pad-page:16px;              /* page horizontal padding */
  --shadow:0 2px 6px rgba(0,0,0,.06);

  /* Per-draw theme colour. Every draw doc carries `themeColor`; app.js sets
     this on the card / page. The heat blend is the app's own default. */
  --theme:#FA6A15;

  --sans:'Switzer','Anuphan',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
}

*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html,body{height:100%}
body{
  font-family:var(--sans);
  background:var(--bg);
  color:var(--ink);
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
  overscroll-behavior-y:none;
}
img{max-width:100%;display:block}
button{font:inherit;color:inherit;background:none;border:none;cursor:pointer}
a{color:inherit;text-decoration:none}

/* ---- app shell --------------------------------------------------------- */
/* Inside LINE this is always a phone-width webview, so the shell fills the
   screen and none of the desktop rules below ever apply. But the same URL opens
   on a laptop (staging, QA, a shared link), and a 520px white column floating
   in a near-white page reads as broken rather than as a phone app. On a wide
   viewport we therefore deepen the page behind it and give the shell a real
   edge, so it presents as a deliberate device frame. */
.shell{
  max-width:520px;margin:0 auto;min-height:100%;
  background:var(--white);
  display:flex;flex-direction:column;
  padding-bottom:calc(64px + env(safe-area-inset-bottom,0px));
}
@media (min-width:600px){
  body{background:#E8EAEE}
  .shell{
    min-height:100vh;
    box-shadow:0 0 0 1px rgba(26,26,46,.09), 0 18px 50px rgba(26,26,46,.13);
  }
  .tabs{box-shadow:0 0 0 1px rgba(26,26,46,.09)}
}
.shell.no-tabs{padding-bottom:0}
.page{flex:1;padding:0 var(--pad-page)}
/* No stat bar on this page, so the content owns the inset. */
.shell.no-tabs > .page:first-child{padding-top:calc(18px + env(safe-area-inset-top,0px))}
.page.wash{background:var(--bg)}

/* ---- section heading --------------------------------------------------- */
.sec{
  font-size:13px;font-weight:800;letter-spacing:.02em;
  color:var(--muted);margin:18px 0 10px;
}

/* ---- the lucky-draw home card -----------------------------------------
   REBUILT to match the app. The first pass used the PROMOTION card recipe
   (baisedCampaign.dart:4068 — a 120x120 thumbnail beside a text column). A
   lucky draw uses a completely different card: a section title OUTSIDE the
   card, then a gradient-edged card whose top is a full-bleed hero image and
   whose bottom is a white strip carrying the brand row, a four-box countdown,
   the entry ticket with the count, and the like count.

   Border edge: heroGradient.outer on the draw doc, e.g. Golden Mountain's
   #FFDB62 against its #DD1F22 primary. Falls back to the theme colour. */

.ldsec{margin:22px 0 26px}
.ldsec:active .ldsec__title{opacity:.7;transition:opacity .09s ease}
.ldsec__title{
  font-size:21px;font-weight:800;letter-spacing:-.02em;
  margin:0 2px 12px;line-height:1.25;
}

.ldcard{
  display:block;overflow:hidden;
  /* SQUIRCLE, not a rounded rect. Every draw card in the app is a
     figma_squircle SmoothRectangleBorder at cornerRadius 28 with
     cornerSmoothing 1.0 (baisedCampaign.dart:6770 and :6790). That is a
     superellipse; CSS's plain border-radius is a circular arc, and at this
     size the difference is obvious at all four corners.

     THE NUMBERS ARE FITTED, AND THE CSS UNIT IS NOT WHAT IT LOOKS LIKE.
     `corner-shape: superellipse(s)` takes s as a LOG2 EXPONENT: the actual
     superellipse exponent is 2^s. superellipse(1) is a circle, and
     superellipse(4.3) is exponent 19.7 — a corner so square it looks unrounded.
     Reading s as the exponent directly is the trap; it silently produces a
     near-rectangle.

     Both values below were fitted by measuring the browser's ACTUAL rendered
     silhouette (hit-testing the painted box row by row) against the real
     figma_squircle path sampled from the Dart source, not from a formula.

     FALLBACK FIRST, on purpose. `corner-shape` is recent and a LINE in-app
     WebView on older Android will not have it. Those browsers get r32, the
     best plain circular approximation (max error 1.9px) — which is also why
     .sq-16/.sq-24 further down inflate by ~1.2x. */
  border-radius:32px;
  /* GRADIENT ring, not a flat border. The app draws a `borderGradient` around
     every draw card; a solid edge was the most obviously wrong thing about the
     first version. padding-box/border-box gives a real gradient stroke. */
  border:3px solid transparent;
  background:
    linear-gradient(var(--white),var(--white)) padding-box,
    linear-gradient(120deg,var(--ring-a,var(--theme)),var(--ring-b,var(--edge,var(--theme)))) border-box;
  /* The FACE's ambient shadow: black 8%, blur 10, offset (0,2)
     (baisedCampaign.dart:6805). Dropped entirely on press. */
  box-shadow:0 2px 10px rgba(0,0,0,.08);
  /* 80ms easeOut, the AnimatedContainer's own duration and curve. */
  transition:transform .08s ease-out, box-shadow .08s ease-out;
}
/* The real thing, where the browser can draw it. r64 + superellipse(2.2)
   (exponent 2^2.2 = 4.6) tracks the true figma_squircle r28 s1.0 outline to a
   maximum error of 0.61px, measured against the painted pixels. The pair is
   load-bearing: the radius is more than double the Flutter number because at
   cornerSmoothing 1.0 Figma's corner runs `(1 + smoothing) * radius` = 56px
   along each edge, and the high exponent is what keeps that long run flat
   instead of ballooning into a stadium. Changing one without refitting the
   other breaks the match. */
@supports (corner-shape: superellipse(2.2)){
  .ldcard{
    border-radius:64px;
    corner-shape:superellipse(2.2);
  }
}

/* ---- the WINNERS-ANNOUNCED card (baisedCampaign.dart:6516) --------------
   A different card from the hero one, not a restyled badge: 8px padding
   around a 120x120 thumbnail, a 12px gap, and a 120px-tall text column.

   Its container radius is 24, NOT the active card's 28. Both squircle pairs
   scale linearly from the fitted r28 values (64 / superellipse 2.2), because
   a smoothing-1.0 corner and a fixed-exponent superellipse are both linear in
   their radius: 64 * 24/28 = 55, and the circular fallback 32 * 24/28 = 27. */
.ldcard--ended{border-radius:27px}
.ldend{display:flex;align-items:flex-start;gap:12px;padding:8px}
.ldend__art{
  width:120px;height:120px;flex:none;object-fit:cover;
  background:var(--grey50);
  /* Flutter cornerRadius 16, smoothing 1.0 — same linear scaling: 64 * 16/28
     = 37, fallback 32 * 16/28 = 18. */
  border-radius:18px;
}
.ldend__col{
  flex:1;min-width:0;height:120px;
  display:flex;flex-direction:column;align-items:flex-start;
}
/* padding 8/4, radius 8, #FFF3E0 on #FF6D00 — fixed values, not the theme. */
.ldend__tag{
  padding:4px 8px;border-radius:8px;background:#FFF3E0;
  font-size:12px;font-weight:700;color:#FF6D00;
}
.ldend__title{
  margin-top:8px;max-width:100%;
  font-size:14px;font-weight:600;color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.ldend__sub{
  max-width:100%;font-size:11px;font-weight:400;color:rgba(0,0,0,.7);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* `const Spacer()` — the count line is pinned to the bottom of the 120px
   column, not stacked under the subtitle. */
.ldend__n{
  margin-top:auto;display:flex;align-items:center;gap:6px;
  font-size:12px;font-weight:600;color:var(--theme);
}
.ldend__n svg{width:16px;height:16px;flex:none;fill:currentColor}

@supports (corner-shape: superellipse(2.2)){
  .ldcard--ended{border-radius:55px;corner-shape:superellipse(2.2)}
  .ldend__art{border-radius:37px;corner-shape:superellipse(2.2)}
}

/* PRESS = A BUTTON BEING PUSHED, NOT A CARD BEING SHRUNK.
   The previous scale(.972) was wrong in kind, not just degree.

   The app builds the card as a Stack (baisedCampaign.dart:6757): behind the
   face sits a slab filled with the SAME ring gradient, inset 2px on each side,
   its top pushed 4px down and its bottom hanging 4px BELOW the face. Only that
   bottom overhang is visible, and it is the thicker edge that makes the card
   read as a physical object.

   On press the SLAB STAYS PUT and the face slides down 4px onto it, losing its
   ambient shadow. The 3D edge collapses to flat, which is exactly the feel of
   pressing a key. Nothing scales.

   The slab needs .ld3d as a host because .ldcard carries overflow:hidden for
   the hero art and would clip an overhanging pseudo-element of its own. */
.ld3d{position:relative}
.ld3d::before{
  content:'';position:absolute;z-index:0;
  top:4px;left:2px;right:2px;bottom:-4px;
  background:linear-gradient(120deg,
    var(--ring-a,var(--theme)),
    var(--ring-b,var(--ring-a,var(--theme))));
  border-radius:32px;
}
.ld3d > .ldcard{position:relative;z-index:1}
.ld3d:active > .ldcard{transform:translateY(4px);box-shadow:none}
/* Must come AFTER the base rule above: an @supports block placed earlier in
   the file loses the border-radius to it and leaves a superellipse at the
   fallback radius, which is a different shape from the face it sits behind. */
@supports (corner-shape: superellipse(2.2)){
  .ld3d::before{border-radius:64px;corner-shape:superellipse(2.2)}
}

/* The winners-announced card has NO depth slab — the app gives it a plain
   10px downward nudge instead (baisedCampaign.dart:6528) and a lighter
   resting shadow (0x0F000000, blur 6, offset 0,2). */
.ldcard--ended{box-shadow:0 2px 6px rgba(0,0,0,.06)}
.ldcard--ended:active{transform:translateY(10px);box-shadow:none}

@media (prefers-reduced-motion:reduce){
  .ldcard{transition:none}
}
.ldcard__hero{
  display:block;width:100%;aspect-ratio:2/1;object-fit:cover;
  background:var(--grey50);
  /* NO radius of its own. The only rounding is the card's own clip, so the art
     never carries a second, mismatched curve inside it. */
  border-radius:0;
  /* Several of these banners ship a TRANSPARENT rounded-corner margin baked
     into the PNG. Drawn at exact size those corners let the card's fill show
     through and read as a curve inside a curve. The app pushes them off with a
     slight zoom (baisedCampaign.dart:1527 and :2797); `--img-scale` carries the
     doc's own value, defaulting to no zoom. */
  transform:scale(var(--img-scale,1));
  transform-origin:center;
}
.ldcard__body{padding:10px 14px 12px}

.ldcard__row{display:flex;align-items:center;gap:8px;min-height:52px}
.ldcard__logo{
  width:38px;height:38px;flex:none;border-radius:50%;
  object-fit:contain;background:var(--white);
}
/* WORDMARK mode. The image is the title (BaiSed ships a gift mark + text as one
   669x195 lockup), so it gets real width and height rather than being crushed
   into the circular-logo slot. */
.ldcard__wordmark{
  height:34px;width:auto;max-width:62%;flex:none;
  object-fit:contain;object-position:left center;
}

.ldcard__brand{
  font-size:15.5px;font-weight:700;flex:1 1 auto;min-width:56px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

/* Four separate boxes, number over unit, exactly as the app draws them. */
.cd{display:flex;align-items:center;gap:3px;flex:none}
/* 3D BOXES, not flat. `_box` (brand_lucky_draw_page:14636) gives every cell
   TWO hard shadows — blurRadius 0, offsets (0,3) and (0,4.5) — in progressively
   darker shades of the box colour, which is what makes the row read as raised
   tiles rather than printed swatches.

   Golden Mountain's home card passes boxColor #FFDB62 (baisedCampaign:7117),
   so the lips are Color.lerp(#FFDB62, black, 0.14 / 0.26) = #DBBC54 and
   #BDA248 — a plain multiply, computed once here rather than eyeballed. The
   old #FFD84D with a single 1px black-6% shadow was neither the right yellow
   nor a lip.

   Numbers AND units are the draw's accent (numberColor/unitColor both
   `accent` for GM, :7101 and :7128) — red on yellow, not the dark brown that
   was here. The separators take the BOX colour so they read as spacing, not
   punctuation (separatorColor, :7134). */
.cd i{
  /* Sized so all four boxes plus the logo still leave room for a Thai brand
     name on a 375px screen. At min-width 46 the row overflowed and the name
     truncated to two characters. */
  font-style:normal;background:var(--cd-box,#FFDB62);
  /* compact:true -> radius 8 and padding 4/2. */
  border-radius:8px;min-width:36px;padding:4px 2px;text-align:center;
  display:flex;flex-direction:column;line-height:1.05;
  box-shadow:0 3px 0 var(--cd-sh1,#DBBC54), 0 4.5px 0 var(--cd-sh2,#BDA248);
}
.cd i b{font-size:18px;font-weight:900;line-height:1;color:var(--cd-num,var(--theme))}
.cd i span{
  font-size:10px;font-weight:600;margin-top:1px;
  color:var(--cd-unit,var(--theme));
}
/* `separatorColor ?? labelColor` — the card's ink at 70%, not the box fill.
   Golden Mountain is the one card that passes its yellow here. */
.cd s{text-decoration:none;font-weight:900;color:var(--cd-sep,var(--cd-box,#FFDB62));font-size:14px}

/* The house draw's countdown: cream boxes, numbers filled by the warm pastel
   gradient rather than a flat brand colour (baisedCampaign.dart:5428). */
/* The house draw passes NO boxColor, so `_box` falls back to the cream
   #FFF6E5 with its own pair of lips (#E6D5B3 / #D4C2A1) and a black45 unit —
   the branded yellow and red would be wrong on it. */
.cd--pastel{--cd-box:#FFF6E5;--cd-sh1:#E6D5B3;--cd-sh2:#D4C2A1;--cd-unit:rgba(0,0,0,.45)}
.cd--pastel i{background:var(--cd-box)}
.cd--pastel i b{
  background:linear-gradient(180deg,#FF6B6B,#FFBD71,#EEDC82);
  -webkit-background-clip:text;background-clip:text;color:transparent;
}


/* Closed / winners-pending badge, shown instead of the countdown.

   NOT a bespoke chip. The app deliberately renders this slot with the SAME
   chrome as ONE countdown cell (baisedCampaign.dart:5822) — same fill, same
   8px radius, same two HARD offset shadows, same value-over-unit stack — so
   the card reads as the same card, just stopped. The green pill this replaces
   was invented here and matched nothing in the app.

   Fill defaults to the countdown's cream #FFF6E5 and the ink to the draw's
   own accent; --cd-box / --cd-ink / --cd-sh1 / --cd-sh2 carry the two
   per-brand overrides, set in index.html. */
.cd--closed{
  /* .cd is a flex row, so without resetting the direction the label and the
     date sat side by side instead of stacking as the app draws them. */
  display:flex;flex-direction:column;gap:0;
  /* RIGHT-ALIGNED. The live countdown's last cell ends flush with the card's
     right padding, so this single box has to land where that cell ENDED, not
     where the first one began (baisedCampaign.dart:5851). Left-aligned it read
     as floating in the middle of the row. */
  margin-left:auto;
  background:var(--cd-box,#FFF6E5);
  border-radius:8px;
  padding:4px 8px;text-align:center;
  /* blurRadius 0 in Flutter: a hard stacked 3D lip, not a soft shadow. */
  box-shadow:0 3px 0 var(--cd-sh1,#E6D5B3), 0 4.5px 0 var(--cd-sh2,#D4C2A1);
}
.cd--closed b{
  display:block;font-size:15px;font-weight:900;line-height:1;
  color:var(--cd-ink,var(--theme));
}
.cd--closed span{
  display:block;margin-top:1px;font-size:9px;font-weight:600;
  color:var(--cd-ink,var(--theme));opacity:.85;
}

/* NO divider above the entries row. There is no Divider anywhere in
   _BrandLuckyDrawHomeCard's tree — the brand row and the entry/like row are
   two children of the same Column with plain spacing between them. The rule
   here was invented and cut the card in half. Kept the vertical rhythm the
   padding gave, so removing the line does not close the gap. */
.ldcard__row2{
  display:flex;align-items:center;gap:8px;margin-top:20px;
}
.ldcard__ticket{height:34px;width:auto;flex:none}
.ldcard__n{font-size:23px;font-weight:900;color:var(--theme);line-height:1}
.ldcard__likes{
  /* Count LEFT of the heart with a 4px gap, 16px w500 grey.shade600
     (baisedCampaign.dart:7577). It was 17px w600 in --faint with a 6px gap. */
  margin-left:auto;display:flex;align-items:center;gap:4px;
}
.ldcard__likes .n{font-size:16px;font-weight:500;color:#757575}
.ldcard__likes .heart{
  width:28px;height:28px;flex:none;display:block;
  /* _kBrandLuckyDrawGradient, centerLeft -> centerRight. */
  background:linear-gradient(90deg,#FFAB70,#FF8989);
  -webkit-mask:url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M12%2021.35l-1.45-1.32C5.4%2015.36%202%2012.28%202%208.5%202%205.42%204.42%203%207.5%203c1.74%200%203.41.81%204.5%202.09C13.09%203.81%2014.76%203%2016.5%203%2019.58%203%2022%205.42%2022%208.5c0%203.78-3.4%206.86-8.55%2011.54L12%2021.35z%22%2F%3E%3C%2Fsvg%3E') center/contain no-repeat;
  mask:url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M12%2021.35l-1.45-1.32C5.4%2015.36%202%2012.28%202%208.5%202%205.42%204.42%203%207.5%203c1.74%200%203.41.81%204.5%202.09C13.09%203.81%2014.76%203%2016.5%203%2019.58%203%2022%205.42%2022%208.5c0%203.78-3.4%206.86-8.55%2011.54L12%2021.35z%22%2F%3E%3C%2Fsvg%3E') center/contain no-repeat;
}

.sk .ldcard__hero{background:rgba(0,0,0,.06)}

/* ---- tag chip (padding 8/4, radius 8, 12px w600) ---------------------- */
.tag{
  display:inline-block;align-self:flex-start;
  padding:4px 8px;border-radius:8px;
  font-size:12px;font-weight:600;
  background:color-mix(in srgb,var(--theme) 12%,transparent);
  color:var(--theme);
}

/* ---- entries pill, from brand_lucky_draw_page.dart:6380 --------------- */
.entries{
  display:flex;align-items:center;gap:4px;
  background:color-mix(in srgb,var(--theme) 8%,transparent);
  border-radius:var(--r-pill);
  padding:12px 14px;margin-bottom:14px;
}
/* The app draws the draw's own `entryTicketImage` beside the count
   (EntryCountCard). Optional: absent art simply collapses the slot. */
.entries__ticket{width:56px;height:auto;flex:none;margin-right:2px}
.entries__label{flex:1;font-size:13px;font-weight:800;color:var(--theme)}
.entries__n{font-size:22px;font-weight:900;line-height:1;color:var(--theme)}
.entries__unit{font-size:11px;font-weight:700;color:color-mix(in srgb,var(--theme) 70%,transparent)}

/* ---- hero (draw detail header) ---------------------------------------- */
.hero{
  border-radius:var(--r-card);overflow:hidden;margin:14px 0;
  background:var(--theme);color:var(--white);
  box-shadow:var(--shadow);
}
.hero__img{width:100%;aspect-ratio:2.2;object-fit:cover;background:var(--grey50)}
.hero__body{padding:14px 16px}
.hero__brand{font-size:11px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;opacity:.85}
.hero__prize{font-size:20px;font-weight:800;line-height:1.25;margin-top:4px}
.hero__cd{
  display:inline-block;margin-top:10px;padding:4px 10px;border-radius:999px;
  background:rgba(255,255,255,.22);font-size:12px;font-weight:600;
}

/* ---- info rows --------------------------------------------------------- */
.row{display:flex;gap:10px;align-items:center;padding:11px 0;border-bottom:1px solid var(--line)}
.row:last-child{border-bottom:none}
.row__ic{
  width:32px;height:32px;flex:none;border-radius:9px;
  background:color-mix(in srgb,var(--theme) 10%,transparent);
  display:flex;align-items:center;justify-content:center;font-size:15px;
}
.row__m{flex:1;min-width:0}
.row__m b{display:block;font-size:14px;font-weight:600}
.row__m span{display:block;font-size:12px;color:var(--faint)}
.row__amt{font-weight:800;font-size:14px;color:var(--theme);white-space:nowrap}

/* ---- buttons (radius 8, primary yellow, black label, w600) ------------ */
.btn{
  display:block;width:100%;text-align:center;
  padding:14px;border-radius:var(--r-btn);
  background:var(--primary);color:var(--ink);
  font-size:15px;font-weight:600;
  margin-top:12px;
}
.btn:active{filter:brightness(.94)}
.btn[disabled]{opacity:.45;pointer-events:none}
.btn--ghost{background:var(--white);border:1.5px solid var(--line);color:var(--ink)}
.btn--theme{background:var(--theme);color:var(--white)}
.btn--line{background:#06C755;color:var(--white)}
.btn--dark{background:var(--ink);color:var(--white)}

/* ---- bottom tab bar ---------------------------------------------------- */
/* ---- bottom nav, from lib/widgets/navigationBar.dart -------------------
   The app's own icon ASSETS, copied to public/assets/icons/nav/, not emoji
   stand-ins: homeIn/homeOut, accountIn/accountOut, scanIconWhite. The app has
   five items (home, rewards, scan, trophy, account); this surface has three,
   because rewards and the leaderboard are not part of it.

   The scan control is the app's raised FAB: a 70px circle with a 6px white
   ring and the yellow fill (navigationBar.dart:506-511), overlapping the bar. */
.tabs{
  position:fixed;left:0;right:0;bottom:0;z-index:30;
  max-width:520px;margin:0 auto;
  background:var(--white);
  /* THE CURVE. `BorderRadius.only(topLeft: 35, topRight: 35)`
     (navigationBar.dart:56-59). This is what makes the bar read as a floating
     sheet the feed scrolls behind, instead of a strip welded to the screen. */
  border-radius:35px 35px 0 0;
  /* The app draws NO top border. It separates with a soft shadow instead
     (:62-68: black 8%, blur 8, offset 0,0). The 1px line that used to be here
     cut straight across the new corners and made them look like a mistake. */
  box-shadow:0 0 8px rgba(0,0,0,.08);
  display:flex;align-items:flex-end;
  padding-bottom:env(safe-area-inset-bottom,0px);
  height:calc(62px + env(safe-area-inset-bottom,0px));
}
.tabs a{
  flex:1;text-align:center;padding:9px 0 10px;
  font-size:10px;font-weight:600;color:var(--faint);
  display:flex;flex-direction:column;align-items:center;gap:2px;
}
.tabs a.on{color:var(--primary)}
.tabs a img{width:26px;height:26px;object-fit:contain;display:block}

.tabs a.fab{
  /* NO position:relative. The ring is positioned against `.tabs` itself, which
     is what the app does: the FAB is a sibling `Positioned(top: -18, left: 0,
     right: 0, child: Center(...))` inside the bar's own Stack
     (navigationBar.dart:241-246), not a child of a nav slot. Anchoring it to
     this <a> instead made it collapse the moment the label came out, because
     the ring is out of flow and left the anchor with nothing to give it
     height. The <a> stays as the middle flex slot and the tap target. */
  justify-content:flex-end;padding-bottom:10px;
}
.tabs a.fab .ring{
  /* THE OVERLAP. `Positioned(top: -18)` (navigationBar.dart:243): the circle's
     top edge sits exactly 18px above the bar's, so about a quarter of it
     breaks out and the rest sinks into the bar. -30px lifted it so far that it
     read as a separate floating button rather than one set into the bar. */
  position:absolute;top:-18px;left:50%;transform:translateX(-50%);
  width:70px;height:70px;border-radius:50%;
  border:6px solid var(--white);
  /* #FEB600, this widget's own `baisedYellow` (navigationBar.dart:492). NOT
     theme.dart's _baisedYellow (#F6BE3B) that --primary carries: navigationBar
     declares its own hotter yellow and the FAB is the only thing using it. */
  background:#FEB600;
  display:flex;align-items:center;justify-content:center;
  /* No shadow. The app's FAB has none (:506-511) — the 6px white ring is what
     separates it from the bar, and a drop shadow on top of that muddied it. */
}
.tabs a.fab .ring img{width:30px;height:30px}

/* NOT PORTED, deliberately: `_NavigationBarClipper` (navigationBar.dart:657)
   carves a 90x22 notch out of the bar's top edge under the FAB. It is dead
   code. The ClipPath sits INSIDE a Container whose decoration already paints
   the same white across the full rounded rect (:53-68), so the clip removes
   white from a region that is white underneath and nothing shows through.
   Reproducing it here would have needed a multi-layer CSS mask to render a
   difference the app itself does not render. */

/* ---- app-faithful primitives ------------------------------------------
   Two things the first pass got wrong across the whole build.

   1. SQUIRCLES. The app uses Flutter's ContinuousRectangleBorder in 36 places.
      That is a superellipse, not CSS's plain rounded rect, and the difference
      is visible at the corner of every button and field. `paint()` is not
      available in a LINE webview, so this approximates it the way iOS designers
      do on the web: a slightly larger radius than the Flutter value, which
      reads far closer than matching the number literally.

   2. BUTTON COLOUR IS PER CONTEXT. `theme.dart`'s _baisedYellow is the
      ElevatedButton default, but real screens override it. The auth screen uses
      #FF6B6B with WHITE text (signUpLogIn.dart:1372), not yellow with black.
      Do not assume the theme default is what a given screen actually shows. */

/* Flutter radius 16 -> 20px here; radius 24 -> 28px. */
.sq-16{border-radius:20px}
.sq-24{border-radius:28px}

/* The auth field's gradient border, painted by _SquircleGradientBorderPainter
   (referralCodeScreen.dart:530) as #F23540 -> #FEB600 left to right, 2px.
   Reproduced with the padding-box/border-box trick so the stroke is a real
   gradient rather than a flat approximation. */
.field--auth{
  /* radius 16 and a 2px stroke, both straight off
     _SquircleGradientBorderPainter(radius: 16, strokeWidth: 2) at :1351.
     It was 20 — the ~1.2x inflation .sq-16/.sq-24 use to fake a squircle
     with a circular arc. That convention does NOT apply here: this radius is
     painted directly, so the number is the number. */
  border:2px solid transparent;
  border-radius:16px;
  background:
    linear-gradient(var(--white),var(--white)) padding-box,
    linear-gradient(90deg,#F23540,#FEB600) border-box;
  padding:14px 16px;
  width:100%;font:inherit;font-size:16px;margin:0;
}
.field--auth:focus{outline:none}

/* ---- the sign-in hero (signUpLogIn.dart:1673) --------------------------
   The screen is a full-bleed vertical gradient with the white gift lockup on
   it, and the form sits in a white sheet with heavily rounded TOP corners
   overlapping it. Same four stops and stops[] as the app; the last one is
   #F6F7F9 so it fades into the page ground rather than ending on a colour. */
.auth{
  background:linear-gradient(180deg,
    #FF6B6B 0%, #FFBD71 40%, #EEDC82 70%, #F6F7F9 100%);
  min-height:100vh;
}
.auth__hero{
  display:flex;align-items:center;justify-content:center;gap:14px;
  /* The sheet takes 60% of the screen (:1798), so the lockup centres in the
     40% above it. */
  height:40vh;min-height:220px;
  padding-top:env(safe-area-inset-top,0px);
}
.auth__logo{width:100px;height:100px;object-fit:contain;flex:none}
.auth__word{
  font-size:44px;font-weight:900;color:var(--white);letter-spacing:-.01em;
  line-height:1;
}
/* The app writes `ContinuousRectangleBorder(top: 32 * 2)` (:1806) — and the
   doubling is the tell. Flutter's ContinuousRectangleBorder UNDER-renders its
   radius: the corner it draws is far tighter than a circular radius of the
   same number, so authors routinely pass ~2x to get the curve they want. The
   author doubled 32 for exactly that reason, which means the INTENDED visual
   radius is about 32, not 64.

   My first pass took the 64 at face value and then scaled it AGAIN through
   the squircle fit (to 96), which is why the corners came out roughly three
   times too round. It is also the wrong shape family: ContinuousRectangleBorder
   is not figma_squircle, so the superellipse pairing fitted elsewhere on this
   surface does not transfer. A plain 32px arc is the honest match. */
.auth__sheet{
  background:var(--white);
  border-radius:32px 32px 0 0;
  padding:40px 24px calc(40px + env(safe-area-inset-bottom,0px));
  min-height:60vh;
}
/* `signInToBaised`, centred above the form. */
.auth__title{
  font-size:24px;font-weight:900;color:var(--ink);
  text-align:center;margin-bottom:26px;line-height:1.25;
}

/* signUpLogIn.dart:1367-1377 — #FF6B6B, white label, min height 48, r24. */
/* minimumSize(double.infinity, 48), #FF6B6B on white, radius 24 (:1373-1379).
   28 was the same mistaken inflation as the field's; on a 48px-tall button
   that also tipped it past a true pill. At 24 it is exactly half the height,
   which is the pill the app draws — no more. */
.btn--auth{
  background:#FF6B6B;color:var(--white);
  border-radius:24px;min-height:48px;
  font-weight:600;
}

/* signUpLogIn.dart:1305-1322 — left aligned, 14px w600 grey800 title over a
   12px grey600 example line, 8px gap before the field. */
.auth-head{margin:6px 0 8px}
.auth-head b{display:block;font-size:14px;font-weight:600;color:#424242}
.auth-head span{display:block;font-size:12px;color:#757575;margin-top:2px}
.auth-note{font-size:12.5px;color:var(--muted);text-align:center;padding:18px 4px 0}
.btn--auth{margin-top:24px}

/* ---- OTP step, from signUpLogIn.dart `_stepTwo` (:1398) ---------------- */
.auth-row{display:flex;align-items:center;gap:4px;margin:2px 0 8px}
.auth-back{font-size:22px;line-height:1;color:#9E9E9E;padding:4px 8px 4px 0}
/* The phone step's title block, with the back arrow beside it rather than
   above: the arrow and the two-line title read as one row. */
.auth-head--back{display:flex;align-items:flex-start;gap:4px}
.auth-head--back .auth-back{margin-top:-2px}
.auth-row b{font-size:16px;font-weight:600;color:#616161}
.auth-sent{font-size:12.5px;color:var(--muted);margin-bottom:12px}
.auth-link{
  display:block;width:100%;text-align:center;margin-top:16px;
  font-size:14px;font-weight:500;color:#2196F3;padding:8px;
}
.auth-link[disabled]{color:#BDBDBD;pointer-events:none}

/* The real input sits invisibly over the boxes so one-time-code autofill has a
   single field to fill, which is exactly how the app stacks an invisible
   TextField over its six painted boxes. */
.otpx{position:relative;height:55px}
.otpx input{
  position:absolute;inset:0;width:100%;height:100%;
  opacity:0;border:0;background:none;font-size:16px;
  letter-spacing:2em;z-index:2;
}
.otpx input:focus{outline:none}
.otpx__boxes{display:flex;justify-content:space-evenly;height:100%;pointer-events:none}
.otpx__boxes i{
  width:45px;height:55px;flex:none;
  display:flex;align-items:center;justify-content:center;
  font-style:normal;font-size:20px;font-weight:700;
  color:#F23540;                       /* the app's digit colour, not black */
  border:2px solid transparent;
  /* Flutter r16. The squircle overshoot that helps on wide elements
     over-rounds a 45px box into a pill, so this one takes the literal value. */
  border-radius:16px;
  background:
    linear-gradient(var(--white),var(--white)) padding-box,
    linear-gradient(135deg,#F23540,#FEB600) border-box;   /* DIAGONAL here */
}
/* Active box: solid black border and a black caret (the app swaps the gradient
   painter for _SquircleSolidBorderPainter). */
.otpx__boxes i.on{
  border-radius:16px;
  background:linear-gradient(var(--white),var(--white)) padding-box,
             linear-gradient(#000,#000) border-box;
}
.otpx__boxes i.on:after{content:'';width:2px;height:20px;background:#000;animation:caret 1s step-end infinite}
@keyframes caret{50%{opacity:0}}

/* ---- forms ------------------------------------------------------------- */
.field{
  width:100%;padding:14px;border:1.5px solid var(--line);border-radius:var(--r-btn);
  font:inherit;font-size:16px;background:var(--white);margin-bottom:10px;
}
.field:focus{outline:none;border-color:var(--primary)}
.otp{display:flex;gap:8px;margin-bottom:12px}
.otp input{
  flex:1;min-width:0;height:52px;text-align:center;
  border:1.5px solid var(--line);border-radius:10px;
  font:inherit;font-size:22px;font-weight:700;background:var(--white);
}
.otp input:focus{outline:none;border-color:var(--primary)}

/* ---- states ------------------------------------------------------------ */
.centre{text-align:center;padding:28px 8px}
.centre .em{font-size:44px;line-height:1}
.centre b{display:block;font-size:17px;font-weight:700;margin-top:12px}
.centre p{font-size:13px;color:var(--muted);margin-top:8px}

.spinner{
  width:52px;height:52px;border-radius:50%;margin:36px auto 20px;
  border:4px solid rgba(0,0,0,.07);border-top-color:var(--primary);
  animation:spin 900ms linear infinite;
}
@keyframes spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.spinner{animation-duration:2.4s}}

/* Page-load splash (index.html, draw.html, /cintage/): the website's logo with
   a thin sliding bar, over the whole screen, instead of a spinner at the top of
   an empty white column. It sits inside #main, so the first render's
   main.replaceChildren() removes it and nothing has to hide it. BaiSed branding
   only: everyone sees it before the gating check has run. Below the LINE
   "Join on our website" screen (z-index 1000). */
.splash{
  position:fixed;inset:0;z-index:900;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:22px;
  background:var(--white);
}
.splash__logo{width:min(190px,52vw);height:auto;animation:splash-in .35s ease-out both}
.splash__bar{width:120px;height:3px;border-radius:3px;background:rgba(0,0,0,.07);overflow:hidden}
.splash__bar i{
  display:block;width:40%;height:100%;border-radius:inherit;background:var(--primary);
  animation:splash-slide 1.2s ease-in-out infinite;
}
@keyframes splash-in{from{opacity:0}}
@keyframes splash-slide{from{transform:translateX(-100%)}to{transform:translateX(250%)}}
@media (prefers-reduced-motion:reduce){
  .splash__logo{animation:none}
  .splash__bar i{animation-duration:2.4s}
}
/* Laptop: the landing page's own background, so the splash hands over without
   a flash. Same query as WIDE in draw_page.js. */
@media (min-width:900px) and (hover:hover) and (pointer:fine){.splash{background:#F7F7F9}}

.steps{font-size:13px;padding:0 4px}
.steps div{padding:7px 0;color:var(--faint)}
.steps div.done{color:var(--ink);font-weight:600}
.steps div.now{color:var(--primary);font-weight:700}

.empty{text-align:center;padding:44px 16px;color:var(--faint);font-size:13px}

.tips{background:color-mix(in srgb,var(--primary) 14%,transparent);border-radius:12px;padding:12px 14px;font-size:12px;color:var(--muted)}
.tips b{display:block;color:var(--ink);margin-bottom:5px;font-size:12px}
.tips li{list-style:none;padding-left:13px;position:relative;padding-top:2px}
.tips li:before{content:'•';position:absolute;left:2px}

/* the "credited draws" list on the scan result — one row per draw, because a
   single receipt can credit several campaigns at once */
.credited{border:1px solid var(--line);border-radius:12px;overflow:hidden;margin-top:14px}
.credited__r{display:flex;align-items:center;gap:10px;padding:11px 12px;border-bottom:1px solid var(--line)}
.credited__r:last-child{border-bottom:none}
.credited__sw{width:5px;height:30px;border-radius:3px;flex:none;background:var(--row-theme,var(--heat))}
.credited__m{flex:1;min-width:0}
.credited__m b{display:block;font-size:13px;font-weight:600}
.credited__m span{font-size:11px;color:var(--faint)}
.credited__p{font-weight:900;font-size:15px;color:var(--row-theme,var(--heat))}

.receipt{background:rgba(0,0,0,.03);border-radius:12px;padding:12px 14px;font-size:12.5px;color:var(--muted);margin-top:12px}
.receipt div{display:flex;justify-content:space-between;gap:12px;padding:3px 0}
.receipt b{color:var(--ink);font-weight:600;text-align:right}

.big{text-align:center;padding:18px 0 6px}
.big .em{font-size:44px;line-height:1}
.big .n{font-size:42px;font-weight:900;line-height:1.1;margin-top:6px;color:var(--primary)}
.big .l{font-size:13px;font-weight:600;color:var(--muted)}

.lock{
  display:inline-flex;align-items:center;gap:5px;
  background:rgba(0,0,0,.62);color:var(--white);
  border-radius:var(--r-badge);padding:6px 12px;
  font-size:12px;font-weight:600;
}

/* The app's slide-in toast (SlideToastService / _SlideToast).
   Anchored to the RIGHT EDGE with only the left corners rounded, so it reads as
   something that slid in from off-screen rather than a floating pill. */
.stoast{
  position:fixed;right:0;z-index:300;
  top:calc(env(safe-area-inset-top,0px) + 60px);   /* JS overrides when a .topbar is on screen */
  display:flex;align-items:center;gap:12px;
  max-width:80vw;                                   /* MediaQuery.size.width * 0.8 */
  padding:12px 16px;
  background:#fff;
  border-radius:12px 0 0 12px;                      /* left corners only */
  box-shadow:0 4px 10px rgba(0,0,0,.15);
  cursor:pointer;
  /* Tween(begin: Offset(1,0), end: zero) over 400ms on Curves.easeOutCubic. */
  transform:translateX(100%);
  transition:transform .4s cubic-bezier(.215,.61,.355,1);
}
.stoast.is-in{transform:translateX(0)}
.stoast__ic{flex:none;width:24px;height:24px;color:#FEB600}
.stoast__ic.is-error{color:#F44336}
.stoast__ic svg{width:24px;height:24px;fill:currentColor;display:block}
.stoast__tx{
  min-width:0;color:rgba(0,0,0,.87);font-weight:600;font-size:14px;line-height:1.35;
  /* maxLines: 2 with an ellipsis overflow */
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}
@media (prefers-reduced-motion: reduce){
  .stoast{transition:none;transform:translateX(0)}
}

.hide{display:none !important}

/* Legacy-browser fallback. LINE's in-app WebView on older Android predates
   color-mix(); without this the tinted surfaces render transparent and the
   theme-coloured text sits on bare white. */
@supports not (color: color-mix(in srgb, red 50%, transparent)){
  .entries{background:rgba(0,0,0,.05)}
  .tag{background:rgba(0,0,0,.06)}
  .row__ic{background:rgba(0,0,0,.06)}
  .tips{background:rgba(246,190,59,.16)}
  .card__unit,.entries__unit{color:var(--theme);opacity:.7}
}


/* ═══════════════════════════════════════════════════════════════════════════
   THE DRAW DETAIL PAGE
   Rebuilt from brand_lucky_draw_page.dart. The previous version was a flat
   tinted panel over a list of rows, which shares almost nothing with the app.
   Structure now mirrors `_buildDrawContent` (:1806): layered hero, pill tab
   bar with two swipeable pages, and an always-present sticky bottom bar.
   ═══════════════════════════════════════════════════════════════════════════ */

.dwrap{padding-bottom:calc(150px + env(safe-area-inset-bottom,0px))}

/* ---- hero (_buildHero :2202) ------------------------------------------- */
.dhero{
  position:relative;overflow:hidden;
  /* SQUARE top corners on purpose: the hero reaches the top of the screen and
     the device's own corner is meant to be the only curve there. */
  border-radius:0;
  /* heroGradient center -> outer, top to bottom. */
  background:linear-gradient(180deg,var(--hero-a,var(--theme)),var(--hero-b,var(--theme)));
  min-height:290px;
}
/* LOCKUP MODE. The artwork already carries the brand logo and the campaign
   title, so it fills the hero edge to edge at its OWN aspect ratio (set from
   naturalWidth/Height once loaded) and nothing but the countdown is drawn over
   it. Overscaled very slightly to hide the transparent edge margin these PNGs
   ship with, which otherwise lets the gradient peek through at the sides. */
/* THE WHITE LIP COVERS THE BOTTOM 40px OF THE HERO (see .dhero__lip:
   bottom:-60px, height:100px), which was hiding the bottom of the artwork
   underneath it. Reserve that strip as padding INSTEAD of letting the art run
   into it: content-box sizing means aspect-ratio now describes the ART AREA
   only, and the 40px is added below it. The hero grows by 40px, the art ends
   exactly where the white begins, and nothing is lost off the bottom of the
   image. */
.dhero--art{min-height:0;box-sizing:content-box;padding-bottom:40px}
/* Anchored to the BOTTOM, not centred: the hero box is intentionally shorter
   than the artwork so the app-only camera safe area at the top is cropped
   away (see HERO_SAFE_TOP in draw.html). Centring would eat the strip from
   both ends and clip the bottom of the composition instead. */
.dhero__art{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:50% 100%}
/* Stop the art at the lip's top edge. An EXPLICIT height, not `auto` with a
   `bottom`: for an absolutely positioned REPLACED element (an <img>) `height:auto`
   resolves to the image's intrinsic height and the `bottom` offset is simply
   ignored, so the art kept running its full length under the white. `100%` here
   is the hero's padding box, and subtracting the lip strip leaves exactly the
   content box the aspect-ratio sized. */
.dhero--art .dhero__art{height:calc(100% - 40px)}
/* ART SHOWN WHOLE (`heroArtAspect`, Cintage; draw_page.js `tuck`, owner
   2026-10-06): the app's geometry (_buildHero :2669). The hero is the art's
   own shape, uncropped; the art fills all of it; the white body's edge rides
   15px up over the art's bottom (Positioned bottom -120, height 135, :2728),
   so the lip's rounded top-right corner shows art, not the hero gradient; and
   the like capsule sits ON the hero's bottom edge (bottom:0, :2749). The
   0.6% zoom hides the art's 2px transparent left edge (scale 1.006, :2465). */
.dhero--tuck{padding-bottom:0}
.dhero--tuck .dhero__art{height:100%;transform:scale(1.006)}
.dhero--tuck .dhero__lip{bottom:-85px}
.dhero--tuck .dhero__like{bottom:0;transform:none}
/* Plain backdrop art: ~5% overscale to push the transparent edge margin these
   PNGs carry off-screen, so no gradient shows as a thin border (:2053).
   Lockup art is drawn at exact size, or the baked title gets clipped. */
.dhero__art.over{width:105%;height:105%;left:-2.5%;top:-2.5%}
/* In lockup mode the countdown anchors to the BOTTOM of the hero, in the clear
   space under the baked title, rather than sitting on top of the artwork. */
/* Only TRUE lockup art bottom-anchors the countdown into the clear space under
   the baked title. Plain backdrop art keeps the normal logo/title/countdown
   column on top of it. */
.dhero--lockup .dhero__body{
  position:absolute;left:0;right:0;bottom:0;
  max-width:100%;
  /* 54px, the app's own number (:2347). Not a free choice: the title is baked
     into the artwork and ends around 63% down the hero, so the countdown block
     has to start below that or the "ปิดรับสิทธิ" label lands on top of the
     painted "X TEETEE POR". 78px put the block at 57% — inside the title — and
     the only reason that ever looked acceptable is that the hero was being
     cropped, which shrank the artwork and moved the title up with it. With the
     aspect ratio now tracking the art, the app's 54 is what clears it. */
  padding:0 24px 66px;
}
/* The reward/cover image, right aligned and deliberately half tucked BEHIND
   the white lip below it. */
/* The "stand" backdrop, sitting at the bottom of the hero BEHIND the cover. */
.dhero__stand{
  position:absolute;left:0;right:0;bottom:0;width:100%;
  height:44%;object-fit:cover;object-position:bottom;opacity:.95;
}
.dhero__cover{
  position:absolute;right:-36px;bottom:-46px;
  height:230px;width:auto;object-fit:contain;pointer-events:none;
}
/* RESULTS: the prize is the only thing in the hero, so it sits CENTRED on the
   podium rather than tucked bottom-right beside a title column that no longer
   exists. The stand is 44% tall from the bottom, so lifting the prize to 24%
   lands it on the podium's face instead of half under the white lip. */
.dhero--results .dhero__cover{
  left:50%;right:auto;bottom:24%;
  transform:translateX(-50%);
  height:190px;
}
/* The white container lip. Only the TOP-RIGHT corner is rounded, at 28px, and
   it paints OVER the cover image so the image reads as sliding underneath. */
.dhero__lip{
  position:absolute;left:0;right:0;bottom:-60px;height:100px;
  background:var(--white);
  border-top-right-radius:28px;
  pointer-events:none;
}
.dhero__body{
  position:relative;z-index:2;
  padding:calc(18px + env(safe-area-inset-top,0px)) 16px 54px 24px;
  max-width:66%;
}
.dhero__logo{height:56px;width:auto;object-fit:contain;margin-bottom:8px}
.dhero__brand{
  font-size:12px;font-weight:800;letter-spacing:.06em;
  color:rgba(255,255,255,.9);text-transform:uppercase;
}
.dhero__title{
  font-size:20px;font-weight:800;line-height:1.25;color:var(--white);
  margin-top:4px;text-shadow:0 1px 3px rgba(0,0,0,.18);
}
/* HERO COUNTDOWN. Not a pill: an "Entry closes" label over four yellow boxes,
   exactly the treatment `_buildHeroCountdownBody` (:3275) gives Golden
   Mountain. Box #FFDB62, number 18px and unit 10px both in the brand red, and
   the separators in the box colour so they read as spacing not punctuation. */
/* The app wraps this in SizedBox(width: 260) + Center, with the countdown
   itself in a further SizedBox(width: 180) whose four cells are Expanded
   (:3296). So the row is a FIXED 180 wide with four equal cells — not four
   content-sized boxes — and the label is CENTRED over it, not left-aligned. */
/* transform-origin bottom: the scaleDown shrinks the block UPWARD, away from
   the like capsule, leaving its bottom edge where the padding put it. */
/* LEFT-ALIGNED, under the baked title — the app's Alignment.bottomLeft (:2350).
   The title occupies 1.2%-49.4% of the artwork's width (measured off the PNG),
   so a centred block sat to the right of the text it belongs to. Width is the
   box row's own 180px rather than the app's 260px wrapper: the app centres 180
   inside 260 to get its offset, and starting from the body's 24px padding
   reaches the same place with one less number to keep in sync. The label still
   centres over the boxes, which is what the app's inner Center does.

   transform-origin is bottom-LEFT so the scaleDown shrinks toward the title
   rather than away from it, keeping the alignment at any scale. */
.dhero__cdwrap{margin-top:14px;width:180px;max-width:100%;text-align:center;transform-origin:0% 100%}
.dhero__cdlbl{
  /* 11px w600 and WHITE. The Dart calls this out: the label sits on the hero
     PHOTO, where the near-black used elsewhere was barely legible — which is
     exactly what it was doing here. The shadow is kept, flipped to dark, so it
     survives a light patch of artwork. */
  font-size:11px;font-weight:600;color:#fff;margin-bottom:8px;
  text-shadow:0 1px 3px rgba(0,0,0,.45);
}
.dhero__cd{
  display:flex;align-items:center;gap:3px;
  /* fills the wrap now that the wrap is the box row's width */
  width:100%;max-width:100%;margin:0;
}
/* Expanded cells: the four share the 180px evenly instead of sizing to their
   own text, which left them narrow and the row short of the app's. */
.dhero__cd i{flex:1 1 0;min-width:0}
.dhero__cd i{
  /* compact:true -> radius 8 and padding 4/2 (brand_lucky_draw_page:14668),
     the same cell chrome as the home card. 10px/5px/44px was wider and
     rounder than the app's, which is why the row read as four big pills. */
  font-style:normal;background:var(--dcd-box,#FFDB62);border-radius:8px;
  padding:4px 2px;text-align:center;
  display:flex;flex-direction:column;line-height:1.05;
}
.dhero__cd i b{font-size:18px;font-weight:900;color:var(--cd-num,#DD1F22)}
.dhero__cd i span{font-size:10px;font-weight:700;color:var(--dcd-unit,var(--cd-num,#DD1F22));opacity:.85}
.dhero__cd s{text-decoration:none;color:var(--dcd-sep,#FFDB62);font-weight:900;font-size:14px}
/* DOC COLOURS / FULL-BLEED HERO (Cintage). The --dcd-* properties above are
   set by draw.html only when the draw carries `countdownBoxColor` /
   `countdownTextColor` or `heroArtFullBleed`, and their defaults are the
   literals this skin always had, so Golden Mountain computes exactly as
   before. This modifier adds the app's real compact geometry
   (BrandLuckyDrawCountdown `_box` / `_sep`, brand_lucky_draw_page.dart:18110,
   :18084): two HARD lips at (0,3) and (0,4.5) in Color.lerp(box, black,
   .14 / .26), number height 1.0, a 1px gap then the unit at w600 in full
   colour, and a 13px ":" with 2px either side instead of the flex gap. */
.dhero__cd--doc{gap:0}
.dhero__cd--doc i{box-shadow:0 3px 0 var(--dcd-sh1,#E6D5B3), 0 4.5px 0 var(--dcd-sh2,#D4C2A1)}
.dhero__cd--doc i b{line-height:1}
.dhero__cd--doc i span{font-weight:600;opacity:1;margin-top:1px}
.dhero__cd--doc s{font-size:13px;padding:0 2px}
/* Like capsule, bottom left, straddling the hero and the white body. */
.dhero__like{
  /* STRADDLES THE SEAM. The photo ends at the lip's top edge, 40px above the
     hero's bottom; `bottom:0` therefore parked the whole capsule 40px INSIDE
     the white body, which is why it read as sitting under the image instead of
     across it. Sit it ON the seam (bottom:40px) and then push it down by half
     its own height, so the artwork carries the top half and the white body the
     bottom half. translateY(50%) rather than a hardcoded offset, so the
     capsule can change height without re-tuning this. */
  position:absolute;left:0;bottom:40px;transform:translateY(50%);z-index:3;
  display:flex;align-items:center;gap:12px;
  background:var(--white);
  padding:9px 16px 9px 18px;
  border-top-right-radius:999px;border-bottom-right-radius:999px;
  box-shadow:0 2px 8px rgba(26,26,46,.12);
  font-size:16px;font-weight:700;
}
/* 22px Material glyph in Colors.red, and a 12px gap to the count (:768-770);
   the emoji was 20px with a 9px gap. */
.dhero__like .heart{width:22px;height:22px;flex:none;display:block}
.dhero__like .heart svg{width:22px;height:22px;display:block;fill:#F44336}
/* Colors.grey.shade700 at 15px — it was near-black at 16px. */
.dhero__like .n{color:#616161;font-size:15px}
/* Colors.grey.shade300 (:782), NOT the page's 8% hairline — at rgba(0,0,0,.08)
   this divider was about a third of the app's contrast and read as missing. */
.dhero__like .sep{width:1px;height:20px;background:#E0E0E0}
/* The glyph carries its own ring, so no border and no text styling here —
   just the app's 22px box in black (:795-799). */
.dhero__like .help{
  width:22px;height:22px;flex:none;
  display:flex;align-items:center;justify-content:center;
}
.dhero__like .help svg{width:22px;height:22px;display:block;fill:#000}

/* Big rule headline that opens the How-to-join tab. */
.drule{font-size:19px;font-weight:800;line-height:1.35;margin:16px 0 6px}
.dsec{font-size:17px;font-weight:800;margin:22px 0 4px}
.dsub{font-size:13.5px;color:var(--faint);margin-bottom:10px;line-height:1.45}

/* ---- participating products (CATEGORIES, each holding SKUs) -----------
   Deck geometry from brand_lucky_draw_page.dart:70-78: a 124px box holding a
   104px front card, each card behind offset 8px DOWN-RIGHT, no rotation. The
   cards behind are not decoration: there is one per SKU in the category, which
   is why a two-SKU category shows a thinner stack than a five-SKU one.
   Tile colour comes from the doc's `tileColor`, default #EE7D42. */
.prodrow{display:flex;gap:12px;overflow-x:auto;scrollbar-width:none;
  margin:0 calc(var(--pad-page) * -1);padding:6px var(--pad-page) 12px}
.prodrow::-webkit-scrollbar{display:none}
.prodrow>li{list-style:none;flex:0 0 auto;width:124px;cursor:pointer}
.prodrow .skudeck{position:relative;width:124px;height:124px}
.prodrow .skudeck .card{
  position:absolute;top:10px;left:10px;width:104px;height:104px;
  border-radius:16px;overflow:hidden;background:var(--tile,#EE7D42);
  display:flex;align-items:center;justify-content:center;
  transition:transform .12s ease;
}
/* FULL BLEED inside the tile. `_deckCard` puts the image in a
   Stack(fit: StackFit.expand) with BoxFit.contain, so it fills the whole
   104x104 and only letterboxes on its own aspect ratio. The 82% inset here
   shrank every bottle by ~18% against the app and was the most visible
   difference in the product row. */
.prodrow .skudeck .card img{width:100%;height:100%;object-fit:contain}
/* Behind-cards are darkened so they read as sitting under the front one:
   black at 0.10 * i (:129), i.e. a 10% then 20% multiply. No rotation —
   kDeckRot is 0.0, offset only. */
.prodrow .skudeck .card.b1{
  transform:translate(calc(var(--off,8) * 1px),calc(var(--off,8) * 1px));filter:brightness(.9)}
.prodrow .skudeck .card.b2{
  transform:translate(calc(var(--off,8) * 2px),calc(var(--off,8) * 2px));filter:brightness(.8)}
/* Per-product deck fields (owner, 2026-10-04, Cintage). `deckBacks:"plain"`
   cards are flat tints, never darkened. `tileBorder` (--tb, pt) insets the
   image in .card__in so the tile colour frames it (_framedTileImage). */
.prodrow .skudeck .card.card--plain{filter:none}
.skudeck .card__in,.skugrid .tile .card__in{
  position:absolute;inset:calc(var(--tb,0) * 1px);border-radius:inherit;overflow:hidden;
  display:flex;align-items:center;justify-content:center;
}
.skudeck .card__in img,.skugrid .tile .card__in img{width:100%;height:100%;object-fit:contain}
.prodrow>li:active .skudeck .card{transform:translate(0,0) scale(.97)}
/* 12px w600 #1A1A1A, two lines max, 18px below the deck, and indented by
   kDeckBase so it lines up with the FRONT card's left edge rather than the
   124 box's (:4144). It was 14px/700, flush left, 10px down and unclamped. */
.prodrow .nm{
  font-size:12px;font-weight:600;color:#1A1A1A;line-height:1.3;
  margin:18px 0 0 10px;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;
}

/* ---- product TYPES (phoneProducts; _buildParticipatingProducts :5280) ---
   A title per type, 13.5 w800 #1A1A1A, the "Participating products" label at
   its right end when every row has a title (9 w400 black45, 12 left), an
   optional caption 12.5 w500 black45 2 under it. 12 + 14 above the first
   title (no heading on top), 4 above each later one. */
.dprods--inline{margin-top:26px}
.dprods__list{margin-top:10px}
.dprods--inline .dprods__list,.dprods__list--cav{margin-top:0}
.dpg__head--next{margin-top:4px}
.dpg__head--cav{margin-top:8px}
.dpg__head--flat{margin-bottom:4px}
.dpg{display:flex;align-items:baseline;gap:12px}
.dpg__t{flex:1;min-width:0;font-size:13.5px;line-height:1.3;font-weight:800;color:#1A1A1A}
.dpg__h{flex:none;font-size:9px;font-weight:400;color:rgba(0,0,0,.45)}
.dpg__c{margin-top:2px;font-size:12.5px;font-weight:500;color:rgba(0,0,0,.45)}
/* snug: right under a title, no top padding; the deck box's 10px base is the
   only gap (:5584). flat: single photos, no deck room: 4 above the 104 photo,
   its name 8 under it, indented by kDeckBase like the deck's front card. */
.prodrow--snug{padding-top:0}
.prodrow--flat{padding-top:4px}
.prodrow--flat .skudeck{height:104px}
.prodrow--flat .skudeck .card{top:0}
.prodrow--flat .nm{margin-top:8px}
/* "See all N products" / "Show less" (_productsToggle :5505): full width, 42
   tall, #F4F2F5 stadium, 13.5 w700 #1A1A1A, a 20px chevron 4 after it that
   turns over in 280ms; 6 below. */
.dprods__more{
  display:flex;align-items:center;justify-content:center;gap:4px;
  width:100%;height:42px;margin:0 0 6px;border:0;border-radius:999px;
  background:#F4F2F5;font:inherit;font-size:13.5px;font-weight:700;color:#1A1A1A;cursor:pointer;
}
.dprods__more:active{filter:brightness(.96)}
.dprods__more svg{width:20px;height:20px;fill:currentColor;transition:transform .28s ease}
.dprods__more.is-open svg{transform:rotate(180deg)}

/* ---- category SKU panel ------------------------------------------------ */
/* A CENTRED DIALOG, not a bottom sheet. The app's container is a Rect
   centred at (width/2, height*0.45) — deliberately above true centre — width
   min(vw - 32, 420), height capped at 80vh (:9166). It grows out of the
   tapped card. Scrim is black 60%, not 50%. */
.skusheet{
  position:fixed;inset:0;z-index:190;
  background:rgba(0,0,0,.6);
  display:flex;align-items:center;justify-content:center;
  animation:lbIn .16s ease-out;
}
.skusheet__body{
  position:relative;
  /* 45% of the viewport, so the panel sits slightly high — matching
     Rect.fromCenter(center: (w/2, h*0.45)). */
  margin-bottom:10vh;
  width:calc(100vw - 32px);max-width:420px;
  background:var(--white);
  /* SmoothRectangleBorder(cornerRadius: 28, cornerSmoothing: 0.6) — NOT the
     1.0 the cards use, so it needs its own fit: r42 + superellipse(1.6)
     tracks it to 0.31px, with r30 the best plain-circular fallback. */
  border-radius:30px;
  padding:20px;
  max-height:80vh;overflow-y:auto;
  /* black 28%, blur 30, offset (0,14). */
  box-shadow:0 14px 30px rgba(0,0,0,.28);
  animation:skuPop .28s cubic-bezier(.22,.61,.36,1);
  transform-origin:var(--ox,50%) var(--oy,50%);
}
@supports (corner-shape: superellipse(1.6)){
  .skusheet__body{border-radius:42px;corner-shape:superellipse(1.6)}
}
/* easeOutCubic, growing from the tapped card toward its centred spot. */
@keyframes skuPop{
  from{transform:scale(.28);opacity:0}
  to{transform:scale(1);opacity:1}
}
@media (prefers-reduced-motion:reduce){.skusheet,.skusheet__body{animation:none}}
.skusheet__head{text-align:center;margin-bottom:18px;padding:0 26px}
/* 20px w900 #1A1A1A, letter-spacing -0.3 (:9265). */
.skusheet__title{
  font-size:20px;font-weight:900;color:#1A1A1A;letter-spacing:-.3px;
  line-height:1.25;
}
/* 12.5px w500 #8A929B, 3px below the title (:9280). */
.skusheet__sub{font-size:12.5px;font-weight:500;color:#8A929B;margin-top:3px}
/* Circular grey ✕ at the container's top-right (:9309). */
.skusheet__x{
  position:absolute;top:10px;right:12px;
  width:30px;height:30px;padding:6px;border-radius:50%;
  background:#F1F3F5;display:flex;align-items:center;justify-content:center;
}
.skusheet__x svg{width:18px;height:18px;fill:#5F6B76;display:block}
/* Wrapping grid, NOT a horizontal scroller: the app uses a Wrap so you cannot
   mis-swipe sideways (:10031). */
.skugrid{display:flex;flex-wrap:wrap;justify-content:center;gap:16px 14px}
.skugrid>div{width:104px;cursor:pointer}
.skugrid .tile{
  position:relative;width:104px;height:104px;border-radius:16px;overflow:hidden;
  background:var(--tile,#EE7D42);
  display:flex;align-items:center;justify-content:center;
}
.skugrid .tile img{width:100%;height:100%;object-fit:contain}
/* _buildSkuTile (:9686): 12px w800, centred, 8px gap, two lines. */
.skugrid .nm{
  font-size:12px;font-weight:800;color:#1A1A1A;text-align:center;
  margin-top:8px;line-height:1.25;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;
}

/* Bottom-bar entry block + the two gradient action buttons. */
.dbar__entries{display:flex;align-items:center;gap:9px;margin-top:3px}
.dbar__ticket{height:30px;width:auto;flex:none}
.dbar__label .chev{font-size:13px;opacity:.5}
/* The app's _ScanSplitButtons (:14981). Each half is an Expanded, so the pair
   SPLITS the width the entry block leaves instead of being two fixed chips —
   that is most of why this row read so much smaller than the app's. Gap is
   kLuckyDrawCtaGap = 8. */
/* Right-aligned once the halves stop stretching, so they stay against the
   bar's own padding exactly as the app's Expanded pair does. */
.dbar__acts{display:flex;justify-content:flex-end;gap:8px;flex:1;min-width:0;margin-left:14px}
.dbar__act{
  position:relative;flex:1;min-width:0;
  /* CAP THE STRETCH. The halves are Expanded, which is right on a phone but
     the mini's shell is up to 520px on a desktop preview — there they grew to
     147px each against the ~75px they measure at 375px, which is what read as
     "too big". 100px leaves every real phone width fluid (a 430pt Pro Max lands
     around 103 and is unaffected in practice) and only clamps the desktop
     frame back to phone proportions. The app never renders wider than a phone,
     so it has no equivalent and needs none. */
  max-width:100px;
  /* _h 48 + _depth 4. The extra 4 is the depth slab showing below the face. */
  height:52px;display:block;
}
/* The 3D slab: a SECOND gradient, hotter than the face (#FD6900 -> #FF0000,
   :7437), sitting 4px lower. Same button-push language as the campaign cards. */
.dbar__act-depth{
  position:absolute;left:0;right:0;top:4px;height:48px;
  border-radius:14px;
  background:linear-gradient(90deg,#FD6900,#FF0000);
}
.dbar__act-face{
  position:absolute;left:0;right:0;top:0;height:48px;
  border-radius:14px;
  /* centerLeft -> centerRight, so 90deg. The old 135deg tilted a gradient the
     app draws flat. */
  background:linear-gradient(90deg,#FFAB70,#FF8989);
  display:flex;align-items:center;justify-content:center;
  /* white 35%, offset (0,-2), blur 4, spread -1 — an inner top highlight, not
     a drop shadow. Dropped on press. */
  box-shadow:inset 0 2px 4px -1px rgba(255,255,255,.35);
  transition:transform .09s ease-out, box-shadow .09s ease-out;
}
.dbar__act:active .dbar__act-face{transform:translateY(4px);box-shadow:none}
.dbar__act-face svg{width:22px;height:22px;fill:var(--white);display:block}

/* ---- the campaign mini-game button (_TesterOrderIdButton :15422) --------
   54x48 + 4 depth, radius 14, sitting kLuckyDrawCtaGap (8) from the scan
   halves. FIXED width, not a third equal share: the two scan CTAs are the
   Expanded pair and this is their fixed-width sibling. */
.dbar__game{
  position:relative;flex:0 0 54px;width:54px;height:52px;display:block;
}
/* The depth slab is the theme darkened 16% in HSL LIGHTNESS. CSS has no
   lightness operator, so `color-mix` toward black is the closest single-step
   equivalent; the exact HSL shade would need the colour split in JS. */
.dbar__game-depth{
  position:absolute;left:0;right:0;top:4px;height:48px;border-radius:14px;
  background:color-mix(in srgb, var(--theme) 78%, black);
}
/* Face is a TINT, not the solid accent — it has to stay visibly secondary
   next to the vivid scan CTAs. */
.dbar__game-face{
  position:absolute;left:0;right:0;top:0;height:48px;border-radius:14px;
  background:color-mix(in srgb, var(--theme) 8%, white);
  border:1.5px solid var(--theme);
  display:flex;align-items:center;justify-content:center;
  transition:transform .09s ease-out;
}
.dbar__game:active .dbar__game-face{transform:translateY(4px)}
/* A white-on-transparent monochrome mark recoloured with srcIn in Flutter —
   a CSS mask is the faithful equivalent. `background` alone would show the
   artwork's own colours instead of the theme. */
.dbar__game-ic{
  width:26px;height:26px;display:block;background:var(--theme);
  -webkit-mask:url('../../assets/luckyDraw/matchIcon.webp') center/contain no-repeat;
  mask:url('../../assets/luckyDraw/matchIcon.webp') center/contain no-repeat;
}
@supports not (color: color-mix(in srgb, red 50%, transparent)){
  .dbar__game-depth{background:var(--theme)}
  .dbar__game-face{background:#fff}
}
/* Circular translucent back button, 32px, white at 50%. */
.dback{
  position:absolute;z-index:4;
  top:calc(16px + env(safe-area-inset-top,0px));left:16px;
  width:32px;height:32px;border-radius:50%;
  background:rgba(255,255,255,.5);
  display:flex;align-items:center;justify-content:center;
  font-size:18px;line-height:1;color:#1a1a2e;
}

/* ---- pill tab bar (_buildTabBar :3532) --------------------------------- */
.dtabs{
  position:relative;height:50px;margin:10px 24px 12px;padding:4px;
  background:#F1F4EE;border-radius:999px;
  border:1.5px solid color-mix(in srgb,var(--theme) 30%,transparent);
  display:flex;
}
.dtabs__ind{
  position:absolute;top:6px;bottom:6px;left:6px;width:calc(50% - 12px);
  border-radius:999px;background:var(--accent,var(--theme));
  box-shadow:0 2px 4px rgba(0,0,0,.1);
  transition:transform .22s cubic-bezier(.2,.8,.2,1);
  /* Promotes this to its own compositor layer, so the slide runs off the main
     thread instead of competing with the height-clip resize and the two class
     toggles happening in the same tap (draw.html showTab). Cheap insurance
     against exactly the kind of frame the read/write reorder there fixed. */
  will-change:transform;
}
/* The indicator is inset 6px on each side, so its width is (50% - 12px) of the
   track. translateX(100%) therefore moves it by its OWN width and lands 12px
   short of the second segment. It has to travel a full HALF TRACK, which is its
   own width plus those 12px. */
.dtabs__ind.r{transform:translateX(calc(100% + 12px))}
.dtabs button{
  position:relative;z-index:2;flex:1;border-radius:999px;
  font-size:14px;font-weight:700;color:rgba(0,0,0,.55);
  transition:color .18s ease;
}
.dtabs button.on{color:var(--white)}

/* ---- swipeable pages ---------------------------------------------------- */
/* overflow-y:hidden is LOAD-BEARING, not tidiness. `overflow-x:auto` alone
   makes the other axis compute to `auto` too (CSS spec), so the pager became
   a VERTICAL scroller the moment its JS-pinned height came out shorter than
   its content — an image landing late is enough. It then swallowed the
   scroll gesture: the section scrolled inside its own box, the document did
   not move, and the hero sat frozen at the top of the screen. */
.dpages{display:flex;overflow-x:auto;overflow-y:hidden;scroll-snap-type:x mandatory;scrollbar-width:none}
.dpages::-webkit-scrollbar{display:none}
/* min-width:0 is load-bearing. A flex item defaults to min-width:auto, so the
   store carousel's negative-margin bleed pushed page one to 552px inside a
   375px pager. The snap points then never lined up with the tab indicator and
   swiping did nothing. */
/* align-items:flex-start so a section keeps its OWN height instead of being
   stretched to the tallest one; the pager's height is then pinned to the
   active section from JS (syncPagerHeight). Without both, the short prizes
   tab inherited the long "how to join" page's height and you could scroll a
   screenful past the end of it. */
.dpages{align-items:flex-start}
.dpages>section{flex:0 0 100%;min-width:0;scroll-snap-align:start;padding:0 var(--pad-page)}

/* ── the height clip, on a SEPARATE outer element ──────────────────────────
   CORRECTION: an earlier version of this comment claimed the app "animates
   nothing" for the height and only slides horizontally. That was wrong — re-read
   against the actual source, `_buildSwipeablePages` wraps its PageView in
   `AnimatedContainer(duration: 280ms, curve: easeOutCubic, height:
   _activePageHeight)` (brand_lucky_draw_page.dart:3631-3633), the SAME duration
   as `animateToPage(280ms)` for the slide. The app animates height and position
   together, in lock-step. This file previously set height with no transition at
   all, which does not avoid jank — it swaps slow-frame-drop jank for a violent
   instant snap: tapping "Prizes" while on the ~5600px "How to join" page on a
   phone collapsed the container by thousands of pixels in a single frame,
   before the horizontal slide had even started moving. That IS the stutter.

   The fix is not "animate .dpages's own height" — that was the ORIGINAL
   mistake this comment used to warn against, and the warning about relayout
   cost is real for that specific element: `.dpages` is the scroll-snap
   container, and animating a property that changes its own scrollport can make
   the browser re-evaluate scroll-snap targets on every frame of the transition.
   Instead, matching the Dart structure exactly, the animated height lives on
   `.dpager`, a plain `overflow:hidden` block wrapping `.dpages` — `.dpages`
   itself keeps `height:auto` (so it naturally sizes to its tallest section,
   same as `align-items:flex-start` already implies) and never has its own
   height transitioned. Only the ancestor's clip line animates, which is a
   cheap paint-level resize with no scroll-snap recomputation involved, and
   nothing sits below `.dpages` in the DOM to reflow underneath it. */
.dpager{overflow:hidden}
/* Scoped to a class, not the base rule, and only added for the duration of a
   deliberate tab tap (draw.html showTab). A passive re-measurement — an image
   landing late, the FAQ opening, a resize — calls syncPagerHeight() without
   this class and snaps instantly, exactly as before: correcting a mismeasure
   is not motion and does not need to look like it. */
.dpager.is-switching{transition:height .28s cubic-bezier(.215,.61,.355,1)}

/* ---- horizontal carousels (stores / products) -------------------------- */
.carousel{
  display:flex;gap:10px;overflow-x:auto;
  scroll-snap-type:x proximity;scrollbar-width:none;
  /* Bleed to the screen edges so the row can scroll past the page gutter,
     then re-inset the first and last item with padding. Declaring `padding`
     twice previously reset the top/bottom and broke the inset. */
  margin:0 calc(var(--pad-page) * -1);
  padding:2px var(--pad-page) 6px;
}
.carousel::-webkit-scrollbar{display:none}
.carousel>li{
  list-style:none;flex:0 0 auto;scroll-snap-align:start;
  width:96px;text-align:center;
}
.carousel .logo{
  width:56px;height:56px;margin:0 auto 6px;border-radius:14px;
  background:var(--white);border:1px solid var(--line);
  display:flex;align-items:center;justify-content:center;overflow:hidden;
}
.carousel .logo img{width:100%;height:100%;object-fit:contain}
.carousel .nm{font-size:11.5px;font-weight:600;line-height:1.25;color:var(--muted)}

/* ---- prize list --------------------------------------------------------- */
.prizehdr{
  display:flex;align-items:baseline;gap:8px;margin:18px 0 8px;
  font-size:14px;font-weight:800;color:var(--theme);
}
.prizehdr span{margin-left:auto;font-size:12px;font-weight:600;color:var(--faint)}
.prizerow{display:flex;align-items:center;gap:11px;padding:11px 0;border-bottom:1px solid var(--line)}
.prizerow:last-child{border-bottom:none}
.prizerow img{width:46px;height:46px;border-radius:11px;object-fit:cover;background:var(--grey50);flex:none}
.prizerow .m{flex:1;min-width:0}
.prizerow .m b{display:block;font-size:14px;font-weight:700}
.prizerow .m span{font-size:12px;color:var(--faint)}

/* ---- sticky bottom bar (_buildStickyBottomBar :7180) ------------------- */
.dbar{
  position:fixed;left:0;right:0;bottom:0;z-index:40;
  max-width:520px;margin:0 auto;background:var(--white);
  box-shadow:0 -4px 16px rgba(0,0,0,.06);
}
/* 1px pink -> yellow gradient top border, the same colours as the CTA face. */
.dbar:before{
  content:'';display:block;height:1px;
  background:linear-gradient(90deg,#FFAB70,#FF8989);
}
.dbar__in{
  padding:10px 20px calc(10px + env(safe-area-inset-bottom,0px));
  display:flex;align-items:center;gap:12px;
}
/* Content-sized, NOT an equal half. `flex:1` here split the bar 50/50 with
   the action row, which squeezed the two scan CTAs to 42px once the game
   button joined them. In the app the entry block takes its intrinsic width
   and the CTA row is the Expanded one that absorbs the rest. */
/* A <button> since the app makes this a tap target. Form controls
   shrink-wrap and carry UA chrome, so both are stripped. */
.dbar__left{flex:0 1 auto;min-width:0;display:block;padding:0;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer}
.dbar__label{font-size:12px;font-weight:700;color:var(--muted);display:flex;align-items:center;gap:4px}
.dbar__n{font-size:22px;font-weight:900;line-height:1.1;color:var(--theme)}
.dbar__n small{font-size:11px;font-weight:700;margin-left:3px;color:var(--faint)}
.scanbtn{
  flex:none;padding:13px 22px;border-radius:999px;
  background:linear-gradient(90deg,#FFAB70,#FF8989);
  color:var(--white);font-size:15px;font-weight:700;
  box-shadow:0 3px 10px rgba(255,137,137,.4);
}
/* Two lines in a cream box, not a grey chip: the state on top and the
   announcement date under it, so someone who missed the window still learns
   when to come back. */
.dbar__closed{
  flex:0 1 auto;min-width:0;margin-left:auto;
  padding:9px 18px;border-radius:14px;text-align:center;
  background:#FFF6E5;
  display:flex;flex-direction:column;gap:1px;
}
.dbar__closed b{font-size:14px;font-weight:800;color:#8A6A17;line-height:1.2}
.dbar__closed span{font-size:11px;font-weight:600;color:rgba(138,106,23,.8);line-height:1.2}
/* THE APP'S CLOSED BOX (`_buildClosedCtaBox`, brand_lucky_draw_page.dart
   :8596), only on draws with their own countdown colours or a full-bleed hero
   (draw.html sets the class and the --dcd-* values). It takes the CTA's slot
   exactly as the app's Expanded does (same flex/margin as .dbar__acts), 48
   tall, radius 8, padding 0 10, the two hard lerp lips, "closed" 15px w900
   height 1.0, a 2px gap, the date 10px w600 at 85% ink. The app scales an
   overlong line down (FittedBox); here it ellipsizes. */
.dbar__closed--app{
  flex:1;min-width:0;margin-left:14px;
  height:48px;padding:0 10px;border-radius:8px;
  justify-content:center;align-items:center;gap:2px;
  background:var(--dcd-box,#FFF6E5);
  box-shadow:0 3px 0 var(--dcd-sh1,#DBD4C5), 0 4.5px 0 var(--dcd-sh2,#BDB6A9);
}
.dbar__closed--app b,.dbar__closed--app span{
  max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.dbar__closed--app b{font-size:15px;font-weight:900;line-height:1;color:var(--dcd-ink,var(--theme))}
.dbar__closed--app span{font-size:10px;font-weight:600;color:var(--dcd-ink,var(--theme));opacity:.85}
/* baht_spent draws show progress toward the next entry above the row. */
/* baht_spent progress (`_buildBahtSpentProgressBar`): Padding(bottom 10), a
   24px bar with BorderRadius.circular(12); track = theme @ 0.12; fill =
   _kProgressFillGradient #FFD000 -> #FF2D2D (Mellolica #FF7EC6 -> #FF2D8F);
   "{banked} / {per} ฿" right-aligned, padding 0 10, Switzer 12 w800, theme on
   the track and white over the fill (the white copy is clipped to the fill). */
.dbar__prog{padding:10px 20px 0}
.dbar__pbar{position:relative;height:24px;border-radius:12px;overflow:hidden;background:rgba(0,0,0,.06)}
@supports (color: color-mix(in srgb, red 50%, transparent)){
  .dbar__pbar{background:color-mix(in srgb,var(--pb-theme) 12%,transparent)}
}
.dbar__pfill{position:absolute;left:0;top:0;bottom:0;background:linear-gradient(90deg,#FFD000,#FF2D2D)}
.dbar__pbar--mello .dbar__pfill{background:linear-gradient(90deg,#FF7EC6,#FF2D8F)}
.dbar__plbl{position:absolute;inset:0;display:flex;align-items:center;justify-content:flex-end;
  padding:0 10px;font-size:12px;font-weight:800;line-height:1;color:var(--pb-theme);white-space:nowrap}
.dbar__plbl--on{color:#fff}
/* Entry ticks: SizedBox(5) under the count, 14px icons 3px apart;
   check_circle Colors.green #4CAF50, check_circle_outline grey.shade400 #BDBDBD. */
.dbar__ticks{display:flex;gap:3px;margin-top:5px}
.dbar__tick,.dbar__tick svg{width:14px;height:14px;display:block}
.dbar__tick svg{fill:#BDBDBD}
.dbar__tick--on svg{fill:#4CAF50}
/* entriesDrawer: keyboard_arrow_up_rounded 16 black38 beside the label,
   turned pi * t with the drawer (open 520ms / close 420ms easeInOutCubic). */
.dbar__drw{display:inline-flex;width:16px;height:16px;margin-left:2px;vertical-align:-3px;
  transition:transform .42s cubic-bezier(.65,0,.35,1)}
.dbar__drw svg{width:16px;height:16px;fill:rgba(0,0,0,.38)}
.dbar.is-drawer-open .dbar__drw{transform:rotate(180deg);transition-duration:.52s}
/* The page's back control fades with opacity 1 - 2t (gone by half-way) and
   stops taking taps while the drawer is up. */
body.edr-open .dback{opacity:0;pointer-events:none;transition:opacity .26s linear}
.dback{transition:opacity .21s linear} /* the fade back in as the drawer closes */


/* ═══════════════════════════════════════════════════════════════════════════
   SETTINGS LIST (account page)
   From account.dart's Settings screen: a centred AppBar title, then full-width
   ListTile rows separated by 1px #EEEEEE hairlines. Values taken from
   `_buildSimpleListTile` (:2815) and the phone row (:2477): title 16px black,
   contentPadding 16h/4v, subtitle 15px black54 w600, trailing chevron 16px grey.
   ═══════════════════════════════════════════════════════════════════════════ */

/* FULL-BLEED RULES. The page pads itself by var(--pad-page), so the list's
   own borders stopped short of the screen edge; the app's settings list runs
   its dividers the full width with only the content inset. Cancel the gutter
   on the list and put it back as row padding. */
/* ONE full-bleed rule above every top-level block on this page. The list used
   to carry a border on BOTH edges, so the gap between it and the download card
   held two lines a few pixels apart; each block owning only its own top rule
   gives a single, even separator throughout. */
.slist{
  margin:20px calc(var(--pad-page) * -1) 0;
  border-top:1px solid #EEEEEE;
}
/* EVERY row draws its own closing rule. This used to be `.srow + .srow`, an
   ADJACENCY rule — and the language and terms rows each expand an inline
   `.ssub` panel that is inserted between them, so after the first pair no two
   `.srow`s are adjacent any more and the rule silently stopped firing. Only
   one separator in the whole list survived. A border each row owns cannot be
   broken by whatever gets inserted between them. */
.srow{
  display:flex;align-items:center;gap:12px;width:100%;
  padding:12px var(--pad-page);min-height:56px;text-align:left;
  background:none;
  border-bottom:1px solid #EEEEEE;
}
.srow__m{flex:1;min-width:0}
.srow__t{font-size:16px;color:#000;line-height:1.35}
.srow__s{font-size:15px;color:rgba(0,0,0,.54);font-weight:600;margin-top:2px}
.srow__v{font-size:15px;color:rgba(0,0,0,.45);flex:none}
.srow__go{font-size:15px;color:#9E9E9E;flex:none;line-height:1}
/* No danger/red variant: the app's Sign Out row is plain black (account.dart
   _buildSimpleListTile applies no colour override). Keeping this class defined
   but unused would invite reintroducing the same wrong assumption. */

/* The nested legal links, styled like the app's expanded Advanced Settings:
   a grey-50 block indented under its parent row. */
/* No border-top: the row above owns that boundary now. The panel closes
   itself so the row that follows it is still separated. */
.ssub{background:#FAFAFA;border-bottom:1px solid #EEEEEE}
.ssub a,.ssub button{
  display:flex;align-items:center;gap:12px;
  padding:12px var(--pad-page) 12px calc(var(--pad-page) + 16px);
  min-height:48px;font-size:15px;color:#000;
}
.ssub a+a,.ssub button+button{border-top:1px solid #EEEEEE}
.ssub a .srow__go,.ssub button .srow__go{margin-left:auto}

/* Same treatment for the version footer, so the page reads as a stack of
   evenly ruled blocks rather than a list plus two loose items. */
.sver{
  position:relative;text-align:center;font-size:13px;color:var(--faint);
  margin-top:26px;padding:26px 0 10px;
}
.sver:before{
  content:'';position:absolute;top:0;
  left:calc(var(--pad-page) * -1);right:calc(var(--pad-page) * -1);
  height:1px;background:#EEEEEE;
}

/* ---- download-the-app card --------------------------------------------
   Deliberately NOT a settings row. It is the only reason this surface
   exists, and as a grey list item it read as more admin between "Language"
   and "Sign out". The heat gradient is the app's own (--heat-grad). */
/* The card is inset and rounded, so its separator cannot be its own border —
   it is a rule pinned to the top of its margin box and bled out past the page
   gutter on both sides. */
.dlcard{
  position:relative;
  margin:22px 0 0;padding:18px;
  border-radius:22px;
  /* --pastel-grad: the app's own #FF6B6B -> #FFBD71 -> #EEDC82 ramp, the same
     one the top stat bar's rail runs. Light enough that the copy has to be
     DARK — white text died on the yellow end. */
  background:var(--pastel-grad);
  color:#1A1A1A;
  box-shadow:0 8px 20px rgba(26,26,46,.14);
}
@supports (corner-shape: superellipse(2.2)){
  .dlcard{border-radius:44px;corner-shape:superellipse(2.2)}
}
.dlcard:before{
  content:'';position:absolute;top:-22px;
  left:calc(var(--pad-page) * -1);right:calc(var(--pad-page) * -1);
  height:1px;background:#EEEEEE;
}
.dlcard__head{display:flex;align-items:center;gap:14px}
/* The nav's reward icon at the 30px the app draws it (navigationBar.dart:111),
   nudged up slightly for the card. Full-colour artwork — no plate, unlike the
   white-on-transparent scan glyph. */
.dlcard__logo{width:40px;height:40px;flex:none;object-fit:contain;display:block}
.dlcard__m{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}
.dlcard__m b{font-size:16px;font-weight:900;line-height:1.3}
.dlcard__m span{font-size:12.5px;font-weight:500;line-height:1.35;color:rgba(0,0,0,.66)}
/* Both badges, side by side and equal width, so neither store is the
   afterthought. */
.dlcard__badges{display:flex;gap:10px;margin-top:16px}
/* NO plate behind them. The badges are finished artwork with their own black
   pill and corner radius; wrapping them in another dark container drew a box
   around a box. Sized to their own aspect at a fixed height instead of being
   stretched to equal flex widths. */
.dlcard__badge{
  flex:0 1 auto;min-width:0;height:42px;
  display:flex;align-items:center;
  font-size:12px;font-weight:700;color:#1A1A1A;
}
.dlcard__badge img{height:100%;width:auto;max-width:100%;object-fit:contain;display:block}
.dlcard__badge:active{filter:brightness(.94)}

/* ---- store carousel: selectable tiles + the rules panel ---------------- */
.storerow{
  display:flex;gap:12px;overflow-x:auto;scrollbar-width:none;
  margin:0 calc(var(--pad-page) * -1);padding:6px var(--pad-page) 0;
  align-items:flex-end;
}
.storerow::-webkit-scrollbar{display:none}
.storerow>li{list-style:none;flex:0 0 auto;width:84px;text-align:center}
/* The 84px cell IS the highlight: 72px logo + 12px gap, 88 tall with the logo
   sitting 12px down (:4342, :4368). Radius 24 on the top corners, not 16 — it
   is the tab that feeds into the 16px panel below. */
.storerow .cell{
  width:84px;height:88px;padding:12px 6px 0;
  border-radius:24px 24px 0 0;
  background:transparent;transition:background .3s cubic-bezier(.2,.8,.2,1);
}
/* The selected tile's cell fills with the panel colour so the tile and the
   panel below read as ONE L-shaped container, which is the app's trick. */
.storerow>li.on .cell{background:var(--panel-fill)}
/* NO white plate, NO border, NO ring. The app draws the logo art alone at
   72x72 with 3px padding (:4392); the selected one scales 1.08 and the rest
   drop to 45% opacity. The white rounded plate and the themed ring here were
   invented and are most of why the row read as a different component. */
.storerow .logo{
  width:72px;height:72px;margin:0 auto;padding:3px;
  display:flex;align-items:center;justify-content:center;
  opacity:.45;
  transition:transform .35s cubic-bezier(.2,.8,.2,1), opacity .35s cubic-bezier(.2,.8,.2,1);
}
.storerow>li.on .logo{transform:scale(1.08);opacity:1}
.storerow .logo img{width:100%;height:100%;object-fit:contain}
/* A static row (one flat rule for every channel) never dims or scales. */
.storerow[data-static="1"] .cell{background:none}
.storerow[data-static="1"] .logo{transform:none;opacity:1}

.storepanel{
  background:var(--panel-fill);
  padding:14px 16px 16px;
  border-radius:var(--panel-tl,16px) var(--panel-tr,16px) 16px 16px;
  /* AnimatedSize equivalent: grow/shrink smoothly when switching stores. */
  transition:border-radius .35s ease;
}
.storepanel h4{font-size:15.5px;font-weight:800;margin-bottom:9px}
.storepanel ul{list-style:none}
.storepanel li{
  position:relative;padding:5px 0 5px 16px;font-size:13.5px;line-height:1.55;color:#3A3A3A;
}
.storepanel li:before{
  content:'';position:absolute;left:2px;top:12px;
  /* The dot follows the panel's ACCENT, not the theme: on a yellow GM panel a
     red theme dot was the only red thing in the box. */
  width:6px;height:6px;border-radius:50%;
  background:var(--panel-accent,var(--theme));opacity:.9;
}
.storepanel li.focus{font-weight:700;color:var(--ink)}
/* Bonus multiplier lines are shown inline with the rest, never behind a tap.
   Tinted so they still read as the "and it doubles when..." clause rather than
   flattening into the base rule. */
.storepanel li.bonus{font-weight:600;color:var(--ink)}
.storepanel li.bonus:before{opacity:1}

/* ---- store rule rows (`_StoreRuleRows`, brand_lucky_draw_page.dart:13493) --
   A store with `ruleRows` gets ONE white box of rows instead of the bullets:
   a 30x30 kind tile, 10px gap, a 14px/1.3 w800 headline and a 12.5px/1.4
   w500 #6B7280 detail 2px under it; rows 10px top and bottom, split by 1px
   black-6% dividers. Box: white, 12px side padding, SmoothRect r14 s0.6 ->
   21px + superellipse(1.6), fallback 15. Tile: SmoothRect r9 s0.6 -> 13.5 +
   superellipse(1.6), fallback 10. Glyphs 17px (the dot 8px), the bonus
   badge 13px w900. Colours come from draw.html (RULE_TILES). */
.ruler{background:#fff;padding:0 12px;border-radius:15px}
.ruler__row{display:flex;align-items:flex-start;gap:10px;padding:10px 0}
.ruler__row+.ruler__row{border-top:1px solid rgba(0,0,0,.06)}
.ruler__tile{
  flex:none;width:30px;height:30px;border-radius:10px;
  display:flex;align-items:center;justify-content:center;
  background:var(--ruler-bg);color:var(--ruler-ink);
}
.ruler__tile svg{width:17px;height:17px;display:block;fill:currentColor}
.ruler__tile--dot svg{width:8px;height:8px}
.ruler__tile b{font-size:13px;font-weight:900;line-height:1}
@supports (corner-shape: superellipse(1.6)){
  .ruler{border-radius:21px;corner-shape:superellipse(1.6)}
  .ruler__tile{border-radius:13.5px;corner-shape:superellipse(1.6)}
}
/* The reward row's tile IS the draw's ticket: width 30 at the ticket art's
   1038/637 aspect, contained and left-aligned (EntryTicketImage), centred in
   the 30x30 slot. Its fallback is confirmation_number_rounded at 65% of that
   height (~12px) in brandInk. */
.ruler__tick{
  flex:none;width:30px;height:30px;
  display:flex;align-items:center;justify-content:center;color:var(--ruler-ink);
}
.ruler__tick img{
  width:30px;height:calc(30px * 637 / 1038);display:block;
  object-fit:contain;object-position:left center;
}
.ruler__tick svg{width:12px;height:12px;display:block;fill:currentColor}
.ruler__tx{flex:1;min-width:0}
.ruler__tx b{display:block;font-size:14px;line-height:1.3;font-weight:800;color:#1A1A1A;white-space:pre-line}
.ruler__tx span{display:block;margin-top:2px;font-size:12.5px;line-height:1.4;font-weight:500;color:#6B7280;
  /* Partner copy can carry line breaks (Cintage's "ตัวอย่างในการได้รับสิทธิ์" card, 2026-10-05); Flutter's
     Text keeps them, so keep them here too. */
  white-space:pre-line}
/* 14 gap, Divider(1px, black 8%), 12 gap, then the spend table (:5949). */
.ruler__div{height:1px;margin-top:14px;background:rgba(0,0,0,.08)}
.ruler__div + .btab{margin-top:12px}

/* ---- focus-SKU strip (_focusSkuImageStrip :982) ------------------------- */
/* Equal thirds, 8px apart, top-aligned so only the tall caption grows. */
.fsku{display:flex;align-items:flex-start;gap:8px;margin-top:12px}
.fsku__c{flex:1 1 0;min-width:0;cursor:pointer}
.fsku__t{
  position:relative;aspect-ratio:1;border-radius:14px;overflow:hidden;
  background:var(--tile,#EE7D42);
  display:flex;align-items:center;justify-content:center;
}
.fsku__t img{width:100%;height:100%;object-fit:contain}
/* Four lines, not two: a bundle name is roughly double a plain one. */
.fsku__n{
  margin-top:6px;text-align:center;
  font-size:11.5px;font-weight:700;color:#1A1A1A;line-height:1.25;
  display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;
  overflow:hidden;
}

/* ---- per-store spend table (_StoreBonusBar :11084) ---------------------- */
.btab{width:100%;border-collapse:collapse;margin-top:14px;table-layout:fixed}
/* FlexColumnWidth 3.9 / 2.7 / 3.4 — "When" is widened to absorb its 14px
   gutter without ellipsizing a cross-month label like "24 ส.ค. – 16 ก.ย.". */
.btab th:nth-child(1),.btab td:nth-child(1){width:39%}
.btab th:nth-child(2),.btab td:nth-child(2){width:27%}
.btab th:nth-child(3),.btab td:nth-child(3){width:34%}
.btab th{
  text-align:left;padding:0 14px 9px 0;
  font-size:10.5px;font-weight:700;letter-spacing:.4px;
  color:rgba(0,0,0,.38);text-transform:uppercase;line-height:1.1;
}
.btab th:nth-child(2){padding-right:6px}
.btab th:nth-child(3){padding-right:0}
/* horizontalInside only — no outer border. */
.btab tbody tr{border-top:1px solid rgba(0,0,0,.06)}
.btab td{padding:10px 0;vertical-align:middle;font-size:13px;line-height:1.15}
.btab__when{
  padding-right:14px !important;font-weight:700;color:#1A1A1A;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.btab__spend{padding-right:6px !important;font-weight:700;color:#333}
/* A part-day bonus tier: date, then its hours on a second line. `whenCell`
   (brand_lucky_draw_page.dart:14067) allows maxLines 2 ONLY when the label
   carries a "\n", so single-line rows above keep their one ellipsized line.
   The clamp lives on an inner span because a table cell cannot be a
   -webkit-box. */
.btab__when--2{white-space:normal}
.btab__when--2 span{
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;
  overflow:hidden;white-space:pre-line;
}
/* Thai has no separate word for "SKUs", so the whole label underlines. */
.btab__spend--sku{text-decoration:underline;text-underline-offset:2px}
.btab__get{display:flex;align-items:center;gap:6px;padding:10px 0}
.btab__n{font-weight:700;color:var(--btab-ink,#FF8989);white-space:nowrap}
/* A doubling row is w900 and filled with the scan-receipt gradient. */
.btab__n--x2{
  font-weight:900;
  background:linear-gradient(90deg,var(--btab-ink,#FFAB70),var(--btab-ink2,#FF8989));
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
/* `_StoreBonusBar._glyphs`: each ticket wears a 2px white sticker edge that
   follows its shape, so a pink ticket still reads on a pink panel (owner,
   2026-10-05 in the app; 2026-10-06 on the web: "like how it is in the app"),
   and sits 3 + 2 x edge = 7px from the next so the edges don't merge. */
.btab__glyphs{display:flex;align-items:center;gap:7px;flex:none}
.btab__glyphs img{
  height:11px;width:auto;display:block;
  filter:drop-shadow(2px 0 0 #fff) drop-shadow(-2px 0 0 #fff) drop-shadow(0 2px 0 #fff) drop-shadow(0 -2px 0 #fff);
}

/* ---- "how to scan" guide button (_ereceiptGuideButton :4569) ------------ */
/* Full width, theme on theme-at-8%, squircle r12 smoothing 0.6 — the same
   0.6 the SKU dialog uses, scaled linearly: 42 * 12/28 = 18, fallback 13. */
.erbtn{
  display:flex;align-items:center;justify-content:center;gap:8px;
  /* width:100% is load-bearing now that this is a <button> rather than an <a>.
     Form controls size to fit-content even as block-level flex containers, so
     without it the button shrink-wraps its label and sits off to one side. The
     app wraps it in SizedBox(width: double.infinity) for the same reason. */
  width:100%;
  margin-top:12px;padding:12px;
  border-radius:13px;
  background:color-mix(in srgb,var(--theme) 8%,transparent);
  color:var(--theme);font-size:14px;font-weight:800;
}
.erbtn img{height:20px;width:auto;border-radius:4px;display:block}
.erbtn:active{filter:brightness(.96)}
@supports (corner-shape: superellipse(1.6)){
  .erbtn{border-radius:18px;corner-shape:superellipse(1.6)}
}
@supports not (color: color-mix(in srgb, red 50%, transparent)){
  .erbtn{background:rgba(0,0,0,.05)}
}

/* ---- shop link (`_shopLinkButton`, brand_lucky_draw_page.dart:5557) ------
   Full width, 13px w800, centred and wrapping like the app's Text.rich:
   label, the store logo with 6px either side, open_in_new_rounded 16px.
   Padding 12/12 at the panel foot (12px above it), 8/12 in the store row.
   SmoothRect r14 s1.0 -> 32px + superellipse(2.2), fallback 16. Fill and ink
   come from the store doc (--shop-bg / --shop-ink, draw.html). */
.shopbtn{
  display:block;width:100%;margin-top:12px;padding:12px;
  border-radius:16px;text-align:center;
  background:var(--shop-bg);color:var(--shop-ink);
  font-size:13px;font-weight:800;line-height:1.35;
}
.shopbtn--row{margin-top:0;padding:8px 12px}
.shopbtn:active{filter:brightness(.96)}
.shopbtn__lbl{vertical-align:middle}
.shopbtn__logo{
  display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;
  width:22px;height:22px;margin:0 6px;
}
.shopbtn__logo img{width:22px;height:22px;object-fit:contain;display:block}
.shopbtn__logo svg{width:18px;height:18px;display:block;fill:currentColor}
/* On a filled button the logo sits on a white tile, 2px padding, SmoothRect
   r7 s1.0 -> 16px + superellipse(2.2), fallback 8; its fallback glyph is
   #1A1A1A so it never vanishes into the white. */
.shopbtn__logo--tile{box-sizing:content-box;padding:2px;background:#fff;color:#1A1A1A;border-radius:8px}
.shopbtn__go{display:inline-block;vertical-align:middle;width:16px;height:16px;fill:currentColor}
@supports (corner-shape: superellipse(2.2)){
  .shopbtn{border-radius:32px;corner-shape:superellipse(2.2)}
  .shopbtn__logo--tile{border-radius:16px;corner-shape:superellipse(2.2)}
}
/* The store-row home: a box 72 tall level with the logos (row padding 6 +
   cell padding 12 = 18 down), from 12px after the last cell to the page's
   right padding; draw.html sets `left`. The button centres in it. */
.shopbtn__row{position:relative}
.shopbtn__slot{position:absolute;top:18px;right:0;height:72px;display:flex;align-items:center}
.shopbtn__slot[hidden]{display:none}

/* ---- FAQ (collapsible) -------------------------------------------------- */
/* `_FaqSection` / `_FaqItem` (brand_lucky_draw_page.dart). The app puts a
   full-width Divider #E8EAED with 20 above and 20 below it before the FAQ,
   and the same after it (the help row's line here is .inforow's border-top).
   Its section padding is 24; this page keeps its own --pad-page gutter like
   every other section. */
.faq{
  margin:20px calc(var(--pad-page) * -1) 0;
  padding:20px var(--pad-page) 20px;
  border-top:1px solid #E8EAED;
}
/* Header: Padding(vertical 4), title Switzer 16 w900 #1A1A1A, SizedBox(10),
   keyboard_arrow_down_rounded 24 #1A1A1A turning 0.5 over 200ms. */
.faq__head{
  width:100%;display:flex;align-items:center;gap:10px;
  padding:4px 0;background:none;border:0;text-align:left;cursor:pointer;
  color:#1A1A1A;font:inherit;
}
.faq__title{flex:1 1 auto;font-size:16px;font-weight:900;line-height:1.3}
.faq__chev{width:24px;height:24px;flex:none;transition:transform .2s}
.faq__chev svg{width:24px;height:24px;display:block;fill:#1A1A1A}
.faq.open .faq__chev{transform:rotate(180deg)}
/* AnimatedCrossFade(200ms) into a Padding(top 14). Deviation: the section
   appears with a 200ms fade instead of growing, because animating the height
   of a 14-card block re-lays it out every frame. */
.faq__body{display:none;padding-top:14px}
.faq.open .faq__body{display:block;animation:faqIn .2s ease-in-out}
@keyframes faqIn{from{opacity:0}to{opacity:1}}
/* One card per question: Padding(bottom 10); #F5F6F8, BorderRadius 16, a
   1.5 border kept transparent when closed so opening never nudges the text.
   Open: white + accent @ 0.45 (AnimatedContainer 200ms easeInOut). */
.faq__q{
  margin-bottom:10px;background:#F5F6F8;border-radius:16px;
  border:1.5px solid transparent;
  transition:background-color .2s ease-in-out,border-color .2s ease-in-out;
}
.faq__q.open{background:#fff;border-color:rgba(0,0,0,.18)}
@supports (color: color-mix(in srgb, red 50%, transparent)){
  .faq__q.open{border-color:color-mix(in srgb,var(--theme) 45%,transparent)}
}
/* Question row: LTRB 16,14,12,14; Switzer 14 / 1.3 w700 #1A1A1A; SizedBox(10);
   chevron 22, #9AA0A6 closed and the accent open. */
.faq__qbtn{
  width:100%;display:flex;align-items:flex-start;gap:10px;
  padding:14px 12px 14px 16px;background:none;border:0;text-align:left;
  cursor:pointer;color:#1A1A1A;font:inherit;
}
.faq__qt{flex:1 1 auto;font-size:14px;line-height:1.3;font-weight:700}
.faq__qchev{width:22px;height:22px;flex:none;transition:transform .2s}
.faq__qchev svg{width:22px;height:22px;display:block;fill:#9AA0A6;transition:fill .2s ease-in-out}
.faq__q.open .faq__qchev{transform:rotate(180deg)}
.faq__q.open .faq__qchev svg{fill:var(--theme)}
/* Answer: SizeTransition from the top. Opening 200ms easeInOut with a linear
   fade; closing 280ms with the fade done in its first half (Interval 0.5-1.0
   on the reverse). A 0fr/1fr grid row is the CSS SizeTransition; one card's
   answer is small enough to animate. */
.faq__a{display:grid;grid-template-rows:0fr;transition:grid-template-rows .28s ease-in-out}
.faq__q.open .faq__a{grid-template-rows:1fr;transition-duration:.2s}
.faq__ai{min-height:0;overflow:hidden;opacity:0;transition:opacity .14s ease-out}
.faq__q.open .faq__ai{opacity:1;transition:opacity .2s linear}
/* LTRB 16,0,16,16; Switzer 13 / 1.45 w500 #6B7280. */
.faq__ai p{margin:0;padding:0 16px 16px;font-size:13px;line-height:1.45;font-weight:500;color:#6B7280}
/* Line breaks in the doc's text stay, as the app's plain Text keeps them
   (_FaqItem): Cintage's Golden Time and Lucky Fans answers are two lines. */
.faq__ai p,.faq__qt{white-space:pre-line}
/* faqGroups heading pill (`_FaqSection`, brand_lucky_draw_page.dart:22222):
   theme at 12% behind 12px w800 theme text, fully round, padding 5/12; 14px
   above it except before the first question, 10 below. */
.faq__grp{padding:14px 0 10px}
.faq__grp--first{padding-top:0}
.faq__grp span{
  display:inline-block;padding:5px 12px;border-radius:999px;
  background:color-mix(in srgb,var(--theme) 12%,transparent);
  color:var(--theme);font-size:12px;font-weight:800;line-height:1.3;
}
@supports not (color: color-mix(in srgb, red 50%, transparent)){
  .faq__grp span{background:rgba(0,0,0,.05)}
}

/* ---- the info rows under the FAQ --------------------------------------- */
/* FULL-BLEED DIVIDERS + a uniform row height.
   The page pads itself by var(--pad-page), so a border drawn on the row was
   inset by that gutter on both sides. The app draws the Divider OUTSIDE the
   row's horizontal padding (:3863 — Padding(vertical:20) around the Divider,
   while the content row carries EdgeInsets.symmetric(horizontal:24)), so the
   line runs edge to edge. Cancel the gutter with a negative margin and put it
   back as padding, so the border spans the shell and the content stays inset.

   min-height, not height: the rows carry different content (a pill, one line,
   two lines of date) and a hard height would clip the date. 64px is the
   tallest of them, so every row settles at the same height and the content
   centres. */
.inforow{
  display:flex;align-items:center;gap:11px;
  min-height:64px;
  margin:0 calc(var(--pad-page) * -1);
  padding:12px var(--pad-page);
  border-top:1px solid #E8EAED;
  font-size:14px;
}
/* PER-ROW icon treatment — see the mk() comment in draw.html. Three of the
   five are a bare 18px glyph; two sit on their own tinted 20px plate. */
.inforow .ic{
  width:18px;height:18px;flex:none;
  display:flex;align-items:center;justify-content:center;
}
.inforow .ic svg{width:18px;height:18px;display:block;fill:#1A1A1A}

/* Plated pair. 20px circle, smaller glyph inside it. */
.inforow .ic--info,
.inforow .ic--chat{width:20px;height:20px;border-radius:999px}
.inforow .ic--info{background:#EDEDED}
.inforow .ic--info svg{width:14px;height:14px;fill:rgba(0,0,0,.87)}
.inforow .ic--chat{background:rgba(6,199,85,.12)}
.inforow .ic--chat svg{width:13px;height:13px;fill:#06C755}

/* The star takes the hero gradient's START colour, which is the campaign's
   own, via --winners (render() sets it to --hero-a, or to a readable brand
   shade where the hero starts near-white, as Cintage's does). It is not a
   fixed red. */
.inforow .ic--star svg{fill:var(--winners,var(--hero-a,#E5202E))}
.inforow .tx{flex:1;min-width:0;line-height:1.4}
/* Tighter than before: the app's pills are compact enough that a long label
   ("ส่งใบเสร็จเพื่อตรวจสอบ") still fits beside the question on one row. */
.inforow .pill{
  flex:none;padding:7px 13px;border-radius:999px;
  font-size:12px;font-weight:800;color:var(--white);white-space:nowrap;
}
/* `color: themeColor` (_buildParticipateTab's missing-entries row, the
   Submit receipt pill): the campaign's own colour, not a fixed red. It was
   #E5202E for every draw, which put a red pill on Cintage's pink page. */
.inforow .pill--theme{background:var(--theme,#E5202E)}
.inforow .pill--line{background:#06C755}
/* Same colour as its star (:3897) — the campaign's, not a hardcoded red. */
.inforow--winners .tx{color:var(--winners,var(--hero-a,#E5202E));font-weight:800}
.inforow .go{flex:none;display:flex}
.inforow .go svg{width:16px;height:16px;display:block;fill:#1A1A1A}

/* ---- prize showcase + venue map (prizes tab) ---------------------------- */
/* HEIGHT-constrained and centred, not full-bleed: `height: 242, BoxFit.contain`
   (:4968). GM campaign 2's poster is portrait 1654x2339, so a width:100% rule
   scaled it to the whole column. Squircle 20 / smoothing 0.6, scaled off the
   fitted r28 pair: 42 * 20/28 = 30, circular fallback 30 * 20/28 = 21. */
.dprize{display:flex;justify-content:center;margin-top:40px}
.dprize__img{
  height:242px;width:auto;max-width:100%;object-fit:contain;
  border-radius:21px;cursor:zoom-in;
}
@supports (corner-shape: superellipse(1.6)){
  .dprize__img{border-radius:30px;corner-shape:superellipse(1.6)}
}
/* 16px w800 #1A1A1A, centred, 18px under the image (:4979). */
.dprizecap{
  margin-top:18px;text-align:center;
  font-size:16px;line-height:1.4;font-weight:800;color:#1A1A1A;
}

/* ---- Prizes-tab benefit grid (`LuckyDrawPrizeTable`,
   brand_lucky_draw_page.dart:11370) -------------------------------------
   24px under the summary. Title 16px w900 centred, 12 under it. The grid is a
   white box with TableBorder.all(1.5, radius 14): the frame is the wrapper
   (a collapsed table cannot round its border) and the table draws only the
   inside lines. Header row: header colour, 12/6 padding, 13px/1.2 w900
   centred. Perk cell LTRB 10,12,8,12: a 24px accent glyph with `iconNumber`
   8px w900 over it, 8px gap, label 13px/1.2 w800, note 2px under it at
   10.5px/1.25 w500 #6B7280. Value cells centred: a 24px heart, or 14px/1.2
   w900 accent text with 12/4 padding. Footnote 10 above, 12px/1.45 w500
   #6B7280 centred. Colours: --pt-* from draw.html. */
.ptable{margin-top:24px}
.ptable__title{margin-bottom:12px;text-align:center;font-size:16px;font-weight:900;color:#1A1A1A}
.ptable__frame{background:#fff;border:1.5px solid var(--pt-border);border-radius:14px;overflow:hidden}
.ptable__grid{width:100%;border-collapse:collapse;table-layout:fixed}
.ptable__grid th,.ptable__grid td{
  border-left:1.5px solid var(--pt-border);border-top:1.5px solid var(--pt-border);
  vertical-align:middle;
}
.ptable__grid th:first-child,.ptable__grid td:first-child{border-left:0}
.ptable__grid thead th{
  border-top:0;background:var(--pt-head);padding:12px 6px;
  font-size:13px;line-height:1.2;font-weight:900;color:#1A1A1A;text-align:center;
}
.ptable__perk{padding:12px 8px 12px 10px}
.ptable__pin{display:flex;align-items:center;gap:8px}
.ptable__glyph{
  position:relative;flex:none;width:24px;height:24px;
  display:flex;align-items:center;justify-content:center;color:var(--pt-accent);
}
.ptable__glyph svg{position:absolute;inset:0;width:24px;height:24px;fill:currentColor}
.ptable__glyph b{position:relative;font-size:8px;line-height:1;font-weight:900}
.ptable__ptx{flex:1;min-width:0}
.ptable__ptx b{display:block;font-size:13px;line-height:1.2;font-weight:800;color:#1A1A1A}
.ptable__ptx span{display:block;margin-top:2px;font-size:10.5px;line-height:1.25;font-weight:500;color:#6B7280}
.ptable__val{text-align:center}
.ptable__val span{
  display:block;padding:12px 4px;text-align:center;
  font-size:14px;line-height:1.2;font-weight:900;color:var(--pt-accent);
}
.ptable__heart{display:block;margin:0 auto;width:24px;height:24px;fill:var(--pt-heart)}
.ptable__foot{margin-top:10px;text-align:center;font-size:12px;line-height:1.45;font-weight:500;color:#6B7280}

.venue{margin-top:30px}
/* 18px w900 in the DRAW's theme colour, letter-spacing -0.5 (:5093). */
.venue__title{
  font-size:18px;font-weight:900;color:var(--theme);letter-spacing:-.5px;
}
.venue__cap{
  margin-top:6px;font-size:13px;line-height:1.35;font-weight:600;color:#7A786F;
}
/* Same squircle as the prize showcase so the two images on this tab read as
   one family — radius 16 / smoothing 0.6 -> 24, fallback 17. */
.venue__frame{
  position:relative;margin-top:14px;overflow:hidden;
  border-radius:17px;
}
@supports (corner-shape: superellipse(1.6)){
  .venue__frame{border-radius:24px;corner-shape:superellipse(1.6)}
}
.venue__img{display:block;width:100%;height:100%;object-fit:cover;cursor:zoom-in}
/* The tap target is 9% of the artwork width (_kVenueTapFactor), centred on the
   badge PRINTED INTO the image — so it is transparent, with only the pulse
   ring drawn. translate(-50%,-50%) puts its centre on the stored point;
   percentage left/top alone would offset it by half the target. */
.venue__spot{
  position:absolute;width:9%;aspect-ratio:1;
  transform:translate(-50%,-50%);
  background:none;border:0;padding:0;cursor:pointer;
  border-radius:50%;
}
.venue__spot::after{
  content:'';position:absolute;inset:0;border-radius:50%;
  border:2px solid var(--badge,#F5871F);
  opacity:.55;animation:venuePing 1.9s ease-out infinite;
}
@keyframes venuePing{
  0%{transform:scale(.82);opacity:.65}
  70%{transform:scale(1.36);opacity:0}
  100%{transform:scale(1.36);opacity:0}
}
@media (prefers-reduced-motion:reduce){
  .venue__spot::after{animation:none;opacity:.5}
}
.venue__hint{
  margin-top:8px;text-align:center;
  font-size:12px;font-weight:600;color:#9C9A93;
}
/* ---- venue section popup (_VenueSpotPopup :17516) ----------------------
   IMAGE-FIRST: the section close-up is the point of the card, with the
   caption under it. min(100vw - 48, 460) wide, white, squircle 20/0.6 with a
   1.5px border in the marker's badge colour. Radii scale off the fitted
   r28/s0.6 pair: 42 * 20/28 = 30 outer, and the inner clip loses the border,
   42 * 18.5/28 = 28. Circular fallbacks 21 and 20. */
.venuepop{
  position:relative;
  width:calc(100vw - 48px);max-width:460px;
  background:var(--white);
  border:1.5px solid var(--badge,#F5871F);
  border-radius:21px;overflow:hidden;
  box-shadow:0 12px 28px rgba(0,0,0,.25);
  animation:skuPop .28s cubic-bezier(.22,.61,.36,1);
}
@supports (corner-shape: superellipse(1.6)){
  .venuepop{border-radius:30px;corner-shape:superellipse(1.6)}
}
/* The box holds the artwork's aspect and the image is CONTAINED in it, so a
   render a hair off the ratio letterboxes instead of stretching — and the card
   is already its final size while the image loads. */
.venuepop__shot{
  position:relative;width:100%;background:var(--white);
  display:flex;align-items:center;justify-content:center;
}
.venuepop__shot img{width:100%;height:100%;object-fit:contain;display:block}
.venuepop__shot:not(.is-loaded):not(.is-error)::after{
  content:'';position:absolute;width:32px;height:32px;border-radius:50%;
  border:2.5px solid rgba(0,0,0,.12);border-top-color:var(--badge,#F5871F);
  animation:spin .9s linear infinite;
}
/* FIXED height, so a section WITH a note opens exactly as tall as one without
   and the card never resizes between numbers. */
.venuepop__cap{
  height:64px;padding:0 16px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
}
.venuepop__row{display:flex;align-items:center;gap:9px;min-width:0}
.venuepop__n{
  width:24px;height:24px;border-radius:50%;flex:none;
  display:flex;align-items:center;justify-content:center;
  font-size:13px;font-weight:900;color:#fff;
}
.venuepop__lbl{
  font-size:17px;line-height:1.3;font-weight:900;color:#1A1A1A;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.venuepop__note{
  margin-top:3px;text-align:center;
  font-size:13px;line-height:1.35;font-weight:600;color:#7A786F;
}
.venuepop__x{top:8px;right:8px}

/* ---- SKU / product lightbox (_showProductImageDialog :9494) ------------- */
/* ---- full-photo viewer (draw_page.js openFullPhotos; _FullPhotoViewer
   :13741) --------------------------------------------------------------------
   The UNCROPPED photo, contained, as large as the screen allows, on the
   product's `photoBackdrop` (a top-to-bottom gradient, dark ink) or black
   (white ink). Pages are a scroll-snap track, one screen wide each. */
.fph{
  position:fixed;inset:0;z-index:205;background:#000;color:#fff;
  animation:lbIn .16s ease-out;
}
.fph--light{background:linear-gradient(180deg,var(--fph-a),var(--fph-b));color:#1A1A1A}
.fph__track{
  position:absolute;inset:0;display:flex;overflow-x:auto;overflow-y:hidden;
  scroll-snap-type:x mandatory;scrollbar-width:none;overscroll-behavior:contain;
}
.fph__track::-webkit-scrollbar{display:none}
.fph__pg{position:relative;flex:0 0 100%;height:100%;scroll-snap-align:center;scroll-snap-stop:always}
.fph__pg img{width:100%;height:100%;object-fit:contain;display:block;touch-action:pan-x pinch-zoom}
/* 28px, 2.5 stroke, black38 on light / white70 on black, until it loads. */
.fph__pg::before{
  content:"";position:absolute;left:50%;top:50%;width:28px;height:28px;margin:-14px 0 0 -14px;
  border-radius:50%;border:2.5px solid transparent;border-top-color:rgba(255,255,255,.7);
  animation:spin .9s linear infinite;
}
.fph--light .fph__pg::before{border-top-color:rgba(0,0,0,.38)}
.fph__pg.is-in::before{display:none}
/* The line, 16 w700, centred, over a shade from the backdrop's bottom colour
   at 0 to 0.92 (black 0 to 0.6), padding 40 / 24 / 22 + the home bar. */
.fph__cap{
  position:absolute;left:0;right:0;bottom:0;pointer-events:none;
  padding:40px 24px calc(22px + env(safe-area-inset-bottom,0px));
  text-align:center;font-size:16px;font-weight:700;color:#fff;
  background:linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.6));
}
.fph--light .fph__cap{
  color:#1A1A1A;
  background:linear-gradient(180deg,color-mix(in srgb,var(--fph-b) 0%,transparent),color-mix(in srgb,var(--fph-b) 92%,transparent));
}
/* "3 / 7": 16 from the top-left, 6 / 12 padding, 13 w700, a white 75% pill
   (black 35% on the dark viewer). */
.fph__n{
  position:absolute;top:calc(16px + env(safe-area-inset-top,0px));left:16px;pointer-events:none;
  padding:6px 12px;border-radius:999px;font-size:13px;font-weight:700;
  background:rgba(0,0,0,.35);color:#fff;
}
.fph--light .fph__n{background:rgba(255,255,255,.75);color:#1A1A1A}
/* ✕: a 48 circle 8 from the top, 10 from the right, 26px glyph. */
.fph__x,.fph__go{
  position:absolute;width:48px;height:48px;border:0;border-radius:50%;padding:0;
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  background:rgba(0,0,0,.35);color:#fff;
}
.fph--light .fph__x,.fph--light .fph__go{background:rgba(255,255,255,.75);color:#1A1A1A}
.fph__x{top:calc(8px + env(safe-area-inset-top,0px));right:10px}
.fph__x svg,.fph__go svg{width:26px;height:26px;fill:currentColor;display:block}
/* ‹ › only where a mouse cannot swipe (web-only; the app has none). */
.fph__go{top:50%;margin-top:-24px;display:none}
.fph__go--prev{left:16px}
.fph__go--next{right:16px}
@media (hover:hover) and (pointer:fine){.fph__go{display:flex}}

.lightbox{
  position:fixed;inset:0;z-index:200;
  background:rgba(0,0,0,.88);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:0 0 12vh;                 /* Alignment(0,-0.3): sits above centre */
  animation:lbIn .16s ease-out;
}
@keyframes lbIn{from{opacity:0}to{opacity:1}}
@media (prefers-reduced-motion:reduce){.lightbox{animation:none}}
.lightbox__img{
  width:100%;height:50vh;object-fit:contain;
  padding:8px 24px;
  /* The app wraps this in an InteractiveViewer (min 1, max 4). Pinch-zoom is
     the browser's own equivalent; opting the element in is what enables it
     inside a webview that otherwise locks scale. */
  touch-action:pinch-zoom;
}
.lightbox__name{
  margin-top:12px;padding:0 24px;text-align:center;
  color:#fff;font-size:16px;font-weight:700;line-height:1.4;
}
.lightbox__hint{margin-top:10px;color:rgba(255,255,255,.5);font-size:12px}
body.lb-open{overflow:hidden}

/* tappable affordance on the product tiles */
.prodrow>li{cursor:pointer}
.prodrow .tile{transition:transform .12s ease}
.prodrow>li:active .tile{transform:scale(.97)}

/* ---- top stat bar (lib/widgets/baisedHeader.dart) -----------------------
   The capsules are NOT independent. A 2.5px gradient line runs the full width
   behind them (:236-254) and each capsule's border is that same ramp, so the
   colour carries left to right across the whole row: pink at the streak end,
   yellow by the spin end. Building them as five identical pills, as I first
   did, lost exactly that.

   Ramp: #FF6B6B, #FF946E, #FFBD71, #F6CC79, #EEDC82 at 0/.25/.5/.75/1.
   A BaiSed-red bow (#F23540) is tied on the line between the XP and points
   capsules (:467-473).

   THIS SURFACE HAS NO ECONOMY. Streak, XP, points and spins are hard-zero and
   the capsules are inert. Shown because the owner asked for the full bar. If a
   real value is ever piped in it must come from the same users/{uid} fields the
   app reads, never a second source. */
.topbar{
  position:sticky;top:0;z-index:25;
  /* Now the topmost element on the page, so it carries the status-bar inset
     the removed app bar used to. Inside LINE that inset is real. */
  --topbar-pad-top:calc(12px + env(safe-area-inset-top,0px));
  padding:var(--topbar-pad-top) var(--pad-page) 12px;
  /* NO background here, and NO bottom border. See :before. */
  background:transparent;
}
/* THE HEADER IS ONLY HALF OPAQUE.
   baisedHeader.dart:218-229 is a Stack whose white is a `Positioned(top: 0,
   height: lineY)` — white from the top down to the gradient rail's centre line
   and TRANSPARENT below it, with the comment "Below lineY is transparent,
   allowing content to scroll behind".

   So the feed does not stop at the bottom of the header. It slides up behind
   the capsules and vanishes at the rail, which runs through their middle. The
   capsules' lower halves float over live content.

   The old full-height white plus a 1px divider got both halves wrong: content
   disappeared 23px too early, and the app draws no divider at all — the rail
   IS the edge.

   lineY here = padding-top + 23, since the rail box is 46px tall
   (`min-height:46px`, capsules 46px) and its line sits at 50%. */
.topbar:before{
  content:'';position:absolute;left:0;right:0;top:0;
  height:calc(var(--topbar-pad-top) + 23px);
  background:var(--white);
  /* Behind the rail and the capsules, but still inside the sticky element's
     stacking context, so it stays above the scrolling feed. */
  z-index:-1;
}
.topbar__rail{
  position:relative;
  display:flex;align-items:center;justify-content:space-between;
  gap:6px;
  min-height:46px;
  /* The rail is drawn edge to edge and the capsules sit ON it, so the line
     shows in the gaps between them and pokes past both ends. Stretching the
     capsules to fill the row hid it completely, which is what made the bar read
     as five loose pills instead of one strung-together rail. */
  padding:0 2px;
}
/* The connecting line, behind everything, at the capsules' vertical centre. */
.topbar__rail:before{
  content:'';position:absolute;left:-16px;right:-16px;top:50%;height:2.5px;
  transform:translateY(-50%);
  background:linear-gradient(90deg,#FF6B6B 0%,#FF946E 25%,#FFBD71 50%,#F6CC79 75%,#EEDC82 100%);
}
.statpill{
  position:relative;z-index:1;
  /* One of these is a <button> now (the spin lock), and a button inherits
     neither the page font nor the UA's own padding reset. Without this the
     capsule renders in the system font at a different width from the others
     and the rail stops lining up. */
  font-family:inherit;-webkit-appearance:none;appearance:none;margin:0;
  /* Sized to content, never stretched: the gaps are where the rail shows. */
  flex:0 1 auto;min-width:0;
  display:flex;align-items:center;justify-content:center;gap:6px;
  height:46px;padding:0 14px;
  border:2.5px solid transparent;border-radius:23px;
  background:
    linear-gradient(var(--white),var(--white)) padding-box,
    linear-gradient(90deg,var(--edge-a),var(--edge-b)) border-box;
  font-size:15px;font-weight:800;color:#000;
  cursor:default;user-select:none;
}
/* Each capsule takes ITS slice of the ramp, so the row reads continuous. */
.statpill--streak{--edge-a:#FF6B6B;--edge-b:#FF8A6C}
.statpill--xp    {--edge-a:#FF946E;--edge-b:#FFBD71}
.statpill--points{--edge-a:#FFBD71;--edge-b:#F6CC79}
.statpill img{width:24px;height:24px;object-fit:contain;flex:none}
.statpill .v{font-variant-numeric:tabular-nums;white-space:nowrap}

/* 26x26, the size the app hands _StarburstPainter (baisedHeader.dart:1335). */
.statpill--xp .burst{position:relative;width:26px;height:26px;flex:none}
/* The drop shadow is drawn at translate(2,2) with a sigma-2 blur, so it falls
   outside the 26x26 viewBox. Let it. */
.statpill--xp .burst svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
/* The level is WHITE WITH A BLACK OUTLINE, not dark-on-gold. The app stacks two
   Texts (baisedHeader.dart:1344-1367): a black `PaintingStyle.stroke` at width
   2.0 underneath, then the white fill on top — so the fill covers the stroke's
   inner half and only a ~1px outer rim survives. `paint-order:stroke fill`
   reproduces exactly that order; without it the browser paints the stroke over
   the fill and the digit comes out thin and muddy. 12px bold, per :1348. */
.statpill--xp .burst b{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:12px;font-weight:700;color:#fff;letter-spacing:-.02em;
  -webkit-text-stroke:2px #000;
  paint-order:stroke fill;
}

/* The tied bow, centred on the line between the XP and points capsules. */
.topbar__bow{
  position:relative;z-index:2;flex:0 0 auto;
  /* 40, because _HeaderBow is `isSmallPhone(context) ? 32 : 40`
     (baisedHeader.dart:1608) and the painter fills that box edge to edge. The
     44 it used to be scaled the whole bow up by 10%. The 32 in the small-phone
     query below is the other half of the same line. */
  width:40px;height:40px;margin:0 -6px;
  display:flex;align-items:center;justify-content:center;
}
.topbar__bow svg{width:100%;height:100%;display:block}

/* Spin is a FILLED capsule, no outline and no icon in the app's crop. */
.statpill--spin{
  flex:0 0 auto;
  border-color:transparent;color:#fff;
  background:linear-gradient(90deg,#FF5400,#FFA51E) padding-box,
             linear-gradient(90deg,#FF5400,#FFA51E) border-box;
  font-size:16px;font-weight:900;padding:0 18px;
  box-shadow:0 2px 8px rgba(255,110,0,.28);
}

/* LOCKED. There is no points economy on this surface, so the wheel cannot be
   spun and cannot be won here; it lives in the app. Greying it is the honest
   rendering, and it is deliberately still tappable so the tap gets an answer
   (app.js wireSpinLock) instead of reading as a broken button. Grey overrides
   the live orange above, including its glow. */
.statpill--spin.is-locked{
  /* A GHOST CAPSULE, matching the four beside it. Every other pill on the rail
     is a white fill inside a coloured ring; the grey slab this replaces was
     the only solid shape up there and read as a foreign object rather than as
     the last item switched off. Same construction, ring drained to neutral.

     #9A9AA4 on white is 3.0:1 — under AA for body text, but this is a
     DISABLED control, where being visibly recessed is the point and WCAG
     exempts inactive elements. It still has to be readable enough to explain
     the tap, which is why it is not lighter than this. */
  background:
    linear-gradient(var(--white),var(--white)) padding-box,
    linear-gradient(90deg,#DCDCE2,#DCDCE2) border-box;
  color:#9A9AA4;
  box-shadow:none;
  cursor:pointer;
}
.statpill--spin.is-locked:active{transform:scale(.97)}

@media (max-width:360px){
  .statpill{font-size:13.5px;padding:0 7px;gap:4px}
  .statpill--spin{font-size:14px;padding:0 13px}
  .topbar__bow{width:32px;height:32px}
}

/* ---- the scan menu (lib/widgets/scanFloatingMenu.dart) ------------------
   Opened by the scan FAB. Full-screen warm scrim + two circular buttons; the
   FAB itself becomes a navy X. Numbers are the widget's own, sourced in
   app.js wireScanMenu(). */

.scanmenu{position:fixed;inset:0;z-index:28}
.scanmenu[hidden]{display:none}

/* The scrim runs edge to edge and UNDER the nav bar (z-index 30) on purpose:
   the app extends it behind the bar so the bar's 35px top corners are filled
   rather than showing the page through the notches. */
.scanmenu__scrim{
  position:absolute;inset:0;
  background:linear-gradient(180deg,
    rgba(242,53,64,.7),
    rgba(255,84,0,.7),
    rgba(255,107,107,.7));
  opacity:0;
  /* The fade is the first 30% of the 350ms controller, easeOut. */
  transition:opacity .105s ease-out;
}
.scanmenu.is-open .scanmenu__scrim{opacity:1}

/* safeArea.bottom + 64 (scanFloatingMenu.dart:344), 40px apart. Above the bar
   so the buttons are reachable even where the FAB ring pokes up into them. */
.scanmenu__row{
  position:absolute;left:0;right:0;
  bottom:calc(64px + env(safe-area-inset-bottom,0px));
  z-index:31;
  display:flex;justify-content:center;gap:40px;
}
.scanmenu__btn{
  width:55px;height:55px;flex:none;border-radius:50%;
  background:var(--white);
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 6px 12px rgba(0,0,0,.2);
  opacity:0;
  /* Curves.easeOutBack. The overshoot is the point: without it the buttons
     arrive dead and the whole reveal reads as a fade. */
  transition:transform .35s cubic-bezier(.175,.885,.32,1.275),
             opacity .105s ease-out;
}
/* Gallery starts at the FAB's bottom-left indicator — Offset(0.41, 1.52) of
   its own 55px box — and is the ONLY button that also scales up, from 28/55. */
.scanmenu__btn[data-src="gallery"]{transform:translate(22.5px,83.5px) scale(.509)}
/* Camera starts at the FAB centre, Offset(-0.7, 1.2). No scale. */
.scanmenu__btn[data-src="camera"]{transform:translate(-38.5px,66px)}
.scanmenu.is-open .scanmenu__btn{opacity:1;transform:translate(0,0) scale(1)}
.scanmenu__btn:active{filter:brightness(.94)}
.scanmenu__ic{width:26px;height:26px;fill:#FEB600;display:block}

/* The FAB's open state: #000042, scan glyph out, X in. The app sweeps a
   radial yellow-to-navy gradient across the circle; this is a straight colour
   transition, which is the one deliberate simplification here. */
.tabs a.fab .ring,
.tabs button.fab .ring{transition:background-color .28s ease-in-out}
/* Must out-specify `.tabs a.fab .ring` (0,3,1) above, which sets the yellow.
   `.fab.is-open .ring` is only (0,3,0) and silently lost to it, leaving the
   FAB yellow with an X on it. */
.tabs a.fab.is-open .ring,
.tabs button.fab.is-open .ring{background:#000042}
.fab .ring img,
.fab .ring .fab__x{transition:opacity .2s ease-out}
.fab .ring .fab__x{
  /* .ring is already position:absolute, so it is the containing block. Centre
     explicitly — an abs-positioned child with no offsets falls back to its
     static position, which is undefined enough inside a flex box to drift. */
  position:absolute;inset:0;margin:auto;
  width:30px;height:30px;fill:var(--white);opacity:0;
}
.fab.is-open .ring img{opacity:0}
.fab.is-open .ring .fab__x{opacity:1}

/* The page must not scroll behind the scrim. */
body.scanmenu-open{overflow:hidden}

@media (prefers-reduced-motion:reduce){
  .scanmenu__btn{transition-duration:.01s}
}

/* ---- the rules deck (_RulesDeck :14166, build :15233) ------------------
   The full-screen sheet the "?" opens. Barrier is black 60%; the sheet itself
   is `useSafeArea:false` with `insetPadding:zero` and pads its own notch
   insets, so it fills the screen edge to edge rather than sitting in a dialog
   inset. */
.deck{
  position:fixed;inset:0;z-index:210;
  background:rgba(0,0,0,.6);
  display:flex;align-items:stretch;justify-content:center;
  animation:lbIn .16s ease-out;
}
@media (prefers-reduced-motion:reduce){.deck{animation:none}}
/* The deck's own scroll lock: a panel or photo opened over it clears the
   shared `lb-open` when it closes, which must not unlock the page while the
   deck is still up. */
body.deck-open{overflow:hidden}
/* What the deck opens (a product's colour panel, a full photo, the prize
   zoom) is a route ABOVE it in the app. The shared ladder has them under it
   (skusheet 190, lightbox 200, fph 205), so they step over it while the deck
   is open, keeping their own order. */
body.deck-open .skusheet{z-index:211}
body.deck-open .lightbox{z-index:212}
body.deck-open .fph{z-index:213}
.deck__body{
  width:100%;max-width:520px;background:var(--white);
  display:flex;flex-direction:column;min-height:0;
  padding-top:env(safe-area-inset-top,0px);
  padding-bottom:env(safe-area-inset-bottom,0px);
}
/* A circular close button, not a bare X: on a white sheet a plain grey glyph
   stopped reading as a control (:15292). 40px #EDEFF2, Icons.close_rounded
   24 #1A1A1A, 4 from the top and 16 from the right. */
.deck__bar{display:flex;justify-content:flex-end;padding:4px 16px 0}
.deck__x{
  width:40px;height:40px;border-radius:50%;background:#EDEFF2;
  display:flex;align-items:center;justify-content:center;
}
.deck__x svg{width:24px;height:24px;fill:#1A1A1A;display:block}

/* Cards fill ALL remaining height — no top/bottom gaps (:15319). */
.deck__pager{
  flex:1;min-height:0;
  display:flex;overflow-x:auto;overflow-y:hidden;
  scroll-snap-type:x mandatory;scrollbar-width:none;
}
/* The CTA's 300ms easeOutCubic glide writes scrollLeft per frame; a snap
   container would re-snap every write, so snapping is off while it runs. */
.deck__pager.is-gliding{scroll-snap-type:none}
.deck__pager::-webkit-scrollbar{display:none}
.deck__page{flex:0 0 100%;min-width:0;scroll-snap-align:start;padding:0 28px 8px}
.deck__card{
  height:100%;overflow-y:auto;
  display:flex;flex-direction:column;align-items:center;
  /* DELIBERATE DEVIATION. The app centres this card between the close bar and
     the dots (`_cardShell` -> MainAxisAlignment.center,
     brand_lucky_draw_page.dart:14418), which left 129px of empty space above
     the visual on a 375x812 viewport. Asked twice for the content to sit
     closer to the header, so it is TOP-ALIGNED instead.

     Top alignment rather than a bigger bottom padding: padding only biases the
     centre by half its value, so the gap it produces depends on how much slack
     the viewport happens to have — it drifts with screen height. `flex-start`
     plus a fixed inset pins the gap at 24px on every screen, which is what was
     actually asked for. `overflow-y:auto` above still handles short screens.
     Only the `_cardShell` cards: the products and prize cards are top-aligned
     in the app too, and keep the app's own insets (below). */
  justify-content:flex-start;
  padding-top:24px;
  padding-bottom:24px;
  /* The app's card scrolls with no visible bar on a phone; a classic bar
     here would also narrow the card by its own width. */
  scrollbar-width:none;
}
.deck__card::-webkit-scrollbar{display:none}
/* visual + 28 + caption (_cardShell :14411, _caption :14376): 22px w900
   #1A1A1A at 1.25, then 8 and 15px w500 black54 at 1.35. Partner copy (the
   doc's `copy` lines) can carry line breaks, and Flutter's Text keeps them. */
.deck__cap{margin-top:28px;text-align:center}
.deck__t{font-size:22px;font-weight:900;line-height:1.25;color:#1A1A1A;white-space:pre-line}
.deck__s{margin-top:8px;font-size:15px;font-weight:500;line-height:1.35;color:rgba(0,0,0,.54);white-space:pre-line}
/* _iconBadge (:14366): 104px circle of theme-at-12% with a 48px themed glyph
   (the prize image's error stand-in). */
.deck__circ{
  width:104px;height:104px;border-radius:50%;flex:none;
  background:color-mix(in srgb,var(--theme) 12%,transparent);
  display:flex;align-items:center;justify-content:center;
}
.deck__circ svg{width:48px;height:48px;fill:var(--theme);display:block}

/* Products card (_cardProducts :14581): the caption pinned on TOP with no
   inset (the app's Column starts at the page's edge), 18, then the list,
   which is the only thing that scrolls, 18 between rows, down to the page's
   8px foot. Horizontal overflow is clipped as the ListView clips a deck's
   back cards. No visible scrollbar: the app's list shows none on a phone. */
.deck__card--list{
  justify-content:flex-start;align-items:stretch;overflow:hidden;
  padding-top:0;padding-bottom:0;
}
.deck__cap--top{margin-top:0;margin-bottom:18px;flex:none}
.deck__cats{
  flex:1;min-height:0;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;
  display:flex;flex-direction:column;gap:18px;scrollbar-width:none;
}
.deck__cats::-webkit-scrollbar{display:none}
.deck__cat{flex:none}
/* A row's title: 15px w800, 2px in and 10 above its tiles, or 2 above the
   type's caption (12.5px w500 black45, 10 above the tiles) (:14639). */
.deck__catn{
  font-size:15px;line-height:1.2;font-weight:800;color:#1A1A1A;
  margin:0 0 10px 2px;text-align:left;
}
.deck__catn--cap{margin-bottom:2px}
.deck__catc{margin:0 0 10px 2px;font-size:12.5px;font-weight:500;color:rgba(0,0,0,.45);text-align:left}
/* Category rows (_deckCategoryRow :14809): LEFT-aligned so a short last row
   starts at the first tile's x; Wrap spacing 12, runSpacing 16. The name is
   the FULL name (_buildSkuTile :14078), so the two-line clamp comes off. */
.skugrid--start{justify-content:flex-start}
.deck__cat .skugrid{gap:16px 12px}
.deck__cat .skugrid .nm{display:block;overflow:visible}
.tile__empty{width:30px;height:30px;display:block;fill:rgba(0,0,0,.26)}
/* Type rows (_deckGroupRow :14633 / _deckProductTile :14693): the page's own
   stack, three to a row at --dkw = min(124, (width - 24) / 3), the 124 box
   scaled into it as the app's FittedBox does (--dks = --dkw / 124, set from
   the list's measured width). Wrap 12 / 16; the name 8 under the tile, 12px
   w800 at 1.25, centred, never clamped. */
.deck .prodrow--deck{
  display:flex;flex-wrap:wrap;gap:16px 12px;
  overflow:visible;margin:0;padding:0;
}
.deck .prodrow--deck>li{width:var(--dkw,104px)}
.deck .prodrow--deck .dkbox{position:relative;width:var(--dkw,104px);height:var(--dkw,104px)}
.deck .prodrow--deck .skudeck{
  position:absolute;left:0;top:0;
  transform:scale(var(--dks,.84));transform-origin:0 0;
}
.deck .prodrow--deck .nm{
  display:block;overflow:visible;margin:8px 0 0;
  font-size:12px;line-height:1.25;font-weight:800;color:#1A1A1A;text-align:center;
}

/* Where to buy (_cardWhere :14908, _deckStoreTile :14871): 80x80 white
   tiles, 3 padding, r16, three to a centred row 12 apart (3 x 80 + 2 x 12 =
   264). The selected tile wears a 1.5px theme border, the rest 1px black 12%;
   nothing dims or grows. */
.deck__where{width:100%}
.deck__stores{
  display:flex;flex-wrap:wrap;justify-content:center;gap:12px;
  max-width:264px;margin:0 auto;
}
.deck__store{
  width:80px;height:80px;flex:none;padding:3px;border-radius:16px;
  background:#fff;border:1px solid rgba(0,0,0,.12);
  display:flex;align-items:center;justify-content:center;cursor:pointer;
}
.deck__store.on{border:1.5px solid var(--theme)}
.deck__store img{width:100%;height:100%;object-fit:contain;display:block}
.deck__storex{width:32px;height:32px;display:block;fill:rgba(0,0,0,.26)}
/* The selected store's panel, 18 under the tiles: margin 4 at the sides,
   padding 16/14, r16 (circular), theme 7% (or the draw's
   storeSpotlightColor under `ruleRows`, set inline). Head: logo 24, 8, name
   16 w900; 8, a 1px theme-18% divider, 10. */
.deck__panel{margin:18px 4px 0;padding:14px 16px;border-radius:16px;text-align:left}
.deck__ph{display:flex;align-items:center;gap:8px}
.deck__phlogo{width:24px;height:24px;flex:none}
.deck__phlogo img{width:100%;height:100%;object-fit:contain;display:block}
.deck__ph b{flex:1;min-width:0;font-size:16px;font-weight:900;color:#1A1A1A}
.deck__pdiv{display:block;height:1px;margin:8px 0 10px;background:color-mix(in srgb,var(--theme) 18%,transparent)}
/* A `rules` bullet: 4 above, a 5px theme dot 7 down, 8, then 13px w500 #333
   at 1.4. */
.deck__rule{display:flex;align-items:flex-start;gap:8px;padding-top:4px;font-size:13px;line-height:1.4;font-weight:500;color:#333}
.deck__rule::before{content:'';flex:none;width:5px;height:5px;margin-top:7px;border-radius:50%;background:var(--theme)}

/* Prize card (_cardWin :15073): no caption, top-aligned, 12 in at the sides,
   8 on top (48 for Mellolica) and 8 at the foot. The image is 220 high,
   contained, r20, tap to zoom; the summary 18 under it, 16px w800 at 1.4,
   centred; then the Prizes tab's own table (24 above, .ptable) and list (30
   above). */
.deck__card--win{padding:8px 12px}
.deck__card--mello{padding-top:48px}
.deck__card--win > *{flex:none}
.deck__prize{
  display:block;height:220px;width:auto;max-width:100%;
  object-fit:contain;border-radius:20px;cursor:zoom-in;
}
.deck__psum{font-size:16px;line-height:1.4;font-weight:800;color:#1A1A1A;text-align:center}
.deck__psum--gap{margin-top:18px}
.deck__card--win .ptable{width:100%}
.deck__plist{width:100%;margin-top:30px;text-align:left}
.deck__plist>.prizehdr:first-child{margin-top:0}

/* Active dot stretches to 22x8, the rest stay 8x8 at 25% (:15340).
   AnimatedContainer's default curve is linear. */
.deck__dots{display:flex;justify-content:center;padding-bottom:14px}
.deck__dots span{
  width:8px;height:8px;margin:0 4px;border-radius:4px;
  background:color-mix(in srgb,var(--theme) 25%,transparent);
  transition:width .2s linear, background .2s linear;
}
.deck__dots span.on{width:22px;background:var(--theme)}

/* ---- deck card animations ----------------------------------------------
   Static chrome only; every moving value is written per frame from JS. */
.anim{display:flex;flex-direction:column;align-items:center}
.anim__ticket{display:block;flex:none}
.anim__ticket svg{width:100%;height:100%;fill:var(--anim-theme,var(--theme));display:block}
/* The ticket + "×n" row: 26px ticket, 6, 22px w900 in the theme, in a 30px
   slot 18 under the pill (SizedBox(height: 30) :16912). */
.anim__earn{
  display:flex;align-items:center;justify-content:center;gap:6px;
  height:30px;margin-top:18px;flex:none;
}
.anim__n{
  font-size:22px;font-weight:900;color:var(--anim-theme,var(--theme));
  line-height:1;
}

/* Buy + scan (_BuyScanAnim :16711): a tall, thin phone (96x188) with a 2.5px
   bezel, not a chunky tablet. The app's Padding(9), Positioned(top: 7) and
   Positioned.fill are measured from the phone's OUTER edge (the bezel is
   painted inside the 96x188), while an absolute box here is placed from
   INSIDE the border, so each takes the 2.5 off. */
.bs__phone{
  position:relative;width:96px;height:188px;flex:none;
  background:#F2F3F5;border:2.5px solid #1A1A1A;border-radius:22px;
}
.bs__screen{
  position:absolute;inset:6.5px;overflow:hidden;border-radius:12px;
  background:#fff;padding:10px 9px;
  display:flex;flex-direction:column;align-items:flex-start;
}
.bs__line{height:4px;border-radius:2px;background:rgba(0,0,0,.12);margin-bottom:5px;display:block;flex:none}
/* `Spacer()` before the last line pins it to the bottom of the receipt. */
.bs__line--last{margin-top:auto;margin-bottom:0}
/* The scan beam: a 3px bar with a glow in the theme colour. */
.bs__beam{
  position:absolute;left:0;right:0;height:3px;
  background:var(--anim-theme,var(--theme));
  box-shadow:0 0 8px color-mix(in srgb,var(--anim-theme,var(--theme)) 60%,transparent);
  opacity:0;
}
.bs__flash{position:absolute;inset:-2.5px;border-radius:22px;background:#fff;opacity:0;pointer-events:none}
.bs__lens{position:absolute;top:4.5px;left:50%;margin-left:-3px;width:6px;height:6px;border-radius:50%;background:#1A1A1A}

/* The captured-fields pill (_BuyScanAnim / _OrderIdAnim): 12 under the phone,
   padding 14/10, theme 8%, r12, shrink-wrapped and centred; it grows row by
   row because each row collapses its own height (_deckCapturedRow :16950). */
.cap__pill{
  margin-top:12px;padding:10px 14px;border-radius:12px;flex:none;
  background:color-mix(in srgb,var(--anim-theme,var(--theme)) 8%,transparent);
  display:flex;flex-direction:column;align-items:flex-start;opacity:0;
}
.cap__row{overflow:hidden;height:0}
.cap__in{display:flex;align-items:center;gap:6px;padding:3.5px 0;opacity:0}
.cap__ic{width:15px;height:15px;flex:none;display:block;fill:var(--anim-theme,var(--theme))}
.cap__in span{font-size:13px;font-weight:700;color:#1A1A1A;white-space:pre}

/* Order id (_OrderIdAnim :16998): phone 150x294, 3px #1A1A1A bezel, r28 on
   #F2F3F5; the white screen inset 10 from the outer edge (7 inside the
   bezel), r18, padding 9/20/9/12. In it: a 56x6 theme-55% bar, 5, a 40x5
   black-12% bar, 14, the 30-high order-id field (r8, 1.2px border, 7/5 in),
   8, the 28-high Verify button (r8, 11px w800 white), then the 42px green
   check centred in what is left. The lens is 7px, 7 from the top. */
.oi__phone{
  position:relative;width:150px;height:294px;flex:none;
  background:#F2F3F5;border:3px solid #1A1A1A;border-radius:28px;
}
.oi__screen{
  position:absolute;inset:7px;overflow:hidden;border-radius:18px;
  background:#fff;padding:20px 9px 12px;
  display:flex;flex-direction:column;align-items:flex-start;
}
.oi__bar{display:block;border-radius:2px;flex:none}
.oi__bar--a{width:56px;height:6px;background:color-mix(in srgb,var(--anim-theme,var(--theme)) 55%,transparent)}
.oi__bar--b{width:40px;height:5px;margin-top:5px;background:rgba(0,0,0,.12)}
.oi__field{
  align-self:stretch;flex:none;margin-top:14px;height:30px;padding:0 5px 0 7px;
  background:#fff;border:1.2px solid rgba(0,0,0,.12);border-radius:8px;
  display:flex;align-items:center;
}
/* One line, clipped (maxLines 1, TextOverflow.clip): 9px w800 #1A1A1A. */
.oi__id{
  flex:1;min-width:0;overflow:hidden;white-space:nowrap;
  font-size:9px;font-weight:800;color:#1A1A1A;opacity:0;
}
/* `_tapped` (:17035): the child with a 24px fingertip centred over it. */
.oi__tap{position:relative;display:flex;align-items:center;justify-content:center;flex:none}
.oi__paste{width:13px;height:13px}
/* While pressed the app's Stack sizes to the 24px fingertip. */
.oi__paste.is-pressed{width:24px;height:24px}
.oi__pasteic{width:13px;height:13px;display:block;flex:none}
.oi__finger{
  position:absolute;left:50%;top:50%;width:24px;height:24px;margin:-12px 0 0 -12px;
  border-radius:50%;border:1.5px solid transparent;visibility:hidden;pointer-events:none;
}
.oi__vwrap{align-self:stretch;margin-top:8px}
.oi__verify{
  flex:1;min-width:0;height:28px;border-radius:8px;
  display:flex;align-items:center;justify-content:center;
  font-size:11px;font-weight:800;color:#fff;white-space:nowrap;overflow:hidden;
}
.oi__mid{flex:1;align-self:stretch;display:flex;align-items:center;justify-content:center}
.oi__check{
  width:42px;height:42px;border-radius:50%;background:#34A853;
  display:flex;align-items:center;justify-content:center;opacity:0;
}
.oi__check svg{width:28px;height:28px;fill:#fff;display:block}
.oi__lens{position:absolute;top:4px;left:50%;margin-left:-3.5px;width:7px;height:7px;border-radius:50%;background:#1A1A1A}

/* Per receipt / order (_PerReceiptAnim :17324): the chip sits in a fixed
   72px slot so the ticket below never moves as the chip pops. */
.pr__slot{height:72px;display:flex;align-items:center;justify-content:center;flex:none}
.pr__chip{
  position:relative;display:flex;align-items:center;gap:6px;
  padding:9px 14px;border-radius:14px;background:#fff;
  border:1px solid color-mix(in srgb,var(--anim-theme,var(--theme)) 30%,transparent);
  box-shadow:0 2px 8px rgba(0,0,0,.06);
}
.pr__ic{width:18px;height:18px;flex:none}
.pr__ic svg{width:18px;height:18px;fill:var(--anim-theme,var(--theme));display:block}
.pr__amt{font-size:18px;font-weight:900;color:#1A1A1A;line-height:1}
/* The verdict badge overhangs the chip's top-right corner. */
.pr__badge{
  position:absolute;top:-8px;right:-8px;
  width:22px;height:22px;border-radius:50%;border:2px solid #fff;
  display:flex;align-items:center;justify-content:center;transform:scale(0);
}
.pr__badge svg{width:14px;height:14px;fill:#fff;display:block}
/* An order draw with a bonus window: a 26px row under the chip carries the
   bonus order's tag (12px w800 white on a theme stadium, 10/3 in); every
   other draw keeps the plain 22px gap. */
.pr__tagrow{height:26px;display:flex;align-items:center;justify-content:center;flex:none}
.pr__tag{
  padding:3px 10px;border-radius:999px;background:var(--anim-theme,var(--theme));
  font-size:12px;font-weight:800;color:#fff;white-space:nowrap;opacity:0;
}
.anim .pr__slot + .anim__earn{margin-top:22px}
.anim .pr__tagrow + .anim__earn{margin-top:0}

/* Cumulative (_CumulativeAnim :17603): a 60px chip slot (accent-3 receipt
   glyph and a 55% accent-3 border), 16, the running total 26px w900 in the
   theme, 12, a 220x14 bar (r8, #EDEFF2 track, theme -> accent-3 fill), 8,
   "฿n / ฿per" 12px w700 black45, 16, then the ticket row with the accent-2
   "+฿carry" pill 10 after the count. */
.cu__slot{height:60px;display:flex;align-items:center;justify-content:center;flex:none}
.cu__chip{
  display:flex;align-items:center;gap:6px;padding:9px 14px;border-radius:14px;
  background:#fff;border:1px solid var(--cu-a3-55,rgba(0,0,0,.15));
  box-shadow:0 2px 8px rgba(0,0,0,.06);opacity:0;
}
.cu__ic svg{width:18px;height:18px;display:block;fill:var(--cu-a3,var(--theme))}
.cu__amt{font-size:18px;font-weight:900;color:#1A1A1A;line-height:1}
.cu__total{margin-top:16px;font-size:26px;font-weight:900;color:var(--anim-theme,var(--theme))}
.cu__bar{margin-top:12px;width:220px;height:14px;border-radius:8px;overflow:hidden;background:#EDEFF2;flex:none}
.cu__fill{display:block;height:100%;width:0;background:linear-gradient(90deg,var(--anim-theme,var(--theme)),var(--cu-a3,var(--theme)))}
.cu__of{margin-top:8px;font-size:12px;font-weight:700;color:rgba(0,0,0,.45)}
.anim .cu__earn{margin-top:16px;height:auto}
.cu__carry{
  margin-left:4px;padding:5px 10px;border-radius:12px;background:var(--cu-a2,var(--theme));
  font-size:13px;font-weight:900;color:#1A1A1A;
}
.cu__carry[hidden]{display:none}
@supports not (color: color-mix(in srgb, red 50%, transparent)){
  .cap__pill{background:rgba(0,0,0,.05)}
  .pr__chip{border-color:rgba(0,0,0,.15)}
  .oi__bar--a{background:rgba(0,0,0,.2)}
  .bs__beam{box-shadow:0 0 8px rgba(0,0,0,.3)}
}

/* ---- deck entrance (_StoreLogoIn :17846) --------------------------------
   Every store logo and product row enters with a staggered, buoyant pop:
   520ms, `index * 90ms` apart, sliding up 32px and scaling 0.8 -> 1 on
   Curves.easeOutBack, while the opacity ramps LINEARLY over the same window.

   Two animations, not one, precisely because of that split: the app applies
   easeOutBack to the transform only and reads the raw controller value for
   opacity, so a single keyframe set with one easing would overshoot the fade
   too. Flutter's easeOutBack is Cubic(0.175, 0.885, 0.32, 1.275), which is
   NOT the (.34,1.56,.64,1) easings.net curve this used to carry: that one
   overshoots about twice as far.

   It plays when the card BECOMES ACTIVE (`play: active`), so it replays every
   time you land on that card — not once when the deck opens. */
.deck__page .deckin{opacity:0}
.deck__page.is-active .deckin{
  animation:
    deckInFade .52s linear both,
    deckInMove .52s cubic-bezier(.175,.885,.32,1.275) both;
  animation-delay:calc(var(--i,0) * 90ms), calc(var(--i,0) * 90ms);
}
@keyframes deckInFade{from{opacity:0}to{opacity:1}}
@keyframes deckInMove{
  from{transform:translateY(32px) scale(.8)}
  to{transform:none}
}
@media (prefers-reduced-motion:reduce){
  .deck__page .deckin{opacity:1}
  .deck__page.is-active .deckin{animation:none}
}

/* Full-width CTA (:15354): Icons.arrow_forward_rounded 22 until the last
   card, then "เข้าใจแล้ว" 15px w800; 14 tall padding, 28 in, 12 under.
   Squircle r16 smoothing 1.0, so it takes the fitted pair: 37px +
   superellipse(2.2), with 18 as the circular fallback. */
.deck__cta{
  margin:0 28px 12px;padding:14px;
  border-radius:18px;
  background:var(--theme);color:var(--white);
  display:flex;align-items:center;justify-content:center;
  font-size:15px;font-weight:800;
}
.deck__cta svg{width:22px;height:22px;fill:currentColor;display:block}
@supports (corner-shape: superellipse(2.2)){
  .deck__cta{border-radius:37px;corner-shape:superellipse(2.2)}
}
@supports not (color: color-mix(in srgb, red 50%, transparent)){
  .deck__circ{background:rgba(0,0,0,.06)}
  .deck__dots span{background:rgba(0,0,0,.2)}
  .deck__pdiv{background:rgba(0,0,0,.1)}
}

/* ---- results page: an ended draw with published winners ----------------
   No tabs, no products, no stores — the campaign is over, so the body is the
   brand block and the winners list. */
.dend{padding:20px var(--pad-page) 0}
.dend__head{display:flex;align-items:flex-start;gap:14px;margin-bottom:26px}
.dend__logo{width:52px;height:52px;flex:none;border-radius:50%;object-fit:contain;background:var(--white)}
.dend__meta{flex:1;min-width:0}
.dend__brand{font-size:22px;font-weight:900;color:#1A1A1A;line-height:1.2}
.dend__tag{font-size:14px;color:rgba(0,0,0,.5);margin-top:2px}
/* luckyDrawCampaignEnded, in the app's red. */
.dend__over{font-size:14px;font-weight:700;color:#E5202E;margin-top:8px;line-height:1.35}
.dend__title{font-size:19px;font-weight:800;color:#1A1A1A;margin-bottom:16px}
.dend__list{display:flex;flex-direction:column;gap:10px}
/* _WinnerRow (:8140): #F4F5F7 at radius 12, 44px avatar, 16px gap. */
.dend__w{
  display:flex;align-items:center;gap:16px;
  background:#F4F5F7;border-radius:12px;padding:12px 14px;
}
/* The viewer's OWN winning row: brand yellow with a darker edge. */
.dend__w.is-me{background:#FFDB62;border:1.5px solid #E0A82E}
.dend__av{
  width:44px;height:44px;flex:none;border-radius:50%;overflow:hidden;
  background:var(--white);
  display:flex;align-items:center;justify-content:center;font-size:22px;
}
.dend__av img{width:100%;height:100%;object-fit:cover;display:block}
.dend__wn{
  flex:1;min-width:0;font-size:16px;font-weight:700;color:#1A1A1A;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}


/* ---- sign-in landing (signUpLogIn.dart:1828) --------------------------
   The app shows the title, then the sign-in options, then the description.
   The phone field only appears once the phone option is chosen. */
.btn--phone{
  display:flex;align-items:center;justify-content:center;gap:10px;
  width:100%;min-height:52px;
}
.btn--phone .btn__ph{width:20px;height:20px;flex:none;display:block}
.btn--phone .btn__ph svg{width:20px;height:20px;fill:var(--white);display:block}
.auth-note--center{text-align:center}

/* "or sign in with" — a rule with the label sitting in a gap in it. */
.auth-or{
  display:flex;align-items:center;gap:12px;
  margin:22px 0 16px;color:var(--faint);font-size:12.5px;font-weight:600;
}
.auth-or:before,.auth-or:after{content:'';flex:1;height:1px;background:var(--line)}

/* ICON-ONLY, because four labelled buttons stacked under the primary one bury
   it. Each button carries an aria-label, so the provider name is still
   announced. Marks are drawn in the providers' own colours and never
   restyled — a recoloured Google or Apple button is the one thing users stop
   trusting. */
.socialrow{display:flex;justify-content:center;gap:14px}
.socialbtn{
  width:56px;height:56px;flex:none;border-radius:50%;
  background:var(--white);border:1px solid #DADCE0;
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 1px 2px rgba(60,64,67,.14);
  transition:transform .09s ease-out, background .12s ease;
}
.socialbtn svg{width:24px;height:24px;display:block}
.socialbtn:active{transform:scale(.95);background:#F7F8F8}
.socialbtn[disabled]{opacity:.5}

/* ── live receipt camera (livecam.js) ─────────────────────────────────────
   Geometry is the app's, translated one number at a time:

     mask       clear window 92.5%w x 90%h, everything else 40% black. The app
                shades top / left / right only, and the side rails run all the
                way to the bottom edge (cameraWidgets.dart:42-79) — there is no
                bottom band, which is what leaves room for the shutter.
     top row    the app pins these at top:60 from the physical top, ~47px of
                which is the status bar. A web viewport already starts below
                the status bar and any browser chrome, so the equivalent inset
                is the safe area plus ~14px, not a literal 60.
     shutter    outer 20vw, inner 16vw, 6px ring, bottom safe + 30
                (cameraWidgets.dart:1630-1636)
     hint       12px above the shutter (camera.dart:4286-4292) */

.lc {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: #000;
  overflow: hidden;
  animation: lcIn .18s ease-out both;
  --lc-glow: transparent;
}
.lc.is-out { animation: lcOut .2s ease-in both; }
@keyframes lcIn  { from { opacity: 0 } to { opacity: 1 } }
@keyframes lcOut { from { opacity: 1 } to { opacity: 0 } }

/* The page behind must not scroll under the camera. */
body.lc-open { overflow: hidden; }

/* `display:grid` on the controls outranks the UA stylesheet's
   [hidden]{display:none}, so the flash and retake buttons stayed on screen
   while marked hidden. Restore the attribute's meaning inside the camera. */
.lc [hidden] { display: none !important; }

.lc__video,
.lc__freeze {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #000;
}
.lc__video.is-frozen { visibility: hidden; }
.lc__freeze { z-index: 1; }

/* ── framing mask ──────────────────────────────────────────────────────── */
.lc__mask { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.lc__shade { position: absolute; background: rgba(0, 0, 0, .4); display: block; }
.lc__shade--top   { top: 0; left: 0; right: 0; height: 5%; }
.lc__shade--left  { top: 5%; bottom: 0; left: 0;  width: 3.75%; }
.lc__shade--right { top: 5%; bottom: 0; right: 0; width: 3.75%; }

/* ── tap-to-focus ring (cameraWidgets.dart:155-181) ────────────────────── */
.lc__focus {
  position: absolute;
  z-index: 4;
  width: 50px;
  height: 50px;
  margin: -25px 0 0 -25px;
  border: 3px solid #FF6B6B;
  border-radius: 50%;
  pointer-events: none;
}
.lc__focus.is-pulse { animation: lcFocus .5s ease-out; }
@keyframes lcFocus {
  0%   { transform: scale(1);   opacity: 1 }
  40%  { transform: scale(1.6); opacity: 1 }
  100% { transform: scale(1);   opacity: 1 }
}

/* ── top controls ──────────────────────────────────────────────────────── */
.lc__top {
  position: absolute;
  z-index: 5;
  top: calc(env(safe-area-inset-top, 0px) + 14px);
  left: 25px;
  right: 25px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.lc__topright { display: flex; gap: 12px; }

.lc__ic {
  width: 40px;
  height: 40px;
  border: 2px solid transparent;
  border-radius: 50%;
  background: #FEB600;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .2);
  display: grid;
  place-items: center;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.lc__ic.is-on {
  border-color: #FF6B6B;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .2), 0 0 8px 2px rgba(255, 107, 107, .5);
}
.lc__glyph { width: 20px; height: 20px; fill: #fff; display: block; }

/* ── framing hint ──────────────────────────────────────────────────────── */
.lc__hint {
  position: absolute;
  z-index: 5;
  left: 16px;
  right: 16px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 30px + min(20vw, 96px) + 12px);
  margin: 0;
  padding: 6px 12px;
  width: fit-content;
  max-width: calc(100% - 32px);
  margin-inline: auto;
  text-align: center;
  font-weight: 700;
  font-size: 12px;
  line-height: 1.3;
  border-radius: 12px;
  /* white 15% over a 10px backdrop blur, hairline white 25% border
     (camera.dart:4352-4366) */
  background: rgba(255, 255, 255, .15);
  border: 1.2px solid rgba(255, 255, 255, .25);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  opacity: 0;
  transition: opacity .2s ease;
  pointer-events: none;
}
.lc__hint.is-on { opacity: 1; }

/* ── shutter ───────────────────────────────────────────────────────────── */
.lc__shutter {
  position: absolute;
  z-index: 6;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 30px);
  transform: translateX(-50%);
  width: min(20vw, 96px);
  height: min(20vw, 96px);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  display: grid;
  place-items: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.lc__shutter > * { grid-area: 1 / 1; }

/* Sweep gradient ring, 6px, drawn as a masked conic so the centre stays clear
   (_PreviewRingPainter:2186). */
.lc__ring {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: conic-gradient(#FF6B6B 0deg, #FFBD71 119deg, #EEDC82 238deg, #FF6B6B 360deg);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));
  transition: opacity .35s ease;
}

/* Solid state ring just outside the gradient one; blurSigma 0 in the app, so a
   hard 2px rather than a glow (_PreviewOuterGlowPainter:2262). */
.lc__glow {
  width: calc(100% + 4px);
  height: calc(100% + 4px);
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--lc-glow);
  transition: box-shadow .35s ease;
}

.lc__shic { display: grid; place-items: center; }
.lc__shglyph { width: min(6.1vw, 29px); height: min(6.1vw, 29px); fill: #fff; display: block; }

/* Blocked: the glyph becomes a red x at the app's larger 0.52 inner size and
   the ring dims, so the button reads as unavailable without disappearing. */
.lc__shutter.is-off .lc__ring { opacity: .35; }
.lc__shutter.is-off .lc__shglyph {
  width: min(8.3vw, 40px); height: min(8.3vw, 40px);
  fill: rgba(242, 53, 64, .3);
}
.lc__shutter.is-confirm .lc__ring { opacity: 1; }

/* ── retake (camera.dart:4601, left 84 / bottom 104 minus the app's padding) */
.lc__retake {
  position: absolute;
  z-index: 6;
  left: 24px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 30px + min(20vw, 96px) / 2 - 20px);
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, .45);
  display: grid;
  place-items: center;
  cursor: pointer;
  animation: lcRetakeIn .5s cubic-bezier(.34, 1.56, .64, 1) both;
  -webkit-tap-highlight-color: transparent;
}
@keyframes lcRetakeIn {
  from { transform: scale(.6) rotate(-180deg); opacity: 0 }
  to   { transform: scale(1)  rotate(0deg);    opacity: 1 }
}

@media (prefers-reduced-motion: reduce) {
  .lc, .lc.is-out, .lc__focus.is-pulse, .lc__retake { animation: none; }
}

/* ── receipt result screen (receipt_result.js) ────────────────────────────
   Ported from lib/widgets/receiptResult.dart. Full-screen over the mini shell,
   because the app's version is its own route with no stat bar and no tabs.

   THREE SHAPE FAMILIES, and they are genuinely different:
     · sq56          SmoothRectangleBorder(56, smoothing 1.0) — figma_squircle.
                     Fitted the same way as .ldcard: the CSS superellipse that
                     matches it needs r x2.29 and superellipse(2.2), NOT r56.
     · plain16       BorderRadius.circular(16) on the handwritten card (:3460).
                     A plain arc, so a plain 16px.
     · continuous44  ContinuousRectangleBorder(44) on the unknown-error card.
                     Flutter UNDER-renders this family, so 44 there reads as
                     roughly a 22px arc — the same finding as the auth sheet. */

.rr {
  position: fixed;
  inset: 0;
  z-index: 95;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 16px;
  display: flex;
  animation: rrIn .2s ease-out both;
}
body.rr-open { overflow: hidden; }
@keyframes rrIn { from { opacity: 0 } to { opacity: 1 } }

.rr--warn    { background: #FEB600; }
.rr--success { background: linear-gradient(to bottom, #FF6B6B 0%, #FFBD71 50%, #EEDC82 100%); }

/* The app centres the card + buttons as a GROUP on screen (a Center inside a
   min-height box), not pinned to the top. */
.rr__stack {
  margin: auto;
  width: 100%;
  max-width: 350px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

/* ── the warning card ──────────────────────────────────────────────────── */
.rr__card {
  background: #fff;
  padding: 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  box-shadow: 0 4px 10px rgba(0, 0, 0, .1);
}

.rr__card--sq56 { border-radius: 64px; }
@supports (corner-shape: superellipse(2.2)) {
  .rr__card--sq56 { border-radius: 128px; corner-shape: superellipse(2.2); }
}
.rr__card--plain16 { border-radius: 16px; }
.rr__card--continuous44 { border-radius: 22px; }

/* The unknown-error card carries no icon and its own padding (36/40/36/72). */
.rr__card--bare { padding: 40px 36px 72px; justify-content: flex-start; }

.rr__ico {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  flex: none;
}
/* shade-600 glyph on a shade-50 wash; both come from the state's spec, set
   inline, so a new state cannot pick up the wrong tint by omission. */
.rr__glyph { width: 48px; height: 48px; display: block; fill: currentColor; }

.rr__title {
  margin: 24px 0 0;
  font-size: 24px;
  font-weight: 700;
  line-height: 1.25;
}
.rr__card--bare .rr__title { margin-top: 0; font-size: 26px; }

.rr__body {
  margin: 16px 0 0;
  font-size: 16px;
  color: #9E9E9E;
  line-height: 1.35;
}
.rr__card--bare .rr__body { margin-top: 24px; font-size: 14px; line-height: 1.4; }

.rr__link {
  margin-top: 32px;
  padding: 8px 4px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
  color: #2196F3;
  -webkit-tap-highlight-color: transparent;
}
.rr__link--line { color: #06C755; text-decoration-color: #06C755; }

/* ── the button row ────────────────────────────────────────────────────── */
.rr__row { display: flex; gap: 12px; margin-top: 24px; }
.rr__btn {
  flex: 1 1 0;
  min-width: 0;
  padding: 16px 8px;
  border: 0;
  background: #fff;
  color: #000;
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  border-radius: 18px;
  -webkit-tap-highlight-color: transparent;
}
@supports (corner-shape: superellipse(2.2)) {
  .rr__btn { border-radius: 37px; corner-shape: superellipse(2.2); }
}

/* ── the success ticket ────────────────────────────────────────────────── */
.rr__stack--success { max-width: 325px; }
.rr__clip { position: absolute; width: 0; height: 0; }

/* clip-path removes the element's own shadow, so it is applied to a wrapper
   as a drop-shadow of the clipped silhouette — the app does the same thing
   with _TicketBorderShadowPainter (black26, elevation 10). */
.rr__ticketwrap { filter: drop-shadow(0 3px 10px rgba(0, 0, 0, .26)); }

.rr__ticket {
  position: relative;
  background: #fff;
  padding: 32px;
  min-height: 350px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  box-sizing: border-box;
}
/* object-fit is NOT optional here. The artwork is 4640x5774 (aspect 0.804),
   so a bare 64x64 box stretched it wide. Flutter's Image.asset defaults to
   BoxFit.scaleDown, which preserves the ratio inside the 64x64 the app asks
   for (receiptResult.dart:4945) — `contain` is that, for a source this much
   larger than the box. */
.rr__logo { width: 64px; height: 64px; object-fit: contain; display: block; }
.rr__earned { margin: 12px 0 0; font-size: 18px; font-weight: 700; line-height: 1.3; }
/* The app's fixed 40px gap between the headline and the store block; the
   dashed tear line sits inside it, at the notch centre. */
.rr__gap { height: 40px; flex: none; }

.rr__store { margin: 0; font-size: 28px; font-weight: 700; color: #000; line-height: 1.2; }
.rr__line {
  margin: 12px 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 20px;
  color: rgba(0, 0, 0, .87);
}
.rr__line + .rr__line { margin-top: 4px; }
/* calendar.webp is 56x45 and price.webp 58x47 — both landscape, so a square
   box squashed them vertically. Same fix as the logo above. */
.rr__line img { width: 22px; height: 22px; object-fit: contain; display: block; }

/* Dashed line at the notch centre (dy 150), spanning between the notches. */
.rr__dash {
  position: absolute;
  top: 150px;
  left: 4px;
  width: var(--rr-dash-w, calc(100% - 8px));
  height: 1px;
  background: repeating-linear-gradient(
    to right, #9E9E9E 0 1px, transparent 1px 9px);
}

/* ── per-entry cards ───────────────────────────────────────────────────── */
.rr__entries { margin-top: 20px; display: flex; flex-direction: column; gap: 12px; }
.rr__entry {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 64px;
  padding: 12px;
  background: #fff;
  border: 1px solid #EEEEEE;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .05);
}
.rr__ethumb {
  width: 64px;
  height: 64px;
  flex: none;
  border-radius: 8px;
  object-fit: cover;
  background: #EEEEEE;
  display: block;
}
.rr__ethumb--none { display: grid; place-items: center; }
.rr__eglyph { width: 24px; height: 24px; fill: none; stroke: #BDBDBD; stroke-width: 1.6; }
.rr__emeta { flex: 1; min-width: 0; text-align: left; }
.rr__emeta > b {
  font-size: 14px;
  font-weight: 600;
  color: #1A1A1A;
  line-height: 1.3;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* multiplier row: 13px icon + 11.5px label. Standard is a grey outlined
   ticket at w500; doubled is a bolt at w800, both in the entry's own colour
   (receiptResult.dart:5404-5443). */
.rr__emult {
  margin-top: 2px;
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  font-size: 11.5px;
  font-weight: 500;
  color: #757575;
}
.rr__emult i { font-style: normal; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rr__emult.is-x2 { font-weight: 800; color: var(--rr-theme); }
.rr__emicon { width: 13px; height: 13px; flex: none; fill: #9E9E9E; display: block; }
.rr__emult.is-x2 .rr__emicon { fill: var(--rr-theme); }

/* "+ [ticket] N" — one row under the multiplier, NOT pinned to the card's
   right edge; that was the old mini layout, not the app's. */
.rr__ecount {
  margin-top: 4px;
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 16px;
  font-weight: 700;
  color: var(--rr-theme);
}
.rr__ecount img { width: 22px; height: 22px; display: block; }
.rr__ecount b { font-weight: 700; }

/* ── a verified ORDER (receipt_result.js orderSuccessMarkup) ─────────────
   BatchReceiptResultPage's entries-only branch, numbers from
   batchReceiptResult.dart. */
.rr__stack--order { display: flex; flex-direction: column; align-items: center; }
.rr__stack--order > * { align-self: stretch; }
/* `_EntryPill` (:1510): hugs its contents, centred, 16 above the ticket. */
.rr__epill {
  align-self: center !important;
  display: inline-flex; align-items: center; gap: 8px;
  height: 40px; padding: 4px 10px; box-sizing: border-box; margin-bottom: 16px;
  background: #fff; border-radius: 24px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .1);
}
.rr__epillt { width: 52px; height: 32px; display: flex; align-items: center; justify-content: center; }
.rr__epilln { font-size: 20px; font-weight: 700; color: var(--rr-ink); line-height: 1; font-variant-numeric: tabular-nums; }
.rr__eart, .rr__otart { display: block; object-fit: contain; flex: none; }
.rr__eart--none, .rr__otart--none { display: grid; place-items: center; }
.rr__tglyph { width: 100%; height: 100%; fill: currentColor; display: block; }
/* `_BatchTicketCard` (:1253-1258): padding 32/24, min 320. */
.rr__ticket--order { padding: 24px 32px; min-height: 320px; }
/* The carousel page's Padding(6, 10, 6, 0) puts 10 above the ticket; the
   measured carousel adds 14 under it for the shadow (_kTicketShadowRoom). */
.rr__stack--order .rr__ticketwrap { margin-top: 10px; }
/* SizedBox(64x64), the 100x100 pulse centred on it. */
.rr__logoslot { position: relative; width: 64px; height: 64px; flex: none; }
.rr__intro {
  position: absolute; left: -18px; top: -18px; width: 100px; height: 100px;
  /* max-width:none is load-bearing: the global img{max-width:100%} caps this
     100px logo at its 64px slot, so it drew from the slot's left edge and sat
     18px left of centre while it pulsed (and the tickets flew from there). */
  max-width: none;
  object-fit: contain; pointer-events: none; transform-origin: 50% 50%;
}
/* AnimatedOpacity(500ms) on the fixed logo and the earned line, from the burst. */
.rr__fade { opacity: 0; transition: opacity .5s ease; }
.rr.is-burst .rr__fade { opacity: 1; }
/* The flying tickets: fixed, positioned by transform from the top-left. */
.rr__fly {
  position: fixed; left: 0; top: 0; z-index: 2; pointer-events: none;
  display: block; object-fit: contain; will-change: transform, opacity;
  color: var(--rr-ink, #FF52A8);
}
/* `_buildLuckyDrawEntries` (:741): white, r12, #EEEEEE line, shadow black-5%
   blur 8 (0,2), padding 8, 8 between tiles; 64x64 r8 cover thumb (GM 80x100),
   10 gap, the title 14 w600 #1A1A1A two lines, 8 gap, "+N" 18 bold in the
   draw colour, 4 gap, its ticket art 30 wide. */
.rr__otiles { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; }
.rr__otile {
  display: flex; align-items: center; width: 100%; padding: 8px; box-sizing: border-box;
  background: #fff; border: 1px solid #EEEEEE; border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .05);
  font: inherit; color: inherit; text-align: left; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.rr__othumb { width: 64px; height: 64px; flex: none; border-radius: 8px; object-fit: cover; background: #EEEEEE; display: block; }
.rr__othumb.is-gm { width: 80px; height: 100px; }
.rr__othumb--none { display: grid; place-items: center; }
.rr__otitle {
  flex: 1; min-width: 0; margin-left: 10px;
  font-size: 14px; font-weight: 600; color: #1A1A1A; line-height: 1.3;
  overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.rr__ocount { margin-left: 8px; font-size: 18px; font-weight: 700; color: var(--rr-theme); }
.rr__otile .rr__otart { margin-left: 4px; }
/* `_buildButtons` (:1050): 20 above, 52 tall, 18 w700, SmoothRadius 14
   (cornerSmoothing 1.0, the 2.29x superellipse pair). */
.rr__row--order { margin-top: 20px; }
.rr__row--order .rr__btn { min-height: 52px; padding: 0 8px; font-size: 18px; font-weight: 700; border-radius: 16px; }
@supports (corner-shape: superellipse(2.2)) {
  .rr__row--order .rr__btn { border-radius: 32px; corner-shape: superellipse(2.2); }
}

@media (prefers-reduced-motion: reduce) { .rr { animation: none; } }

/* ── scanning animation (scan_progress.js) ────────────────────────────────
   The app's processing screen is a white scaffold holding ONE full-bleed
   looping video at BoxFit.cover, nothing layered on it (ocrProcessor.dart
   :3665-3682). White shows through until the first frame arrives, which is
   the app's behaviour too. z-index sits just under .rr so the result screen
   can land on top and the shell never flashes between the two. */

.sp {
  position: fixed;
  inset: 0;
  z-index: 94;
  background: #fff;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
body.sp-open { overflow: hidden; }

.sp__v {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;      /* BoxFit.cover */
  background: #fff;
  /* Hidden until it actually plays, so a black poster frame or a stalled
     decode never flashes over the white. */
  opacity: 0;
  transition: opacity .2s ease;
}
.sp.is-playing .sp__v { opacity: 1; }

/* The fallback only appears when the video cannot play at all. */
.sp__fallback {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 340px;
  padding: 0 20px;
  text-align: left;
}
.sp__fallback[hidden] { display: none; }
.sp__fallback .spinner { margin: 0 auto 18px; }
.sp__fallback .empty { padding: 20px 8px 0; }

/* ── e-receipt upload page (upload.js) ────────────────────────────────────
   Ported from batchUploadPreview.dart in gallery-picking mode. Squircles use
   this project's fitted mapping for figma_squircle cornerSmoothing 0.6:
   painted radius x1.4 with corner-shape superellipse(1.6), and the nominal
   radius as the fallback for browsers without corner-shape. */

.up {
  position: fixed;
  inset: 0;
  z-index: 88;
  /* Colors.white (:436) — the app's scaffold for this screen, NOT the page's
     #F6F7F9 wash. The wash read as a grey sheet behind the tiles. */
  background: #fff;
  display: flex;
  flex-direction: column;
  animation: upIn .18s ease-out both;
}
@keyframes upIn { from { opacity: 0 } to { opacity: 1 } }
body.up-open { overflow: hidden; }
.up [hidden] { display: none !important; }

/* ── header ────────────────────────────────────────────────────────────── */
.up__bar {
  flex: none;
  display: grid;
  grid-template-columns: 48px 1fr 48px;
  align-items: center;
  padding-top: env(safe-area-inset-top, 0px);
  height: calc(56px + env(safe-area-inset-top, 0px));
}
.up__title {
  margin: 0;
  text-align: center;
  font-size: 20px;
  font-weight: 600;
  color: var(--ink);
}
.up__ic {
  width: 48px; height: 48px;
  display: grid; place-items: center;
  background: none; border: 0; padding: 0; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.up__glyph { width: 24px; height: 24px; fill: var(--ink); display: block; }

/* ── body ──────────────────────────────────────────────────────────────── */
.up__body { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; }

.up__empty {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 0 32px;
  text-align: center;
}
.up__empty h2 { margin: 0; font-size: 18px; font-weight: 600; color: var(--ink); }
.up__empty p  { margin: 8px 0 24px; font-size: 14px; color: rgba(0, 0, 0, .54); }

.up__btn {
  width: 100%;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 0;
  font: inherit;
  font-weight: 700;
  color: rgba(0, 0, 0, .87);
  background: #FEB600;
  cursor: pointer;
  border-radius: 16px;
}
@supports (corner-shape: superellipse(1.6)) {
  .up__btn { border-radius: 22px; corner-shape: superellipse(1.6); }
}
.up__bglyph { width: 20px; height: 20px; fill: rgba(0, 0, 0, .87); display: block; }

/* ── grid ──────────────────────────────────────────────────────────────── */
.up__grid {
  display: flex;
  flex-wrap: wrap;
  /* Left, not centred. The Dart says WrapAlignment.center but the app renders
     a lone tile against the left gutter, and that is what it has to match —
     centring parked a single receipt in the middle of an empty screen. */
  justify-content: flex-start;
  gap: 12px;
  padding: 16px;
}
.up__tile {
  position: relative;
  /* Two columns: half the row minus the gap and the container's padding. */
  width: calc((100% - 12px) / 2);
  aspect-ratio: 1 / 1.45;
  background: #f5f5f5;
  overflow: hidden;
  cursor: pointer;
  /* SmoothRectangleBorder(cornerRadius: 12, cornerSmoothing: 0.6) (:657).
     Fitted per the 0.6 table: r x1.5 with superellipse(1.6), and r x1.07 as
     the plain fallback. */
  border-radius: 13px;
}
@supports (corner-shape: superellipse(1.6)) {
  .up__tile { border-radius: 18px; corner-shape: superellipse(1.6); }
}
.up__tile img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;          /* never crop a receipt */
}
/* Unselected reads as dimmed, which is the app's cue that a tap puts it back.
   The overlay MUST inherit the tile's shape. It did not, and that is what made
   the selected border look uneven: a pseudo-element defaults to
   `border-radius: 0` and `corner-shape: round`, so the yellow ring was drawn
   square and then clipped to the squircle by `overflow: hidden`, thinning it
   at each corner by a different amount. */
.up__tile::after {
  content: "";
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, .18);
  pointer-events: none;
  border-radius: inherit;
  corner-shape: inherit;
}
/* A real border, not an inset shadow — the app draws BorderSide(width: 3) ON
   the SmoothRectangleBorder (:743-748), so the stroke follows the superellipse
   and stays an even 3px the whole way round. An inset shadow traces the
   padding box and cannot. */
.up__tile.is-on::after {
  background: none;
  border: 3px solid #FEB600;
}

.up__badge {
  position: absolute;
  top: 6px;
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border: 1.5px solid #fff;
  border-radius: 50%;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.up__rm   { left: 6px;  background: rgba(0, 0, 0, .55); }
.up__tick { right: 6px; background: rgba(0, 0, 0, .35); }
.up__tile.is-on .up__tick { background: #FEB600; }
.up__bd { width: 20px; height: 20px; display: block; }
.up__rm .up__bd { fill: #fff; }
.up__tick .up__bd { fill: rgba(0, 0, 0, .87); }

/* Full-image control, bottom-right. Same 32px circle as the other two badges
   but a thinner 1.2px rule and a flatter black-55% fill (:855-861), and it sits
   on the tile's 4px bottom row rather than the 6px top inset. */
.up__zoom {
  top: auto;
  bottom: 4px; right: 4px;
  background: rgba(0, 0, 0, .55);
  border-width: 1.2px;
}
.up__zoom .up__bd { fill: #fff; }

/* _FullImageViewer (:1559): black scaffold, white close. */
.up__full {
  position: fixed; inset: 0; z-index: 215;
  background: #000;
  display: flex; align-items: center; justify-content: center;
}
.up__full img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
.up__fullx {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 8px); right: 8px;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border: 0; background: none; cursor: pointer;
}
.up__fullx svg { width: 26px; height: 26px; fill: #fff; display: block; }

/* ── count + footer ────────────────────────────────────────────────────── */
.up__count {
  flex: none;
  margin: 0;
  padding: 4px 16px 8px;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
.up__foot {
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 16px calc(16px + env(safe-area-inset-bottom, 0px));
}
.up__add {
  flex: none;
  width: 48px; height: 48px;
  display: grid; place-items: center;
  background: #fff;
  border: 1.5px solid #FEB600;
  border-radius: 50%;
  padding: 0;
  cursor: pointer;
}
.up__add:disabled { border-color: #e0e0e0; }
.up__add:disabled .up__glyph { fill: #bdbdbd; }

.up__submit {
  flex: 1;
  height: 48px;
  border: 0;
  font: inherit;
  font-weight: 700;
  color: rgba(0, 0, 0, .87);
  background: #FEB600;
  cursor: pointer;
  border-radius: 16px;
}
@supports (corner-shape: superellipse(1.6)) {
  .up__submit { border-radius: 22px; corner-shape: superellipse(1.6); }
}
.up__submit:disabled {
  background: #e0e0e0;
  color: #757575;
  cursor: default;
}

.up__file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

@media (prefers-reduced-motion: reduce) { .up { animation: none; } }

/* ── scan-button ripple (scan_ripple.js) ──────────────────────────────────
   Geometry is driven from script, because the wave's width, height and radius
   follow a two-phase curve that keyframes cannot express cleanly. This sheet
   only supplies the fixed parts and the safe-area inset, which script cannot
   read from env() directly. */

:root { --safe-bottom: env(safe-area-inset-bottom, 0px); }

.rip {
  position: fixed;
  inset: 0;
  z-index: 95;
  pointer-events: none;
  overflow: hidden;
}
.rip.is-out { opacity: 0; transition: opacity .2s ease; }

.rip__wave {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  transform: translate(-50%, -50%);
  display: block;
  /* baisedAccountPastelHeatGradient, topLeft -> bottomRight (theme.dart:33) */
  background: linear-gradient(135deg, #FF6B6B 0%, #FFBD71 50%, #EEDC82 100%);
}

.rip__fab {
  position: absolute;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 70px;
  height: 70px;
  border-radius: 50%;
  background: #FEB600;
  display: grid;
  place-items: center;
}
.rip__fab img { display: block; }

/* The real FAB steps aside while its stand-in travels. */
.tabs .fab.is-riding { visibility: hidden; }

@media (prefers-reduced-motion: reduce) { .rip { display: none; } }

/* The arrival cover. Same gradient as the wipe's final frame, painted before
   the destination's first frame so the navigation between the two pages is
   invisible. Sits above the upload page (z-index 88) until lifted. */
html.rip-arrive::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 94;
  background: linear-gradient(135deg, #FF6B6B 0%, #FFBD71 50%, #EEDC82 100%);
  pointer-events: none;
}
html.rip-arrive.rip-lift::before {
  opacity: 0;
  transition: opacity .3s ease;
}
@media (prefers-reduced-motion: reduce) { html.rip-arrive::before { display: none; } }

/* ── username editor (account.html) ──────────────────────────────────────
   Lives inside the same `.ssub` panel the language and legal rows expand
   into, so the row behaves like every other expandable setting. */
.uname {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 10px 16px;
}
.uname__in {
  flex: 1;
  min-width: 0;
  height: 40px;
  padding: 0 12px;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
}
.uname__in:focus {
  outline: none;
  border-color: var(--primary);
}
.uname__in:disabled { opacity: .6; }
.uname__save {
  flex: none;
  height: 40px;
  padding: 0 18px;
  font: inherit;
  font-weight: 700;
  font-size: 14px;
  color: rgba(0, 0, 0, .87);
  background: var(--primary);
  border: 0;
  border-radius: 10px;
  cursor: pointer;
}
.uname__save:disabled { opacity: .6; cursor: default; }
.uname__err {
  margin: 0;
  padding: 0 16px 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--brand-red);
}

/* ── e-receipt guide sheet (ereceipt_instructions.dart _BrandTutorialSheet) ─
   A BOTTOM sheet, unlike the SKU panel above it: the app opens this one with
   showModalBottomSheet (:355), so it belongs at the bottom edge. Numbers are
   the widget's: 85% of the viewport tall, top corners 20, a 40x4 grey handle,
   a 36px logo beside an 18px w800 name, images at radius 14 with 16/8 padding,
   and pill dots whose active state is 18x7 in #FEB600. */

.ergd {
  position: fixed;
  inset: 0;
  z-index: 195;
  background: rgba(0, 0, 0, .5);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  animation: lbIn .16s ease-out;
}
.ergd__body {
  position: relative;
  width: 100%;
  max-width: 520px;
  height: 85vh;
  background: var(--white);
  border-radius: 20px 20px 0 0;
  display: flex;
  flex-direction: column;
  animation: ergdUp .26s cubic-bezier(.22, .61, .36, 1);
}
@keyframes ergdUp { from { transform: translateY(100%) } to { transform: none } }
@media (prefers-reduced-motion: reduce) {
  .ergd, .ergd__body { animation: none }
}

.ergd__grab {
  width: 40px; height: 4px;
  margin: 12px auto;
  border-radius: 2px;
  background: #E0E0E0;          /* grey.shade300 */
  flex: none;
}

.ergd__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 8px 8px 20px;
  flex: none;
}
.ergd__logo { width: 36px; height: 36px; object-fit: contain; flex: none; }
.ergd__name {
  flex: 1; min-width: 0;
  font-size: 18px; font-weight: 800; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ergd__x {
  width: 40px; height: 40px; flex: none;
  display: grid; place-items: center;
  border-radius: 50%;
}
.ergd__x svg { width: 24px; height: 24px; fill: var(--ink); display: block; }

/* One image per page, snapping — the PageView equivalent. */
.ergd__pager {
  flex: 1; min-height: 0;
  display: flex;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.ergd__pager::-webkit-scrollbar { display: none }
.ergd__page {
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: center;
  padding: 8px 16px;
  display: flex; align-items: center; justify-content: center;
}
.ergd__page img {
  max-width: 100%; max-height: 100%;
  object-fit: contain;
  border-radius: 14px;
}
/* A dead image leaves an empty page rather than a broken-icon page. */
.ergd__page.is-dead { display: none }

.ergd__dots {
  flex: none;
  display: flex; align-items: center; justify-content: center;
  gap: 6px;
  padding: 12px 0 calc(12px + env(safe-area-inset-bottom, 0px));
}
.ergd__dots i {
  width: 7px; height: 7px;
  border-radius: 4px;
  background: #E0E0E0;
  transition: width .2s ease, background .2s ease;
}
.ergd__dots i.on { width: 18px; background: #FEB600; }

/* ── winners lock (web only) ──────────────────────────────────────────────
   The app lists the winners here. This surface never does: seeing who won is
   a reason to install, and a public page naming winners would publish their
   display names and avatars to anyone holding the link.

   ONE container, not two. An earlier pass put the account page's download card
   inside a neutral panel, which drew a card within a card — two backgrounds,
   two radii, two paddings, for a single message. This panel now IS the
   download card: the same pastel gradient, the same squircle, the same shadow,
   with the lock copy and the store badges living directly in it. */
.dlock {
  margin-top: 4px;
  padding: 28px 22px 22px;
  text-align: center;
  /* Same shape and skin as .dlcard on the account page. Copy must be DARK —
     white dies on the yellow end of the ramp. */
  border-radius: 22px;
  background: var(--pastel-grad);
  color: #1A1A1A;
  box-shadow: 0 8px 20px rgba(26, 26, 46, .14);
}
@supports (corner-shape: superellipse(2.2)) {
  .dlock { border-radius: 44px; corner-shape: superellipse(2.2); }
}
.dlock__ic {
  width: 56px; height: 56px;
  margin: 0 auto 16px;
  border-radius: 50%;
  background: var(--white);
  display: grid; place-items: center;
}
/* Dark, not themed: the panel is BaiSed's own warm ramp, so a brand-green lock
   sitting on orange read as a clash rather than a signal. */
.dlock__ic svg { width: 28px; height: 28px; fill: #1A1A1A; display: block; }
.dlock__t { font-size: 19px; font-weight: 900; line-height: 1.25; }
.dlock__b {
  margin-top: 8px;
  font-size: 14px; line-height: 1.45;
  color: rgba(0, 0, 0, .66);
}
/* The badges keep their own 16px top margin from .dlcard__badges; centre them
   here because this panel is centred and the account card is not. */
.dlock .dlcard__badges { justify-content: center; }

/* ── dialog (lib/widgets/customDialog.dart _CustomDialogOverlay:147) ───────
   Replaces the browser's native confirm()/alert(). Numbers are the widget's;
   see the showDialog() comment in app.js for the full extraction. */

.dlg {
  position: fixed;
  inset: 0;
  /* Above the sheets (190-210): a dialog is the one thing that must never be
     covered, since it is asking a question the user has to answer. */
  z-index: 220;
  background: rgba(0, 0, 0, .85);      /* black withOpacity(0.85) */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 32px;                      /* the card's horizontal margin */
  animation: dlgFade .2s cubic-bezier(0, 0, .58, 1) both;   /* Curves.easeOut */
}
body.dlg-open { overflow: hidden; }
.dlg.is-out { animation: dlgFadeOut .16s ease-in both; }
@keyframes dlgFade    { from { opacity: 0 } to { opacity: 1 } }
@keyframes dlgFadeOut { from { opacity: 1 } to { opacity: 0 } }

.dlg__card {
  width: 100%;
  max-width: 400px;
  padding: 24px;
  background: var(--white);
  /* BorderRadius.circular(20) — a PLAIN arc. No squircle inflation here; the
     buttons below are the figma_squircle ones. */
  border-radius: 20px;
  box-shadow: 0 10px 20px rgba(0, 0, 0, .3);
  text-align: center;
  /* scale 0.8 -> 1 on Curves.easeOutBack over 200ms */
  animation: dlgPop .2s cubic-bezier(.34, 1.56, .64, 1) both;
}
.dlg.is-out .dlg__card { animation: dlgPopOut .16s ease-in both; }
@keyframes dlgPop    { from { transform: scale(.8) } to { transform: none } }
@keyframes dlgPopOut { from { transform: none } to { transform: scale(.9) } }
@media (prefers-reduced-motion: reduce) {
  .dlg, .dlg__card, .dlg.is-out, .dlg.is-out .dlg__card { animation: none }
}

/* 60px circle of the accent at 10%, 30px glyph in the accent itself. */
.dlg__ic {
  width: 60px; height: 60px;
  margin: 0 auto 16px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--dlg-accent) 10%, transparent);
  display: grid; place-items: center;
}
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  .dlg__ic { background: rgba(0, 0, 0, .05); }
}
.dlg__ic svg { width: 30px; height: 30px; fill: var(--dlg-accent); display: block; }

.dlg__t { font-size: 20px; font-weight: 700; color: rgba(0, 0, 0, .87); line-height: 1.3; }
.dlg__m { margin-top: 12px; font-size: 16px; color: rgba(0, 0, 0, .54); line-height: 1.45; }

.dlg__row { display: flex; gap: 12px; margin-top: 24px; }
.dlg__btn {
  flex: 1 1 0;
  min-width: 0;
  padding: 12px 8px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  /* SmoothRectangleBorder(cornerRadius 12, cornerSmoothing 1.0) — this one IS
     a squircle: r12 x 2.29 = 27, with r14 as the plain fallback. */
  border-radius: 14px;
  -webkit-tap-highlight-color: transparent;
}
@supports (corner-shape: superellipse(2.2)) {
  .dlg__btn { border-radius: 27px; corner-shape: superellipse(2.2); }
}
.dlg__btn--cancel {
  background: var(--white);
  border: 1px solid #E0E0E0;           /* grey.shade300 */
  color: rgba(0, 0, 0, .54);
}
.dlg__btn--ok {
  background: var(--dlg-accent);
  color: var(--white);
  border: 0;
}
.dlg__btn:active { filter: brightness(.96); }

/* ── entry history (account page) ─────────────────────────────────────────
   The app's history row (history.dart _buildHistoryListItem:1913, isReceipt
   branch): 16/12 padding, a 44px #F5F5F5 tile at radius 12, 12px gap, then a
   column of store + amount over a second line carrying the award. Rows are
   separated by the app's 1px #EEEEEE rule.

   The one substitution: the app's second line ends in "+ N" with the coin;
   this surface pays entries, so it is "+ N" with the lucky-draw ticket. */

.hist { margin-top: 22px; }
.hist__h {
  font-size: 13px; font-weight: 800; letter-spacing: .02em;
  color: var(--muted);
  margin-bottom: 10px;
}
.hist__body {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 16px;
  overflow: hidden;
}

/* Date group header. */
.hist__d {
  padding: 10px 16px 6px;
  font-size: 12px; font-weight: 700;
  color: var(--faint);
  background: #FAFAFA;
}

.hist__r {
  display: flex; align-items: center;
  padding: 12px 16px;
  border-top: 1px solid #EEEEEE;
}
.hist__d + .hist__r { border-top: 0; }

.hist__m { flex: 1; min-width: 0; }
.hist__top { display: flex; align-items: baseline; gap: 8px; }
.hist__store {
  flex: 1; min-width: 0;
  font-size: 14px; font-weight: 700; color: rgba(0, 0, 0, .87);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* w900 15 black, the widget's amount. */
.hist__amt { font-size: 15px; font-weight: 900; color: #000; flex: none; }

.hist__bot {
  margin-top: 4px;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.hist__draw {
  flex: 1; min-width: 0;
  font-size: 12px; color: var(--faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hist__n { display: flex; align-items: center; gap: 4px; flex: none; }
.hist__n i { font-style: normal; font-size: 13px; font-weight: 700; color: #FEB600; }
/* Sized by HEIGHT with the width left to follow. This slot now carries the
   draw's own entry ticket, and those are not square — Golden Mountain's is
   1674x770, so the old fixed 18x18 box would have squashed it to less than
   half its width. `width:auto` keeps any brand's ticket at its true aspect,
   and object-fit guards the case where a brand ships a square one. */
.hist__n img { height: 18px; width: auto; max-width: 44px; object-fit: contain; display: block; }
.hist__n b { font-size: 13px; font-weight: 800; color: #FEB600; }

.hist__empty {
  padding: 22px 16px;
  text-align: center;
  font-size: 13px; color: var(--faint);
}

/* A rich body in the dialog's message slot (the Dart's `messageWidget`).

   The store badges are WHITE artwork — measured, not assumed: every opaque
   pixel in both PNGs is near-white (avg luminance 254, zero pixels below 90).
   They are drawn for the app's coral-to-yellow download card and they vanish
   completely on this dialog's white card.

   So give them that background rather than reworking the art: this is a small
   .dlcard, same pastel ramp, same radius language. Reusing the app's own
   download surface is also the honest read of what this block is. */
.dlg__node { margin-top: 4px; }
.dlg__node .dlcard__badges {
  justify-content: center;
  padding: 14px;
  border-radius: 18px;
  background: var(--pastel-grad);
}

/* ── "How to submit each store" sheet (EreceiptHowItWorksSheet) ───────────
   The "?" on the upload page. Two views in one bottom sheet: the store list
   with a "How this page works" button, and the 3-step carousel behind it.
   Numbers are the widget's. */

.hiw {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0, 0, 0, .5);
  display: flex; align-items: flex-end; justify-content: center;
  animation: lbIn .16s ease-out;
}
.hiw__body {
  position: relative;
  width: 100%; max-width: 520px;
  height: 85vh;
  background: #fff;
  display: flex; flex-direction: column;
  /* SmoothBorderRadius.vertical(top: SmoothRadius(20, 0.6)) (:437-440):
     the 0.6 table gives r x1.5 with superellipse(1.6), r x1.07 as fallback. */
  border-radius: 21px 21px 0 0;
  animation: ergdUp .26s cubic-bezier(.22, .61, .36, 1);
}
@supports (corner-shape: superellipse(1.6)) {
  .hiw__body { border-radius: 30px 30px 0 0; corner-shape: superellipse(1.6); }
}
@media (prefers-reduced-motion: reduce) { .hiw, .hiw__body { animation: none } }

.hiw__grab {
  width: 40px; height: 4px; margin: 12px auto;
  border-radius: 2px; background: #E0E0E0; flex: none;
}
.hiw__view { flex: 1; min-height: 0; display: flex; flex-direction: column; }

.hiw__head {
  display: flex; align-items: center; gap: 4px;
  padding: 12px 12px 0 24px;
  flex: none;
}
.hiw__head--tut { padding-left: 8px; padding-top: 8px; }
.hiw__title {
  flex: 1; min-width: 0; margin: 0;
  font-size: 18px; font-weight: 700; color: var(--ink);
}
.hiw__x, .hiw__back {
  width: 40px; height: 40px; flex: none;
  display: grid; place-items: center;
  border: 0; background: none; cursor: pointer;
}
.hiw__glyph { width: 22px; height: 22px; fill: var(--ink); display: block; }

/* ── the store list ────────────────────────────────────────────────────── */
.hiw__list { flex: 1; min-height: 0; overflow-y: auto; padding: 8px 20px 4px; }
.hiw__brand {
  width: 100%;
  display: flex; align-items: center; gap: 12px;
  padding: 12px;
  margin-bottom: 10px;
  background: #fff;
  border: 1px solid #EEEEEE;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .05);
  text-align: left; cursor: pointer;
}
.hiw__logo { width: 40px; height: 40px; flex: none; object-fit: contain; }
.hiw__bname { flex: 1; min-width: 0; font-size: 15px; font-weight: 700; color: var(--ink); }
.hiw__chev { color: rgba(0, 0, 0, .32); font-size: 20px; flex: none; }

/* OutlinedButton, full width, 46 tall, 1.5px #FEB600, squircle r12 s0.6. */
.hiw__how {
  flex: none;
  margin: 4px 20px 16px;
  min-height: 46px;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: none;
  border: 1.5px solid #FEB600;
  border-radius: 13px;
  color: rgba(0, 0, 0, .87);
  font-size: 15px; font-weight: 700;
  cursor: pointer;
}
@supports (corner-shape: superellipse(1.6)) {
  .hiw__how { border-radius: 18px; corner-shape: superellipse(1.6); }
}
.hiw__howic { width: 20px; height: 20px; fill: currentColor; display: block; }

/* ── the carousel ──────────────────────────────────────────────────────── */
.hiw__pager {
  flex: 1; min-height: 0;
  display: flex;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.hiw__pager::-webkit-scrollbar { display: none }
.hiw__step {
  flex: 0 0 100%; min-width: 0;
  scroll-snap-align: start;
  padding: 0 24px;
  /* _TutorialStep (:338): the visual takes ALL the free height and centres in
     it, and the title/body sit at the BOTTOM of the page. Centring the whole
     column instead left the app's tight text-above-the-dots block floating in
     the middle with dead space under it. */
  display: flex; flex-direction: column; text-align: center;
}
.hiw__vis { flex: 1; min-height: 0; display: grid; place-items: center; }
.hiw__st { margin: 12px 0 0; font-size: 18px; font-weight: 800; color: var(--ink); }
.hiw__sb { margin: 8px 0 8px; font-size: 14px; line-height: 1.4; color: rgba(0, 0, 0, .54); }

/* ── tutorial demos ────────────────────────────────────────────────────────
   Drawn to the app's own scale: _kDemoWidth 196, tiles 92x133, primary button
   196x40. No phone-mock wrapper — see the note in ereceipt_guide.js. */
.hiw__demo { width: 196px; display: flex; flex-direction: column; align-items: center; }

/* step 1 — the upload page's empty state (_AndroidSelectDemo :1190) */
.hiw__dtitle { margin: 0; font-size: 15px; font-weight: 600; color: var(--ink); }
.hiw__dcap {
  margin: 8px 0 0; max-width: 220px;
  font-size: 11.5px; line-height: 1.35; color: rgba(0, 0, 0, .54);
}
.hiw__demo--select .hiw__press { margin-top: 22px; }

/* The page's primary button, scaled 48 -> 40 (_DemoPrimaryButton :1080).
   SmoothRectangleBorder(16, 0.6) -> 24px + superellipse(1.6), fallback 17. */
.hiw__press { position: relative; display: grid; place-items: center; }
.hiw__press--grow { flex: 1; min-width: 0; }
.hiw__dbtn {
  width: 196px; height: 40px;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  background: #FEB600; border-radius: 17px;
  font-size: 12px; font-weight: 700; color: rgba(0, 0, 0, .87);
  animation: hiwPress 2.4s linear infinite;   /* _c 2400ms, pressed .55-.72 */
}
.hiw__press--grow .hiw__dbtn { width: 100%; }
.hiw__dbglyph { width: 15px; height: 15px; fill: rgba(0, 0, 0, .87); flex: none; }
@supports (corner-shape: superellipse(1.6)) {
  .hiw__dbtn { border-radius: 24px; corner-shape: superellipse(1.6); }
}
@keyframes hiwPress {
  0%, 54%, 73%, 100% { transform: scale(1) }
  56%, 71%           { transform: scale(.95) }
}
/* _TapDot (:1052) — 26px, black 16% with a 26% ring, on while pressed. */
.hiw__tap {
  position: absolute; width: 26px; height: 26px; border-radius: 50%;
  background: rgba(0, 0, 0, .16); border: 1px solid rgba(0, 0, 0, .26);
  opacity: 0; animation: hiwTap 2.4s linear infinite;
}
@keyframes hiwTap {
  0%, 54%, 73%, 100% { opacity: 0; transform: scale(.6) }
  56%, 71%           { opacity: 1; transform: scale(1) }
}

/* step 2 — the SYSTEM photo picker (_AndroidPickDemo :1294) */
.hiw__phead { width: 196px; display: flex; align-items: center; gap: 5px; }
.hiw__pheadl { display: flex; align-items: center; gap: 5px; flex: 1; min-width: 0; }
.hiw__pheadic { width: 13px; height: 13px; fill: #757575; flex: none; }
.hiw__pheadl b { font-size: 11px; font-weight: 700; color: #616161; }
.hiw__pcount {
  font-size: 10px; font-weight: 600; color: #757575;
  display: grid; justify-items: end;
}
/* The app's header counts up as picks land at v .3 / .5 / .7 of the 3s loop.
   Each label gets an ABSOLUTE window on that one timeline — an animation-delay
   would leave the later labels un-started, and therefore invisible, for the
   whole first pass. */
.hiw__pcount i { grid-area: 1 / 1; font-style: normal; opacity: 0; white-space: nowrap; }
.hiw__pcount .c0 { animation: hiwCount0 3s linear infinite }
.hiw__pcount .c1 { animation: hiwCount1 3s linear infinite }
.hiw__pcount .c2 { animation: hiwCount2 3s linear infinite }
.hiw__pcount .c3 { animation: hiwCount3 3s linear infinite }
@keyframes hiwCount0 { 0%, 29.9% { opacity: 1 } 30%, 100% { opacity: 0 } }
@keyframes hiwCount1 { 0%, 29.9% { opacity: 0 } 30%, 49.9% { opacity: 1 } 50%, 100% { opacity: 0 } }
@keyframes hiwCount2 { 0%, 49.9% { opacity: 0 } 50%, 69.9% { opacity: 1 } 70%, 100% { opacity: 0 } }
@keyframes hiwCount3 { 0%, 69.9% { opacity: 0 } 70%, 100% { opacity: 1 } }
.hiw__pgrid {
  margin-top: 10px; width: 196px;
  display: grid; grid-template-columns: repeat(3, 60px); gap: 8px;
}
.hiw__pcell {
  position: relative; width: 60px; height: 60px;
  border-radius: 8px; background: #EEEEEE;
  border: 2.5px solid transparent;
  display: grid; place-items: center;
}
.hiw__pglyph { width: 20px; height: 20px; fill: #BDBDBD; }
.hiw__pcheck {
  position: absolute; top: 3px; right: 3px;
  width: 14px; height: 14px; fill: #FEB600; opacity: 0;
}
/* _c 3000ms; picks land at v .3 / .5 / .7 and hold to the loop's end. */
/* Same absolute-window rule as the counter: cells 0, 3 and 4 light at v .3,
   .5 and .7, each holding to the end of the loop. */
.hiw__pcell.p0 { animation: hiwCellOn0 3s linear infinite }
.hiw__pcell.p1 { animation: hiwCellOn1 3s linear infinite }
.hiw__pcell.p2 { animation: hiwCellOn2 3s linear infinite }
.hiw__pcell.p0 .hiw__pcheck { animation: hiwCount0r 3s linear infinite }
.hiw__pcell.p1 .hiw__pcheck { animation: hiwCount1r 3s linear infinite }
.hiw__pcell.p2 .hiw__pcheck { animation: hiwCount2r 3s linear infinite }
.hiw__pcell.p0 .hiw__pglyph { animation: hiwGlyph0 3s linear infinite }
.hiw__pcell.p1 .hiw__pglyph { animation: hiwGlyph1 3s linear infinite }
.hiw__pcell.p2 .hiw__pglyph { animation: hiwGlyph2 3s linear infinite }
@keyframes hiwCellOn0 { 0%, 29.9% { border-color: transparent } 30%, 100% { border-color: #FEB600 } }
@keyframes hiwCellOn1 { 0%, 49.9% { border-color: transparent } 50%, 100% { border-color: #FEB600 } }
@keyframes hiwCellOn2 { 0%, 69.9% { border-color: transparent } 70%, 100% { border-color: #FEB600 } }
@keyframes hiwCount0r { 0%, 29.9% { opacity: 0 } 30%, 100% { opacity: 1 } }
@keyframes hiwCount1r { 0%, 49.9% { opacity: 0 } 50%, 100% { opacity: 1 } }
@keyframes hiwCount2r { 0%, 69.9% { opacity: 0 } 70%, 100% { opacity: 1 } }
@keyframes hiwGlyph0  { 0%, 29.9% { opacity: 1 } 30%, 100% { opacity: 0 } }
@keyframes hiwGlyph1  { 0%, 49.9% { opacity: 1 } 50%, 100% { opacity: 0 } }
@keyframes hiwGlyph2  { 0%, 69.9% { opacity: 1 } 70%, 100% { opacity: 0 } }

/* step 3 — the picked tiles and the page footer (_AndroidSubmitDemo :1437) */
.hiw__trow { display: flex; gap: 12px; }
.hiw__tile {
  position: relative; width: 92px; height: 133px;
  background: #F5F5F5; border-radius: 13px;
  display: grid; place-items: center;
  box-shadow: inset 0 0 0 3px #FEB600;
}
@supports (corner-shape: superellipse(1.6)) {
  .hiw__tile { border-radius: 18px; corner-shape: superellipse(1.6); }
}
.hiw__tglyph { width: 30px; height: 30px; fill: #BDBDBD; }
.hiw__tcheck { position: absolute; top: 5px; right: 5px; width: 16px; height: 16px; fill: #FEB600; }
.hiw__dfoot { margin-top: 14px; width: 196px; display: flex; align-items: center; gap: 12px; }
/* The 48pt add-more circle at the demo's 40/48 scale. */
.hiw__dadd {
  width: 40px; height: 40px; flex: none; border-radius: 50%;
  background: #fff; border: 1.5px solid #FEB600;
  display: grid; place-items: center;
}
.hiw__daglyph { width: 18px; height: 18px; fill: rgba(0, 0, 0, .87); }
/* _c 3400ms: label until .60, spinner until .86, then the tick. */
.hiw__dbtn--send { animation-duration: 3.4s }
.hiw__demo--submit .hiw__tap { animation-duration: 3.4s }
.hiw__dsend, .hiw__dspin, .hiw__dtick { animation: none; }
.hiw__dsend { animation: hiwSendLabel 3.4s linear infinite }
.hiw__dspin {
  position: absolute; width: 16px; height: 16px; opacity: 0;
  border: 2.2px solid rgba(0, 0, 0, .2); border-top-color: rgba(0, 0, 0, .87);
  border-radius: 50%;
  animation: spin .8s linear infinite, hiwSendSpin 3.4s linear infinite;
}
.hiw__dtick {
  position: absolute; width: 18px; height: 18px; fill: rgba(0, 0, 0, .87);
  opacity: 0; animation: hiwSendTick 3.4s linear infinite;
}
@keyframes hiwSendLabel { 0%, 59% { opacity: 1 } 60%, 100% { opacity: 0 } }
@keyframes hiwSendSpin  { 0%, 59% { opacity: 0 } 60%, 85% { opacity: 1 } 86%, 100% { opacity: 0 } }
@keyframes hiwSendTick  { 0%, 85% { opacity: 0 } 86%, 100% { opacity: 1 } }
/* The send button's press window is .42-.56 of a 3400ms loop, not .55-.72. */
.hiw__demo--submit .hiw__dbtn, .hiw__demo--submit .hiw__tap {
  animation-name: hiwPressSend;
}
.hiw__demo--submit .hiw__tap { animation-name: hiwTapSend }
@keyframes hiwPressSend {
  0%, 41%, 57%, 100% { transform: scale(1) }
  43%, 55%           { transform: scale(.95) }
}
@keyframes hiwTapSend {
  0%, 41%, 57%, 100% { opacity: 0; transform: scale(.6) }
  43%, 55%           { opacity: 1; transform: scale(1) }
}

.hiw__dots {
  flex: none;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 12px 0 0;
}
.hiw__dots i {
  width: 7px; height: 7px; border-radius: 4px; background: #E0E0E0;
  transition: width .22s ease, background .22s ease;
}
.hiw__dots i.on { width: 18px; background: #FEB600 }

/* ElevatedButton, #FEB600 on white, 48 tall, squircle r12 s0.6 (:290-312). */
.hiw__next {
  flex: none;
  margin: 12px 24px calc(16px + env(safe-area-inset-bottom, 0px));
  min-height: 48px;
  background: #FEB600; color: #fff;
  border: 0; border-radius: 13px;
  font-size: 15px; font-weight: 700;
  cursor: pointer;
}
@supports (corner-shape: superellipse(1.6)) {
  .hiw__next { border-radius: 18px; corner-shape: superellipse(1.6); }
}

/* ---- "Your entries" → entry history (_showEntriesHistory :6325) ----------
   The app opens a Dialog with insetPadding horizontal 36 and a
   SmoothRectangleBorder(cornerRadius: 24, cornerSmoothing: 1.0) — so the
   figma_squircle pair, 24 → 55px + superellipse(2.2), fallback 27px. */
.ehist{
  position:fixed;inset:0;z-index:200;
  background:rgba(0,0,0,.54);              /* Flutter's default barrier */
  display:grid;place-items:center;
  padding:24px 36px;                       /* insetPadding.horizontal = 36 */
}
.ehist__card{
  width:100%;max-width:420px;max-height:min(86vh,720px);
  display:flex;flex-direction:column;
  background:#fff;
  padding:18px 20px 16px;                  /* fromLTRB(20,18,20,16) */
  border-radius:27px;
}
@supports (corner-shape: superellipse(2.2)){
  .ehist__card{border-radius:55px;corner-shape:superellipse(2.2)}
}
.ehist__head{display:flex;align-items:center;gap:8px}
.ehist__title{flex:1;min-width:0;font-size:16px;font-weight:900;color:#1A1A1A}
.ehist__x{
  flex:none;width:22px;height:22px;padding:0;border:0;background:none;
  color:rgba(0,0,0,.45);cursor:pointer;
}
.ehist__x svg{width:22px;height:22px;fill:currentColor;display:block}

/* Total pill. radius 14 is a PLAIN BorderRadius.circular in the Dart, so the
   squircle inflation must NOT be applied to it. */
.ehist__total{
  display:flex;align-items:center;
  margin:12px 0 14px;padding:12px 14px;border-radius:14px;
  background:color-mix(in srgb,var(--eh) 8%,transparent);
}
.ehist__totall{flex:1;min-width:0;font-size:13px;font-weight:800;color:var(--eh)}
.ehist__totalv{flex:none;display:flex;align-items:baseline}
.ehist__totalv b{font-size:22px;font-weight:900;line-height:1;color:var(--eh)}
.ehist__totalv .u{
  margin-left:4px;font-size:11px;font-weight:700;
  color:color-mix(in srgb,var(--eh) 70%,transparent);
}

.ehist__body{overflow-y:auto;max-height:360px;-webkit-overflow-scrolling:touch}
.ehist__empty{
  margin:0;padding:24px 0;font-size:13px;font-weight:500;color:rgba(0,0,0,.54);
}
.ehist__row{
  display:flex;align-items:center;width:100%;
  padding:8px 0;border:0;background:none;text-align:left;
  font:inherit;color:inherit;cursor:pointer;
}
.ehist__info{flex:1;min-width:0}
.ehist__store{
  font-size:14px;font-weight:700;color:#1A1A1A;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.ehist__sub{margin-top:2px;font-size:11.5px;font-weight:500;color:rgba(0,0,0,.45)}
.ehist__n{flex:none;margin-left:10px;font-size:16px;font-weight:900;color:var(--eh)}
.ehist__tk{flex:none;margin-left:6px;width:30px;height:24px;object-fit:contain}
.ehist__chev{flex:none;margin-left:4px;font-size:18px;line-height:1;color:rgba(0,0,0,.26)}

.ehist__spin,.ehimg__spin{
  width:22px;height:22px;margin:28px auto;
  border:2.4px solid rgba(0,0,0,.12);border-top-color:rgba(0,0,0,.4);
  border-radius:50%;animation:spin .8s linear infinite;
}

/* The receipt photo behind a row (_showReceiptImageDialog). Boxed panel, not
   full-bleed: barrier black at 95%, insetPadding (24,56). */
.ehimg{
  position:fixed;inset:0;z-index:212;
  background:rgba(0,0,0,.95);
  display:grid;place-items:center;padding:56px 24px;
}
.ehimg__panel{
  max-width:100%;max-height:100%;overflow:auto;
  background:#fff;border-radius:16px;
  display:grid;place-items:center;min-width:120px;min-height:120px;
}
.ehimg__panel img{display:block;max-width:100%;height:auto}
.ehimg__none{margin:0;padding:32px 20px;font-size:13px;color:rgba(0,0,0,.54)}
.ehimg__x{
  position:absolute;top:calc(12px + env(safe-area-inset-top));right:12px;
  width:40px;height:40px;padding:8px;border:0;background:none;color:#fff;cursor:pointer;
}
.ehimg__x svg{width:100%;height:100%;fill:currentColor;display:block}

/* ---- zero-entry extras on the success screen (_missingEntriesButton :2456) --
   A TextButton, not a filled one: 14px w700, underlined, black54, turning
   green and losing the underline once the report is in. */
.rr__miss {
  display: block; width: 100%; margin: 4px 0 0;
  padding: 12px 20px; border: 0; background: none;
  font: inherit; font-size: 14px; font-weight: 700;
  color: rgba(0, 0, 0, .54);
  text-decoration: underline; text-decoration-color: rgba(0, 0, 0, .38);
  cursor: pointer;
}
.rr__miss:disabled { cursor: default; color: rgba(0, 0, 0, .38); }
.rr__miss.is-sent { color: #2E7D32; text-decoration: none; }
/* The app pitch, restyled for THIS screen. The account page's card is a pastel
   gradient, which here would sit on the success screen's own gradient and
   directly under a white ticket — a gradient on a gradient, competing with the
   thing the user actually came to read. White instead, so it reads as a sibling
   of the ticket rather than a second panel shouting over it. */
.rr .dlcard {
  /* NO side gutter. The ticket, the button row and the report line all span
     the stack's full width; a 20px inset here made the card the one element
     out of column, which is what read as 'not fitting'. */
  margin: 10px 0 0; padding: 15px 16px;
  background: #fff;
  box-shadow: 0 6px 18px rgba(26, 26, 46, .10);
}
/* A divider meant for the account page's settings list. There is no list here,
   and it is positioned off --pad-page, so it would draw a stray rule across
   the screen. */
.rr .dlcard::before { display: none; }
.rr .dlcard__m b { font-size: 15px; }
.rr .dlcard__badges { margin-top: 12px; }
/* The badge artwork is monochrome WHITE — measured, not assumed: 0% coloured
   pixels and a max channel spread of 0 across both files. It therefore vanishes
   on a white card. Inverting yields the black badges Apple and Google also
   publish, with the transparency intact, so this needs no second set of assets
   that could drift out of sync with the account page's. */
.rr .dlcard__badge img { filter: invert(1); }

/* ---- the claim popup (_showClaimDialog, receiptResult.dart:2166) ----------
   Barrier black 85%, white card inset 32 with radius 20 and a soft drop
   shadow, capped at 85vh and scrolling rather than overflowing. */
.clm {
  position: fixed; inset: 0; z-index: 205;
  background: rgba(0, 0, 0, .85);
  display: grid; place-items: center; padding: 0 32px;
  animation: clmIn .2s ease-out;
}
@keyframes clmIn { from { opacity: 0 } to { opacity: 1 } }
.clm__card {
  width: 100%; max-width: 420px; max-height: 85vh;
  background: #fff; border-radius: 20px;
  box-shadow: 0 10px 20px rgba(0, 0, 0, .3);
  overflow: hidden; display: flex;
  /* scale 0.8 -> 1.0 on easeOutBack, the app's transitionBuilder */
  animation: clmPop .2s cubic-bezier(.34, 1.56, .64, 1);
}
@keyframes clmPop { from { transform: scale(.8) } to { transform: scale(1) } }
.clm__scroll { flex: 1; min-width: 0; overflow-y: auto; padding: 24px; }
.clm__t { margin: 0; font-size: 20px; font-weight: 700; color: rgba(0,0,0,.87); text-align: center; }
.clm__s { margin: 12px 0 0; font-size: 16px; color: rgba(0,0,0,.54); text-align: center; }
/* A <button> wrapping the receipt so it can open the full-screen viewer —
   the app's own image is static here, this is the added affordance. Reset to
   a plain block so the button chrome disappears and only the photo shows. */
.clm__imgbtn {
  display: block; margin: 20px auto 0; padding: 0; border: 0; background: none;
  width: 150px; height: 190px; border-radius: 12px; overflow: hidden;
  cursor: pointer; position: relative;
}
/* A small expand glyph in the corner signals it's tappable — without it a
   150x190 photo reads as decoration, not a control. A pre-coloured white SVG
   on a translucent dark circle, not a CSS mask, so an older LINE webview
   without mask support still shows the badge instead of nothing. */
.clm__imgbtn::after {
  content: ''; position: absolute; right: 6px; bottom: 6px;
  width: 22px; height: 22px; border-radius: 50%;
  background-color: rgba(0, 0, 0, .5);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: center; background-size: 14px;
}
.clm__img {
  display: block; width: 100%; height: 100%; object-fit: cover;
}
.clm__noimg { margin: 20px 0 0; text-align: center; font-size: 14px; color: rgba(0,0,0,.45); }
.clm__lbl { margin: 18px 0 8px; font-size: 14px; font-weight: 700; color: rgba(0,0,0,.87); }

/* The expand-in-place picker, not a native select — the app's own pattern. */
.clm__field {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 14px 16px; border: 1px solid #E0E0E0; border-radius: 12px;
  background: #fff; font: inherit; cursor: pointer; text-align: left;
}
.clm__val { flex: 1; min-width: 0; font-size: 14px; color: #424242;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.clm__val.is-hint { color: #9E9E9E; }
.clm__cglyph { width: 20px; height: 20px; fill: #757575; display: block; }
.clm__field.is-open .clm__chev { transform: rotate(180deg); }
.clm__chev { display: block; transition: transform .15s ease; }
.clm__list {
  margin-top: 8px; max-height: 240px; overflow-y: auto;
  border: 1px solid #E0E0E0; border-radius: 12px; background: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .06);
}
.clm__opt {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 12px 16px; border: 0; background: none; font: inherit;
  cursor: pointer; text-align: left;
}
.clm__opt.has-div { border-bottom: 1px solid #EEEEEE; }
.clm__pill {
  flex: none; width: 28px; height: 28px; border-radius: 14px;
  background: #E0E0E0; display: grid; place-items: center;
}
.clm__opt.is-on .clm__pill { background: #FEB600; }
.clm__pglyph { width: 16px; height: 16px; fill: #fff; opacity: 0; display: block; }
.clm__opt.is-on .clm__pglyph { opacity: 1; }
.clm__olbl { flex: 1; min-width: 0; font-size: 14px; font-weight: 500; color: rgba(0,0,0,.87); }

/* Cancel / Submit, equal widths. Both are r12 at cornerSmoothing 1.0. */
.clm__row { display: flex; gap: 12px; margin-top: 18px; }
.clm__btn {
  flex: 1; min-width: 0; padding: 12px 8px; border: 0;
  font: inherit; font-weight: 600; font-size: 14px;
  border-radius: 14px; cursor: pointer;
}
@supports (corner-shape: superellipse(2.2)) {
  .clm__btn { border-radius: 27px; corner-shape: superellipse(2.2); }
}
.clm__cancel { background: #fff; border: 1px solid #E0E0E0; color: rgba(0,0,0,.54); }
.clm__submit { background: #FEB600; color: #fff; }
/* Disabled until a reason is picked — the app's one deviation from its own
   house dialog, whose confirm is always tappable. */
.clm__submit:disabled { background: #EEEEEE; color: rgba(0,0,0,.26); cursor: default; }

/* ---- receipt picker for a missing-entry claim (_ReceiptSelectionModal) -----
   A full-width bottom sheet (isScrollControlled), header / scrolling body /
   pinned submit. The reason picker reuses the .clm__ dropdown: the app uses
   one pattern in both places and a second copy here would drift. */
.rsel {
  position: fixed; inset: 0; z-index: 206;
  background: rgba(0, 0, 0, .5);
  display: flex; align-items: flex-end; justify-content: center;
  animation: lbIn .16s ease-out;
}
.rsel__card {
  width: 100%; max-width: 520px; max-height: 92vh;
  display: flex; flex-direction: column;
  background: #fff; border-radius: 20px 20px 0 0; overflow: hidden;
}
.rsel__head { padding: 20px; }
.rsel__top { display: flex; align-items: flex-start; gap: 8px; }
.rsel__t { flex: 1; min-width: 0; margin: 0; font-size: 20px; font-weight: 700; color: var(--ink); }
.rsel__x { flex: none; width: 24px; height: 24px; padding: 0; border: 0; background: none; cursor: pointer; }
.rsel__xg { width: 24px; height: 24px; fill: rgba(0,0,0,.55); display: block; }
.rsel__s { margin: 8px 0 0; font-size: 14px; color: #757575; }
.rsel__when {
  display: flex; align-items: center; gap: 4px; margin: 8px 0 0;
  font-size: 12px; font-style: italic; color: #757575;
}
.rsel__wg { width: 14px; height: 14px; fill: #757575; flex: none; }
.rsel__body {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 16px 20px 20px; border-top: 1px solid #E8EAED;
}
.rsel__cwrap {
  border: 1px solid #E0E0E0; border-radius: 10px; padding: 10px 12px; background: #fff;
}
.rsel__comment {
  width: 100%; border: 0; outline: none; resize: none;
  font: inherit; font-size: 14px; color: var(--ink); background: none;
}
.rsel__list { margin-top: 16px; display: flex; flex-direction: column; gap: 12px; }
.rsel__none { margin: 24px 0; text-align: center; font-size: 14px; color: rgba(0,0,0,.5); }
.rsel__spin {
  width: 22px; height: 22px; margin: 24px auto;
  border: 2.4px solid rgba(0,0,0,.12); border-top-color: rgba(0,0,0,.4);
  border-radius: 50%; animation: spin .8s linear infinite;
}
/* Selected: the accent at 20% with a 2px accent border and a soft glow. */
.rsel__tile {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 16px; border: 1px solid #E0E0E0; border-radius: 12px;
  background: #fff; font: inherit; text-align: left; cursor: pointer;
}
.rsel__tile.is-on {
  border: 2px solid var(--rs); padding: 15px;
  background: color-mix(in srgb, var(--rs) 20%, #fff);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--rs) 30%, transparent);
}
.rsel__radio {
  flex: none; width: 24px; height: 24px; border-radius: 50%;
  border: 2px solid #BDBDBD; display: grid; place-items: center;
}
.rsel__tile.is-on .rsel__radio { border-color: var(--rs); background: var(--rs); }
.rsel__rg { width: 16px; height: 16px; fill: #fff; opacity: 0; display: block; }
.rsel__tile.is-on .rsel__rg { opacity: 1; }
.rsel__thumb {
  flex: none; width: 44px; height: 56px; border-radius: 8px; overflow: hidden;
  background: #F5F5F5; display: grid; place-items: center;
}
.rsel__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.rsel__tg { width: 20px; height: 20px; fill: #BDBDBD; }
.rsel__meta { flex: 1; min-width: 0; }
.rsel__meta b {
  display: block; font-size: 16px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Order-id draws (_buildPastedOrderField, LuckyDrawReceiptTile). The field is
   the app's outlined TextField: radius 12, grey 300 hairline; a valid id or
   focus takes the accent at 2px; red 400 on error; grey 100 fill when locked. */
.rsel__paste { margin-top: 12px; }
.rsel__paste .clm__lbl { margin-top: 0; }
.rsel__opt { font-weight: 500; color: #9E9E9E; }
.rsel__pfield {
  display: flex; align-items: center; gap: 4px;
  border: 1px solid #E0E0E0; border-radius: 12px; background: #fff;
  padding: 0 6px 0 14px; transition: border-color .12s ease, box-shadow .12s ease;
}
.rsel__pfield:focus-within, .rsel__pfield.is-valid { border-color: var(--rs); box-shadow: 0 0 0 1px var(--rs); }
.rsel__pfield.is-err { border-color: #EF5350; box-shadow: 0 0 0 .5px #EF5350; }
.rsel__pfield.is-locked { background: #F5F5F5; border-color: #EEEEEE; box-shadow: none; }
.rsel__pin {
  flex: 1; min-width: 0; height: 48px; border: 0; outline: none; background: none;
  font: inherit; font-size: 15px; color: var(--ink); letter-spacing: .02em;
}
.rsel__pin::placeholder { color: #BDBDBD; letter-spacing: 0; }
.rsel__pin:disabled { color: #9E9E9E; }
.rsel__pbtn {
  flex: none; width: 36px; height: 36px; border: 0; border-radius: 50%;
  background: none; display: grid; place-items: center; cursor: pointer;
}
.rsel__pbtn:hover:not(:disabled) { background: rgba(0,0,0,.05); }
.rsel__pbtn:disabled { cursor: default; opacity: .4; }
.rsel__pbg { width: 18px; height: 18px; fill: #616161; display: block; }
.rsel__pmsg { margin: 6px 4px 0; font-size: 12px; line-height: 1.45; color: #757575; }
.rsel__pmsg.is-err { color: #E53935; }
.rsel__olbl { margin-top: 20px; }
.rsel__ohint { margin: -4px 0 0; font-size: 12px; color: #757575; }
.rsel__list--orders { margin-top: 8px; }
.rsel__list.is-locked .rsel__tile { opacity: .4; pointer-events: none; }
.rsel__tile { transition: opacity .15s ease; }
.rsel__none--orders { margin: 12px 0; text-align: left; }
.rsel__thumb--mk { width: 56px; height: 56px; border-radius: 13px; background: #fff; }
.rsel__thumb--mk img { object-fit: cover; }
.rsel__oid {
  display: block; margin-top: 2px; padding: 2px 0;
  font-size: 13px; font-weight: 500; color: #616161;
  font-variant-numeric: tabular-nums; cursor: copy;
}
.rsel__oidv { display: inline-flex; align-items: baseline; gap: 6px; white-space: nowrap; }
.rsel__cg { width: 15px; height: 15px; fill: #757575; align-self: center; flex: none; }
.rsel__sub { display: flex; align-items: center; gap: 12px; margin-top: 4px; font-size: 14px; color: #616161; }
.rsel__date { display: flex; align-items: center; gap: 4px; }
.rsel__dg { width: 14px; height: 14px; fill: #757575; flex: none; }
.rsel__foot {
  padding: 20px; background: #fff;
  box-shadow: 0 -3px 10px rgba(0, 0, 0, .05);
  padding-bottom: calc(20px + env(safe-area-inset-bottom));
}
.rsel__note { margin: 0 0 12px; font-size: 12px; color: #616161; }
.rsel__send {
  width: 100%; padding: 14px 12px; border: 0; border-radius: 14px;
  background: var(--rs); color: #fff; font: inherit; font-weight: 700; font-size: 15px;
  cursor: pointer;
}
@supports (corner-shape: superellipse(2.2)) {
  .rsel__send { border-radius: 27px; corner-shape: superellipse(2.2); }
}
.rsel__send:disabled { background: #EEEEEE; color: rgba(0,0,0,.26); cursor: default; }
/* Laptop (same query as WIDE in draw_page.js): a full page, not a bottom
   sheet (owner, 2026-10-06: "I don't like how it's like a pop up or a slide
   container"). The page background fills the window; a white header carries
   the title; the reason and note sit on the left (sticky while the list
   scrolls), the paste box and the orders on the right; the submit bar is
   pinned to the bottom. Same fields, same order of logic. Phones unchanged. */
@media (min-width:900px) and (hover:hover) and (pointer:fine){
  .rsel { display: block; overflow-y: auto; background: #F7F7F9; }
  .rsel__card {
    max-width: none; max-height: none; min-height: 100%;
    border-radius: 0; overflow: visible; background: transparent;
  }
  .rsel__head {
    padding: 32px max(24px, calc((100% - 1040px) / 2)) 28px;
    background: #fff; border-bottom: 1px solid #E8EAED;
  }
  .rsel__t { font-size: 30px; line-height: 1.25; }
  .rsel__x, .rsel__xg { width: 32px; height: 32px; }
  .rsel__s { margin-top: 10px; font-size: 16px; max-width: 720px; }
  .rsel__when { font-size: 13px; }
  .rsel__body {
    flex: 1 0 auto; overflow: visible; border-top: 0;
    width: min(1040px, calc(100% - 48px)); margin: 0 auto; padding: 36px 0 48px;
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
    column-gap: 48px; align-items: start;
  }
  .rsel__form { position: sticky; top: 24px; }
  .rsel__body .clm__lbl:first-child, .rsel__pick > :first-child { margin-top: 0; }
  .rsel__comment { min-height: 140px; }
  .rsel__foot {
    position: sticky; bottom: 0; z-index: 1;
    display: flex; align-items: center; justify-content: space-between; gap: 24px;
    padding: 16px max(24px, calc((100% - 1040px) / 2));
    box-shadow: 0 -1px 0 #E8EAED, 0 -6px 16px rgba(0, 0, 0, .04);
  }
  .rsel__note { margin: 0; font-size: 14px; }
  .rsel__send { width: 340px; flex: none; }
}

/* ── order-id entry (order_id_squircle_cta.dart, _ScanNowButton, _TesterOrderIdSheet) ── */
/* The order-id-only lucky draw (Cintage): the home-card pill, the draw-page
   pill and the paste-an-order-id dialog. Rendered by order_id.js; every
   number below is read from the Dart symbol named on the block.
   Re-ported 2026-09-30 against the app as of 2026-09-19: the home control
   became a pill, the sheet became a centred dialog, and the draw-page pill's
   base runs full width. */

/* `display:flex` beats the UA's [hidden] rule; scope the trap fix to the dialog. */
.oids [hidden]{display:none!important}

/* ---- home-card pill: OrderIdSquircleButton(pill: true, size 40, width 112,
        depth 5, style outlined), built by _HomeCardOrderIdButton
        (baisedCampaign.dart) ------------------------------------------------ */
/* SizedBox(width: 112, height: size + depth = 45). Flexible + FittedBox(
   scaleDown) in the app: the row hands it a bounded width and it shrinks.
   DEVIATION: here the face narrows (and fitLabel scales the words) instead of
   the whole control scaling uniformly; same result on every phone that fits
   112, which is all of them at 360pt and up. */
.oid{
  position:relative;flex:0 1 112px;min-width:0;height:45px;
  padding:0;border:0;background:none;font:inherit;display:block;
  color:var(--oid);cursor:pointer;
  /* No opacity or transform transition: the control hides and returns
     INSTANTLY both ways, at full size (order_id_squircle_cta.dart:435-440). */
}
/* StadiumBorder on base and face: fully round ends. */
.oid__base,.oid__face{position:absolute;left:0;right:0;height:40px;border-radius:999px}
/* The 3D base: `baseGradient` = the host card's ring (LinearGradient
   centerLeft -> centerRight), read from the --ring-a/--ring-b that card()
   sets on .ld3d, so the pill's bottom edge IS the border it sits on. With no
   ring, the widget's own `_shade(themeColor, 0.19)` tint (--oid-base). */
.oid__base{
  top:5px;
  background:linear-gradient(90deg,
    var(--ring-a,var(--oid-base,#E6E6E6)),
    var(--ring-b,var(--ring-a,var(--oid-base,#E6E6E6))));
}
/* Face: Colors.white, BorderSide(themeColor, width 2), BoxShadow black 12%
   blur 10 offset (0,3) while not pressed. Padding(horizontal: size*0.30 = 12)
   is carried by the face so the label's own box is what fitLabel measures.
   AnimatedPositioned 90ms, Curves.linear. */
.oid__face{
  top:0;box-sizing:border-box;background:#FFFFFF;border:2px solid var(--oid);
  box-shadow:0 3px 10px rgba(0,0,0,.12);
  display:flex;align-items:center;justify-content:center;
  padding:0 12px;
  transition:transform .063s linear,box-shadow .063s linear; /* `_pushFlat` 63ms (order_id_squircle_cta.dart) */
}
/* `_fontSize` = size*0.35 = 14 on a pill, height 1.08, w900, one line, colour
   = themeColor (`_textColor` outlined, NOT shaded). FittedBox(scaleDown) ->
   fitLabel's transform. */
.oid__lbl{
  display:block;max-width:100%;min-width:0;
  font-size:14px;font-weight:900;line-height:1.08;text-align:center;white-space:nowrap;
  /* Scale from the LEFT edge: when the words overflow, the capped box is the
     face's full content width and the overflow runs off its right side, so
     only a left origin lands the shrunk words exactly inside it (a centre
     origin left them ~9px right of centre). When they fit there is no
     transform and the flex parent centres the box. */
  color:var(--oid);transform-origin:0 50%;
}
/* Press: the face travels `depth` (5) onto the base and loses its shadow
   (`top: _pressed ? depth : 0`, order_id_squircle_cta.dart:367; shadow only
   `if (!off && !_pressed)`, :395). `.is-pressed` (order_id.js pushToFlat)
   holds it from pointerdown through the 90+40ms push (`_fire`, :185-206)
   and while the dialog is open; the rise back is `.is-rising` (A.css). */
.oid.is-pressed .oid__face{transform:translateY(5px);box-shadow:none}
/* Home-card placement: Row[EntryCountCard, Spacer, Flexible(pill),
   SizedBox(8), heart]. The heart holds the row's OUTER edge and the pill sits
   inboard of it; the row's gap:8px is the SizedBox(8). With no heart (the
   mini shows it only once the draw has likes), the pill takes the edge. */
.ldcard__row2>.oid{margin-left:auto}
.ldcard__row2>.oid+.ldcard__likes{margin-left:0}
/* Pressing the pill must not also dip the card (the button's GestureDetector
   wins the arena in the app). :has() is a progressive enhancement — older
   webviews show the card dip too (plan §6.13). */
.ld3d:has(.oid:active)>.ldcard{transform:none;box-shadow:var(--shadow)}

/* ---- draw-page pill: _ScanNowButton (brand_lucky_draw_page.dart) as the
        `_orderIdEntryOnly` CTA branch skins it: faceColor white, borderColor
        theme, labelColor _shade(theme,-0.16), shadowGradient [theme,
        _shade(theme,+0.07)] --------------------------------------------- */
/* The bar's Expanded CTA slot; 48 face + 5 base offset = 53. */
.oidp{
  position:relative;flex:1;min-width:0;height:53px;
  padding:0;border:0;background:none;font:inherit;display:block;cursor:pointer;
  color:var(--oidp-lbl,var(--oid));
  /* _HidesWhileOrderIdOpen: Opacity, INSTANT both ways, no fade
     (brand_lucky_draw_page.dart:19335). */
}
/* ScaleTransition 1.0 -> 1.045, 900ms, easeInOut, repeat(reverse: true),
   enabled only. CSS `alternate` is the reverse repeat (plan §6.5). */
.oidp:not([disabled]){animation:oidpPulse .9s ease-in-out infinite alternate}
@keyframes oidpPulse{from{transform:scale(1)}to{transform:scale(1.045)}}
/* Base: Container(height 48, margin top 5), gradient = shadowGradient
   (order_id.js writes --oidp-sh as the 90deg linear-gradient). The BORDERED
   branch runs it FULL WIDTH (left/right 0, not the scan CTAs' 4) in a
   StadiumBorder, so it meets the ring at both ends and reads as one extruded
   edge instead of a drop shadow. */
.oidp__sh{
  position:absolute;top:5px;left:0;right:0;height:48px;
  border-radius:999px;background:var(--oidp-sh,var(--oid));
}
/* Face: AnimatedContainer(100ms, height 48, margin top: pressed ? 5 : 0),
   Colors.white, StadiumBorder(side: BorderSide(theme, width 2)).
   ShapeDecoration insets the child by the stroke, hence border-box. Inner
   highlight BoxShadow(white 35%, (0,-2), blur 4, spread -1) while not
   pressed: invisible on a white face, ported for parity. */
.oidp__face{
  position:absolute;top:0;left:0;right:0;height:48px;box-sizing:border-box;
  background:#FFFFFF;border:2px solid var(--oid);border-radius:999px;
  display:flex;align-items:center;justify-content:center;
  box-shadow:inset 0 2px 4px -1px rgba(255,255,255,.35);
  transition:transform .07s linear,box-shadow .07s linear; /* `_kPushFlat` 70ms */
}
/* FittedBox(scaleDown) > Text(label, 16 w900, color: labelColor). */
.oidp__lbl{
  display:block;max-width:100%;min-width:0;
  font-size:16px;font-weight:900;line-height:1;white-space:nowrap;
  /* Left origin, for the same reason as .oid__lbl. */
  color:var(--oidp-lbl,var(--oid));transform-origin:0 50%;
}
/* Press: the face drops 5 (`margin top: _pressed ? 5 : 0`) and the highlight
   goes (`if (!_pressed)`), _ScanNowButton's face AnimatedContainer
   (brand_lucky_draw_page.dart:18918-18954). `.is-pressed` (order_id.js
   pushToFlat) holds it from pointerdown through the 100+30ms push (onTapUp,
   :18830-18852) and while the dialog is open; the rise back is `.is-rising`
   (A.css). */
.oidp.is-pressed .oidp__face{transform:translateY(5px);box-shadow:none}
/* Capped (`widget.disabled`): a separate branch in the Dart that keeps the
   scan CTAs' geometry: #D9D9D9 base inset 4 each side, #ECECEC face, NO
   border, ContinuousRectangleBorder(44) -> HALF, 22px; label 14 w900 #9E9E9E,
   no highlight, no pulse, no gesture. Same footprint so the bar keeps its
   height. */
.oidp[disabled]{animation:none;pointer-events:none;cursor:default}
.oidp[disabled] .oidp__sh{left:4px;right:4px;border-radius:22px;background:#D9D9D9}
.oidp[disabled] .oidp__face{background:#ECECEC;border:0;border-radius:22px;box-shadow:none}
.oidp[disabled] .oidp__lbl{font-size:14px;color:#9E9E9E}

/* While a dialog is open both controls step aside (orderIdSheetDepth):
   Opacity 0, INSTANT both ways and at full size (order_id_squircle_cta.dart
   :435-440; _HidesWhileOrderIdOpen, brand_lucky_draw_page.dart:19335). Gone
   on the frame the copy starts travelling, back on the frame it lands, still
   flat, then the face rises. The app dropped the 0.86 shrink. Keeps its
   slot, so nothing reflows. */
body.oid-away .oid{opacity:0;pointer-events:none}
body.oid-away .oidp{opacity:0;pointer-events:none}

/* ---- the dialog: _TesterOrderIdSheet.build, presented by
        showBrandOrderIdSheet (showGeneralDialog, barrier transparent,
        dismissible) and _MorphFromOrigin --------------------------------- */
/* z 200 sits in the ladder's sheet band (190-210), above the result's 95.
   Dialog(insetPadding: symmetric(24, 24)) centres the card. order_id.js
   raises padding-bottom by the keyboard's height. */
.oids{
  position:fixed;inset:0;z-index:200;box-sizing:border-box;
  display:flex;align-items:center;justify-content:center;
  padding:24px;
}
body.oids-open{overflow:hidden}
/* The scrim, painted inside the transition: black 54% (the settled
   `orderIdMorphScrim`). */
.oids__scrim{position:absolute;inset:0;background:rgba(0,0,0,.54)}
/* The travelling copy of the button: `orderIdMorphBox`. order_id.js animates
   its box; the face inside is the button's own white face and 2px accent
   ring, fading out as the card takes over. */
.oids__ghost{position:fixed;left:0;top:0;width:0;height:0;overflow:hidden;pointer-events:none;z-index:1}
.oids__ghostface{
  position:absolute;inset:0;box-sizing:border-box;border-radius:inherit;
  background:#fff;border:2px solid var(--oid);
}
/* Dialog: Colors.white, elevation 8, SmoothRectangleBorder(28,
   cornerSmoothing 0.6) -> fallback 1.07x = 30px FIRST, then the 1.5x = 42px
   superellipse(1.6) pair (shape-and-color.md). ConstrainedBox(maxWidth: 420);
   a Row with an Expanded title makes the card take that full width.
   clipBehavior antiAlias. */
.oids__card{
  position:relative;z-index:2;width:100%;max-width:420px;max-height:100%;
  display:flex;flex-direction:column;overflow:hidden;
  background:#fff;border-radius:30px;
  box-shadow:0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12);
}
@supports (corner-shape: superellipse(1.6)){
  .oids__card{border-radius:42px;corner-shape:superellipse(1.6)}
}
/* Padding fromLTRB(20, 22, 20, 20) > SingleChildScrollView: scrolls rather
   than overflowing when the keyboard takes most of the screen. */
.oids__scroll{min-height:0;overflow-y:auto;overflow-x:hidden;padding:22px 20px 20px;-webkit-overflow-scrolling:touch}
/* Row(crossAxisAlignment: start)[Expanded(title), help]. */
.oids__hd{display:flex;align-items:flex-start}
/* luckyDrawOrderIdTitle: 18 w900 #1A1A1A. */
.oids__t{flex:1;min-width:0;margin:0;font-size:18px;font-weight:900;color:#1A1A1A}
/* Help: 28x28 circle, #F2F3F5 (accent 12% while open), Icons.question_mark
   15, #6B7280 (accent while open). */
.oids__help{
  flex:none;width:28px;height:28px;border-radius:50%;border:0;padding:0;
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  background:#F2F3F5;font:inherit;
}
.oids__helpg{width:15px;height:15px;fill:#6B7280;display:block}
.oids__help.is-open{background:var(--oid-12,#F2F3F5)}
.oids__help.is-open .oids__helpg{fill:var(--oid)}
/* "?" opens two tutorial buttons side by side (`_tutorialButton`, owner
   2026-10-03): SizedBox(10) above, SizedBox(8) between, each Expanded. */
.oids__tuts{margin-top:10px;display:flex;gap:8px}
.oids__tuts[hidden]{display:none}
/* OutlinedButton: white, side #E3E5E8 1.2, padding 10/10, SmoothRectangle
   r12 s0.6 (18px superellipse(1.6), fallback 13px). Row(min): logo 20x20 r5,
   SizedBox(8), label Switzer 13 w800 #1A1A1A one line, SizedBox(6),
   play_circle_outline_rounded 18 in the store accent. */
.oids__tut{
  flex:1 1 0;min-width:0;display:flex;align-items:center;justify-content:center;
  padding:10px;background:#fff;border:1.2px solid #E3E5E8;border-radius:13px;
  cursor:pointer;font:inherit;color:#1A1A1A;
}
@supports (corner-shape: superellipse(1.6)){
  .oids__tut{border-radius:18px;corner-shape:superellipse(1.6)}
}
.oids__tutlogo{width:20px;height:20px;border-radius:5px;flex:none;object-fit:contain}
.oids__tutl{margin-left:8px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:800}
.oids__tutg{width:18px;height:18px;margin-left:6px;flex:none;fill:var(--tut-accent,#1A1A1A)}
/* SizedBox(6); subtitle 13, height 1.4, #6B7280. */
.oids__s{margin:6px 0 0;font-size:13px;line-height:1.4;color:#6B7280}
/* SizedBox(16); TextField: 15 w600 onSurface, hint #B0B4BB, contentPadding
   14/14, r12. The field answers what is typed (order_id.js render()): --fill
   is #F6F7F9, the marketplace colour at 6% or red at 5%; --ring is the
   campaign accent until the id has a marketplace's shape, then that
   marketplace's colour, red when it can be neither. enabledBorder 1.5,
   focusedBorder 2, drawn as an inset shadow so the 0.5 step never shifts
   the text. The InputDecorator suffix slot is 48x48 and sits OUTSIDE the
   content padding, so the right inset is 14 + 48. line-height 20 makes the
   row 48 tall, which is what the 48pt suffix forces on the decorator. */
.oids__field{margin-top:16px;position:relative;--ring:var(--oid);--fill:#F6F7F9}
.oids__in{
  width:100%;box-sizing:border-box;display:block;
  font:inherit;font-size:15px;font-weight:600;line-height:20px;color:var(--ink);
  background:var(--fill);border:0;border-radius:12px;
  box-shadow:inset 0 0 0 1.5px var(--ring);
  padding:14px 62px 14px 14px;outline:none;
  transition:background-color .15s ease, box-shadow .15s ease;
}
.oids__in:focus{box-shadow:inset 0 0 0 2px var(--ring)}
.oids__in::placeholder{color:#B0B4BB}
/* prefixIcon: Padding(left 12, right 2) + ClipRRect(6) 24x24, centred on the
   48 row; the text then starts after the contentPadding's own 14. */
.oids__logo{
  position:absolute;left:12px;top:12px;width:24px;height:24px;
  border-radius:6px;object-fit:cover;display:block;pointer-events:none;
}
.oids__logo[hidden]{display:none}
.oids__field.has-logo .oids__in{padding-left:52px}
/* errorText: luckyDrawOrderIdErrFormat, _kNotWonRed 12 w700, errorMaxLines 2,
   under the field at the content's inset. */
.oids__ferr{
  margin:6px 0 0;padding:0 14px;
  font-size:12px;line-height:16px;font-weight:700;color:#D23B3B;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}
.oids__ferr[hidden]{display:none}
/* AbsorbPointer(absorbing: _loading): nothing in the card answers a tap
   while the order is checked. */
.oids__card.is-busy .oids__scroll{pointer-events:none}
/* suffixIcon IconButton(Icon(content_paste_rounded, 18)): 48x48, glyph onSurface. */
.oids__paste{
  position:absolute;right:0;top:0;width:48px;height:48px;
  display:flex;align-items:center;justify-content:center;
  background:none;border:0;padding:0;cursor:pointer;font:inherit;
}
.oids__pasteg{width:18px;height:18px;fill:#000;display:block}
/* SizedBox(16); SizedBox(w inf, h 50) ElevatedButton: bg accent,
   disabledBackgroundColor #D9D9D9, elevation 0, SmoothBorderRadius(16,
   cornerSmoothing 0.6) -> fallback 1.07x = 17px first, then the 1.5x = 24px
   superellipse(1.6) pair. Child Text(16 w900, white) — the colour is on the
   Text, so it stays white when disabled. */
.oids__go{
  margin-top:16px;width:100%;height:50px;padding:0;border:0;
  background:var(--oid);color:#fff;
  font:inherit;font-size:16px;font-weight:900;
  border-radius:17px;display:flex;align-items:center;justify-content:center;cursor:pointer;
}
@supports (corner-shape: superellipse(1.6)){
  .oids__go{border-radius:24px;corner-shape:superellipse(1.6)}
}
.oids__go:disabled{background:#D9D9D9;color:#fff;cursor:default}
/* Loading: SizedBox(20x20, CircularProgressIndicator(strokeWidth 2, white)) —
   a bare arc, no track. */
.oids__spin{
  width:20px;height:20px;border:2px solid transparent;border-top-color:#fff;
  border-radius:50%;display:block;animation:oidsSpin .8s linear infinite;
}
@keyframes oidsSpin{to{transform:rotate(360deg)}}
/* Error box (_resultBox isError): SizedBox(14); padding 12, #FDECEA, border
   #F2C4C1, r10, SelectableText 12 / 1.45 monospace #B3261E, maxHeight 260
   scrolling. The `_result` twin is dead in the app and not ported (§6.7). */
.oids__err{
  margin-top:14px;padding:12px;
  background:#FDECEA;border:1px solid #F2C4C1;border-radius:10px;
  font:12px/1.45 monospace;color:#B3261E;
  max-height:260px;overflow-y:auto;
  -webkit-user-select:text;user-select:text;
  white-space:pre-wrap;word-break:break-word;
}
@media (prefers-reduced-motion:reduce){
  .oidp{animation:none}
}


/* ── artless lucky-draw card header ──────────────────────────────────────
   A draw with no `cardImage` / `heroImage` still gets the app's gradient
   header: `SizedBox(height: 168)` holding the header gradient, five
   `_ConfettiBit`s and `Icons.card_giftcard` at 56 in white-92%, with the
   promo-tag pill pinned top-left (baisedCampaign.dart, _BrandLuckyDrawHomeCard).
   Cintage is the first such draw in the mini's feed. The card's own
   overflow:hidden clips these corners, so the block carries no radius. */
.ldhero{position:relative;height:168px;overflow:hidden;background:var(--theme)}
/* `imageHeaderPadding` fromLTRB(12, 12, 12, 10) — the placeholder sits in the
   same padded box the cover art would fill. */
.ldhero__ico{
  position:absolute;top:12px;right:12px;bottom:10px;left:12px;
  display:flex;align-items:center;justify-content:center;
}
.ldhero__ico svg{width:56px;height:56px;fill:rgba(255,255,255,.92);display:block}
/* Positioned(top: 12, left: 12); padding 8/4, radius 8, #FFF3E0 on #E65100. */
.ldhero__tag{
  position:absolute;top:12px;left:12px;
  padding:4px 8px;border-radius:8px;
  background:#FFF3E0;color:#E65100;
  font-size:12px;font-weight:700;line-height:1.25;
  max-width:calc(100% - 24px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* _ConfettiBit: a dot, or a 2px-cornered square when `rounded: false`. Every
   offset, size, colour and rotation is set inline from the Dart's literals. */
.ldconf{position:absolute;display:block;border-radius:50%}
.ldconf--sq{border-radius:2px}

/* Entry row with no ticket art: Icon(confirmation_number_rounded, 20, accent)
   (entry_count_card.dart, compact). The row's gap:8 is the SizedBox(8). */
.ldcard__tkico{flex:none;display:block}
.ldcard__tkico svg{width:20px;height:20px;fill:var(--theme);display:block}


/* ── art-less draw hero ──────────────────────────────────────────────────
   The app pads its hero by the device's top inset (`EdgeInsets.only(top:
   topPad)`, _buildHero) and puts its own back control inside that band. LINE
   draws its own header above the webview, so the inset is 0 here and the
   hero's first line landed underneath the mini's back button. A draw WITH art
   is unaffected: its body is anchored to the artwork, which is why this was
   invisible until an art-less draw (Cintage) rendered. 16 + 32 + the app's own
   4px = 52. */
.dhero:not(.dhero--art) .dhero__body{
  padding-top:calc(52px + env(safe-area-inset-top,0px));
}

/* The generic hero countdown: no label, not compact, the widget's own cream
   box (BrandLuckyDrawCountdown defaults — `boxColor ?? #FFF6E5` with the
   #E6D5B3 / #D4C2A1 lips, radius `compact ? 8 : 12`, padding 6/2, number 20,
   unit 9 at black45, separator 18 at labelColor = white-90%). Golden Mountain
   and Mellolica keep their own compact, coloured treatment above. */
.dhero__cdwrap--plain{width:auto;text-align:left}
.dhero__cd--plain i{
  flex:0 0 auto;min-width:40px;
  background:var(--dcd-box,#FFF6E5);border-radius:12px;padding:6px 2px;
  box-shadow:0 3px 0 var(--dcd-sh1,#E6D5B3), 0 4.5px 0 var(--dcd-sh2,#D4C2A1);
}
.dhero__cd--plain i b{font-size:20px}
/* --dcd-*: the draw's own countdown colours (`countdownBoxColor` box + sep +
   Color.lerp lips, `countdownTextColor` unit; brand_lucky_draw_page.dart
   :4016), set only when the doc carries them. Defaults are the literals. */
.dhero__cd--plain i span{font-size:9px;font-weight:600;color:var(--dcd-unit,rgba(0,0,0,.45));opacity:1}
.dhero__cd--plain s{color:var(--dcd-sep,rgba(255,255,255,.9));font-size:18px}

/* ==== Order-ID press: rise + pulse rest (LINE mini Cintage parity, 2026-10-03) ==== */
/* ── agent A: order-id press (push to flat, rise back) ───────────────────
   New rules only; the edits to existing .oid / .oidp rules are in
   A_css_edits.md. Every selector here out-ranks the base rule it overrides
   on specificity (0,3,0 vs 0,1,0 / 0,2,0), so EOF placement is safe.
   Driven by order_id.js `pushToFlat` / `riseBack`:
     .is-pressed  pointerdown → through the push hold → while the dialog is
                  open (face flat, `_awaitingReturn`)
     .is-rising   60ms after the dialog has fully closed, for 320ms
   ─────────────────────────────────────────────────────────────────────── */

/* The rise back to 3D after the dialog flies home: `_riseBack` 320ms,
   Curves.easeOutCubic (order_id_squircle_cta.dart:215, :365-366). The press
   itself keeps the face's base 90ms linear transition (:365). */
.oid.is-rising .oid__face{transition-duration:.32s;transition-timing-function:cubic-bezier(.215,.61,.355,1)}

/* Same for the draw-page pill: `_kRiseBack` 320ms easeOutCubic, the press
   `_kPushFlat` 100ms linear (brand_lucky_draw_page.dart:18622, :18618; face
   AnimatedContainer duration/curve, :18918-18920). */
.oidp.is-rising .oidp__face{transition-duration:.32s;transition-timing-function:cubic-bezier(.215,.61,.355,1)}

/* The heartbeat rests while pressed and while rising, and restarts only once
   the face is fully 3D again (onTapDown `_pulseController.animateBack(0.0)`,
   `_pulseController.repeat` after the rise; _ScanNowButton,
   brand_lucky_draw_page.dart:18710-18722, :18808-18814). DEVIATION: the
   scale snaps to 1 instead of easing back over 100ms, since a CSS animation
   cannot be eased out from whatever frame it is on. */
.oidp.is-pressed:not([disabled]),.oidp.is-rising:not([disabled]){animation:none}

/* ==== Home card full-bleed art, doc countdown size, winner claim (Cintage parity, 2026-10-03) ==== */
/* ── home card: cardArtFullBleed header (index.html card()) ──────────────
   `cardArtFullBleed: true` (baisedCampaign.dart:6459) draws the card art in
   the SAME 168px `.ldhero` the art-less card uses, over the same header
   gradient (set inline by heroBg()): padding EdgeInsets.zero, scale 1.0,
   BoxFit.fitWidth, Alignment.center, ClipRect (:6992-7024). fitWidth = the
   image is exactly the header's width at its own aspect; centred vertically;
   `.ldhero`'s overflow:hidden is the ClipRect, so a taller banner crops top
   and bottom equally and a shorter one letterboxes onto the gradient. */
.ldhero--bleed .ldhero__art{
  position:absolute;left:0;top:50%;
  width:100%;max-width:none;height:auto;
  transform:translateY(-50%);
  display:block;
}
/* The gift fallback (the art's errorWidget, :7011-7019) sits in the art's
   own box, which has NO padding here (EdgeInsets.zero, :6996). */
.ldhero--bleed .ldhero__ico{top:0;right:0;bottom:0;left:0}

/* ── home card: the app's second body layout's countdown size ─────────────
   `numberFontSize: 13, unitFontSize: 8` (baisedCampaign.dart:7367-7368) and
   the closed notice's `valueFontSize: 12, unitFontSize: 8` (:7339-7340), for
   cards without the logo + wordmark lockup. Added by index.html only on
   cards carrying countdownBoxColor / countdownTextColor / cardArtFullBleed,
   so every older card keeps today's 18/10 and 15/9. */
.cd--sm i b{font-size:13px}
.cd--sm i span{font-size:8px}
.cd--closed.cd--sm b{font-size:12px}
.cd--closed.cd--sm span{font-size:8px}

/* ── the signed-in winner's own result + claim (winner_claim.js) ─────────
   brand_lucky_draw_page.dart _buildWinnerRevealBlock (:9805) and
   _WinnerClaimSection (:17248). --wc-theme* are set inline from the draw's
   theme colour (`_themeColor`, :582), tints precomputed as rgba(). Every
   radius here is a plain BorderRadius.circular in the Dart, so no squircle
   inflation; the name fields below are the one figma_squircle. */
.wclaim{margin-bottom:18px}            /* SizedBox(height: 18) under the block (:9632) */

/* Banner: AspectRatio(2277/351), BoxFit.contain, Alignment.centerLeft (:9875-9881). */
.wclaim__banner{width:100%;aspect-ratio:2277/351}
.wclaim__banner img{width:100%;height:100%;object-fit:contain;object-position:left center;display:block}

/* _buildWinnerCongratsFallback (:9979): 32x32 trophy, 10 gap, 20 w900 theme. */
.wclaim__head{display:flex;align-items:center;gap:10px}
.wclaim__trophy{width:32px;height:32px;flex:none;display:flex;align-items:center;justify-content:center}
.wclaim__trophy img{width:32px;height:32px;object-fit:contain;display:block}
.wclaim__trico svg{width:28px;height:28px;fill:var(--wc-theme);display:block}
.wclaim__htx{min-width:0;font-size:20px;font-weight:900;line-height:1.3;color:var(--wc-theme)}

/* Info rows, `_winnerInfoRow` (:10377): 16 under the heading (:9889), 10 under
   each row, 84px label column 13 w600 grey.shade600, value 14 w800 #1A1A1A.
   Rows top-aligned unless a trailing control centres them. */
.wclaim__rows{margin-top:16px}
.wclaim__row{display:flex;align-items:flex-start;padding-bottom:10px}
.wclaim__row--t{align-items:center}
.wclaim__k{width:84px;flex:none;font-size:13px;font-weight:600;line-height:1.4;color:#757575}
.wclaim__v{flex:1 1 auto;min-width:0;font-size:14px;font-weight:800;line-height:1.4;color:#1A1A1A;overflow-wrap:anywhere}
/* "View my seat" (:9913-9952): 8 gap, 10/6 padding, pill, theme @6% fill,
   theme @35% 1px border, event_seat 13 + 5 gap, 11 w800 theme. */
.wclaim__seat{
  flex:none;margin-left:8px;
  display:inline-flex;align-items:center;gap:5px;
  padding:6px 10px;border-radius:999px;
  background:var(--wc-theme-06);border:1px solid var(--wc-theme-35);
  color:var(--wc-theme);font-size:11px;font-weight:800;line-height:1.2;
}
.wclaim__seatic svg{width:13px;height:13px;fill:currentColor;display:block}

/* 12 between the rows and the claim section (:9954). */
.wclaim__state{margin-top:12px}

/* `_box` (:17360): full width, 14 padding, radius 12; icon 22, 10 gap, text
   13 / 1.5 w700. */
.wclaim__box{display:flex;align-items:center;gap:10px;width:100%;padding:14px;border-radius:12px}
.wclaim__bic{flex:none;display:block}
.wclaim__bic svg{width:22px;height:22px;display:block}
.wclaim__btx{flex:1 1 auto;min-width:0;font-size:13px;line-height:1.5;font-weight:700}
/* Verified (:17482-17503). */
.wclaim__box--ok{background:#E7F6EA}
.wclaim__box--ok .wclaim__bic svg{fill:#2E7D32}
.wclaim__box--ok .wclaim__btx{color:#1B5E20}
/* Expired (:17521-17540). */
.wclaim__box--x{background:#FDECEA}
.wclaim__box--x .wclaim__bic svg{fill:#C62828}
.wclaim__box--x .wclaim__btx{color:#B71C1C}
/* No claimDeadline (:17557-17571): gold @20%, 13 / 1.5 w600 #5A4A1A. */
.wclaim__box--gold{background:var(--wc-gold-20)}
.wclaim__box--gold .wclaim__btx{font-weight:600;color:#5A4A1A}

/* Pending countdown (:17573-17617): theme @8%, 12/14 padding, radius 12. */
.wclaim__cd{
  display:flex;flex-direction:column;align-items:center;text-align:center;
  width:100%;padding:12px 14px;border-radius:12px;background:var(--wc-theme-08);
}
.wclaim__cdl{font-size:12px;font-weight:600;line-height:1.3;color:#757575}
.wclaim__cdv{
  margin-top:4px;font-size:24px;font-weight:900;line-height:1.2;
  color:var(--wc-theme);font-variant-numeric:tabular-nums;
}
.wclaim__cdi{margin-top:6px;font-size:12px;font-weight:600;line-height:1.3;color:#5A4A1A}

/* Verify entry: ThreeDSupportButton (three_d_support_button.dart). Face 48
   tall, white, 2px accent border, radius 12, 16 w700 #2D2D2D, with its 4px
   bottom margin; 12 under the status box (:17619). Its depth layer sits
   exactly behind the face (both top-aligned in the Stack, 48 tall), so it
   never shows and is not drawn. Press = AnimatedScale 0.95 over 100ms. */
.wclaim__verify{
  position:relative;display:flex;align-items:center;justify-content:center;
  width:100%;height:48px;margin-top:12px;margin-bottom:4px;
  background:#FFFFFF;border:2px solid var(--wc-theme);border-radius:12px;
  color:#2D2D2D;font-size:16px;font-weight:700;line-height:1.2;
  transition:transform .1s;
}
.wclaim__verify:active:not(:disabled){transform:scale(.95)}
/* Loading = disabled face: grey.shade200 fill, grey.shade400 border, the
   bottom margin dropped, a 20px accent spinner (stroke 2) for the label. */
.wclaim__verify.is-busy{background:#EEEEEE;border-color:#BDBDBD;margin-bottom:0;cursor:default}
.wclaim__spin{display:none}
.wclaim__verify.is-busy .wclaim__vtx{display:none}
.wclaim__verify.is-busy .wclaim__spin{
  display:block;width:20px;height:20px;border-radius:50%;
  border:2px solid var(--wc-theme);border-right-color:transparent;
  animation:wclaimSpin .8s linear infinite;
}
@keyframes wclaimSpin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){
  .wclaim__verify{transition:none}
  .wclaim__verify.is-busy .wclaim__spin{animation:none}
}

/* ── the ID-card name form (_ClaimNameDialog :17674) in showDialog ───────
   Subtitle 13 / 1.5 w500 grey.shade700 (:17826-17843), 16 to the first field,
   12 between fields, error 10 below, 12 w700 #D32F2F (:17857-17869). */
.wclaim__form{text-align:left}
.wclaim__fsub{margin-top:8px; /* + .dlg__node's 4 = the dialog's own 12 title gap */ font-size:13px;line-height:1.5;font-weight:500;color:#616161}
/* SmoothRectangleBorder(12, smoothing 1.0) => r27 + superellipse(2.2), r14
   fallback FIRST; side #D6DAE0 1px, focused theme 1.6px (:17742-17752). The
   extra .6px of the focused side is an inset ring so the box does not grow. */
.wclaim__field{
  display:block;margin-top:12px;
  background:#FFFFFF;border:1px solid #D6DAE0;border-radius:14px;
}
@supports (corner-shape: superellipse(2.2)){
  .wclaim__field{border-radius:27px;corner-shape:superellipse(2.2)}
}
.wclaim__fsub + .wclaim__field{margin-top:16px}
.wclaim__field:focus-within{border-color:var(--wc-theme);box-shadow:inset 0 0 0 .6px var(--wc-theme)}
/* contentPadding 14/9, 15 w600 #1A1A1A (:17766-17780). */
.wclaim__input{
  display:block;width:100%;margin:0;border:0;outline:0;background:transparent;
  padding:9px 14px;font:inherit;font-size:15px;font-weight:600;line-height:1.5;color:#1A1A1A;
  -webkit-appearance:none;appearance:none;
}
.wclaim__input::placeholder{color:rgba(0,0,0,.45);font-weight:500}
.wclaim__ferr{margin-top:10px;font-size:12px;font-weight:700;line-height:1.4;color:#D32F2F}
.wclaim__ferr[hidden]{display:none}

/* The AlertDialog chrome around that form (:17796-17821), where :has()
   exists; an engine without it keeps the stock .dlg look, which is still a
   complete, usable dialog. barrier black 55% (:17389); side inset 22
   (:17801); SmoothRectangleBorder(24, 1.0) => r55 + superellipse(2.2), r27
   fallback first (:17803-17808); title 18 w900 #1A1A1A, start-aligned
   (:17809-17821); Submit min height 44, 14 w800 (:17923, :17931-17939). */
.dlg:has(.wclaim__form){background:rgba(0,0,0,.55);padding:24px 22px}
.dlg:has(.wclaim__form) .dlg__card{text-align:left;border-radius:27px}
@supports (corner-shape: superellipse(2.2)){
  .dlg:has(.wclaim__form) .dlg__card{border-radius:55px;corner-shape:superellipse(2.2)}
}
.dlg:has(.wclaim__form) .dlg__t{font-size:18px;font-weight:900;color:#1A1A1A}
.dlg:has(.wclaim__form) .dlg__btn{min-height:44px;font-size:14px;font-weight:800}

/* ==== Home card body, app geometry for cards with the new doc fields (Cintage), 2026-10-03 ==== */
/* Padding LTRB 18,8,18,8 (baisedCampaign.dart:7079-7084); the row has no floor;
   SizedBox(12) above the entry row (:7430). */
.ldcard__body--app{padding:8px 18px}
.ldcard__body--app .ldcard__row{min-height:0}
.ldcard__body--app .ldcard__row2{margin-top:12px}
/* `_brandNamePlaceholder`: Switzer 22 w900 #1A1A1A (:6141-6154). */
.ldcard__brand--lg{font-size:22px;font-weight:900;color:#1A1A1A}
/* EntryCountCard compact: ticket iconWidth 48 with 6px after it, count 22 w900
   (entry_count_card.dart:215-238). The row's own gap is 8, so pull 2. */
.ldcard__body--app .ldcard__ticket{width:48px;height:auto}
.ldcard__body--app .ldcard__ticket + .ldcard__n{margin-left:-2px}
.ldcard__body--app .ldcard__n{font-size:22px;font-weight:900}

/* ==== Order-id tutorial videos (tutorial_video.js), 2026-10-03 ==== */
/* Store rules box: "How to get your order ID on Shopee" (`_storeHowToSection`):
   Padding(top 14), full width, white, side 1.5 in the store colour, padding
   12/12, SmoothRectangle r14 s1.0 (32px superellipse(2.2), fallback 16px);
   play_circle_outline_rounded 20 + label Switzer 13 w800, both in the colour. */
.howto{
  margin-top:14px;width:100%;display:flex;align-items:center;justify-content:center;gap:8px;
  padding:12px;background:#fff;border:1.5px solid var(--howto);border-radius:16px;
  color:var(--howto);cursor:pointer;font:inherit;
}
@supports (corner-shape: superellipse(2.2)){
  .howto{border-radius:32px;corner-shape:superellipse(2.2)}
}
.howto__g{width:20px;height:20px;flex:none;fill:var(--howto)}
.howto__l{font-size:13px;font-weight:800;text-align:center;line-height:1.3}

/* The pop-up (`showOrderIdTutorialVideo`): barrier black 60%; Dialog white,
   r24, insetPadding 20/24; content padding LTRB 16,12,8,16. Above the order-id
   sheet (z 200). */
body.tvid-open{overflow:hidden}
.tvid{position:fixed;inset:0;z-index:230;display:flex;align-items:center;justify-content:center;padding:24px 20px}
.tvid__scrim{position:absolute;inset:0;background:rgba(0,0,0,.6)}
.tvid__card{
  position:relative;width:100%;max-width:560px;max-height:100%;overflow:auto;
  display:flex;flex-direction:column;
  background:#fff;border-radius:24px;padding:12px 8px 16px 16px;
}
/* Header: play_circle_outline_rounded 20 accent, SizedBox(8), title Switzer
   15 w900 #1A1A1A, IconButton close_rounded (48 box, 24 glyph, black45). */
.tvid__head{display:flex;align-items:center;gap:8px}
.tvid__hico{width:20px;height:20px;flex:none;fill:var(--tvid-accent)}
.tvid__title{flex:1 1 auto;margin:0;font-size:15px;font-weight:900;color:#1A1A1A;line-height:1.3}
.tvid__x{width:48px;height:48px;flex:none;display:flex;align-items:center;justify-content:center;background:none;border:0;padding:0;cursor:pointer}
.tvid__xg{width:24px;height:24px;fill:rgba(0,0,0,.45)}
/* SizedBox(4); the 9:16 clip as tall as fits: maxHeight 62% of the screen
   (68% with no steps), Padding(right 8), ClipRRect r16 on #F2F2F2. */
.tvid__box{
  position:relative;flex:none;align-self:center;margin:4px 8px 0 0;overflow:hidden;
  aspect-ratio:9/16;border-radius:16px;background:#F2F2F2;
  height:min(62vh, calc((min(100vw - 40px, 560px) - 32px) * 16 / 9));
}
.tvid--nosteps .tvid__box{height:min(68vh, calc((min(100vw - 40px, 560px) - 32px) * 16 / 9))}
/* BoxFit.cover; hidden until the first frame so no black flash. */
.tvid__v{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;opacity:0;cursor:pointer}
.tvid__box.is-ready .tvid__v{opacity:1}
/* Loading: CircularProgressIndicator 26, stroke 2.6, accent. */
.tvid__spin{
  position:absolute;left:50%;top:50%;width:26px;height:26px;margin:-13px 0 0 -13px;
  border-radius:50%;border:2.6px solid var(--tvid-accent);border-right-color:transparent;
  animation:tvidSpin .8s linear infinite;box-sizing:border-box;
}
@keyframes tvidSpin{to{transform:rotate(360deg)}}
/* Failed: luckyDrawTutorialVideoError, 13 black54, centred, padding 24. */
.tvid__err{position:absolute;inset:0;margin:0;display:flex;align-items:center;justify-content:center;
  padding:24px;text-align:center;font-size:13px;color:rgba(0,0,0,.54)}
/* Paused: play_circle_fill_rounded 64 white over the frame, 150ms fade. */
.tvid__pause{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  pointer-events:none;opacity:0;transition:opacity .15s}
.tvid__pauseg{width:64px;height:64px;fill:#fff}
.tvid.is-paused .tvid__box.is-ready .tvid__pause{opacity:1}
/* Speaker: bottom-right 8, CircleBorder black 45%, IconButton compact (40),
   volume_off/up_rounded 20 white. */
.tvid__mute{position:absolute;right:8px;bottom:8px;width:40px;height:40px;border-radius:50%;
  border:0;padding:0;background:rgba(0,0,0,.45);display:none;align-items:center;justify-content:center;cursor:pointer}
.tvid__box.is-ready .tvid__mute{display:flex}
.tvid__muteg{width:20px;height:20px;fill:#fff}
/* Steps (`OrderIdTutorialSteps`): SizedBox(12), Padding(right 8), #F6F6F7
   r16 padding 12; rows 8 apart; numbered circle 20 in the accent (11 w800
   white), SizedBox(10), text 13 / 1.35 w500 #4A4A4A with "labels" w800 #1A1A1A. */
.tvid__steps{flex:none;margin:12px 8px 0 0;padding:12px;background:#F6F6F7;border-radius:16px}
.tvid__step{display:flex;align-items:flex-start;gap:10px}
.tvid__step + .tvid__step{margin-top:8px}
.tvid__n{width:20px;height:20px;flex:none;border-radius:50%;background:var(--tvid-accent);
  color:#fff;font-size:11px;font-weight:800;line-height:1;display:flex;align-items:center;justify-content:center}
.tvid__stept{margin:1px 0 0;font-size:13px;line-height:1.35;font-weight:500;color:#4A4A4A}
.tvid__stept b{font-weight:800;color:#1A1A1A}
/* Laptop (same query as WIDE in draw_page.js): the clip is the point, so it
   runs the full height of the window on the left, with the title and steps
   beside it, instead of a 62vh clip with the steps stacked under it (owner,
   2026-10-06: "bigger so we can better see the video"). The height also
   respects the width, so a 900px wide window still fits both columns.
   Phones are unchanged. */
@media (min-width:900px) and (hover:hover) and (pointer:fine){
  .tvid{padding:24px}
  .tvid__card{
    width:auto;max-width:none;
    display:grid;grid-template-columns:auto minmax(280px,340px);
    grid-template-rows:auto 1fr;grid-template-areas:"box head" "box steps";
    column-gap:28px;padding:16px 12px 16px 16px;
  }
  .tvid__head{grid-area:head;align-self:start}
  .tvid__title{font-size:17px}
  .tvid__box,
  .tvid--nosteps .tvid__box{
    grid-area:box;margin:0;align-self:start;
    height:min(calc(100vh - 80px), 900px, calc((100vw - 384px) * 16 / 9));
  }
  .tvid__steps{grid-area:steps;align-self:start;margin:8px 4px 0 0;padding:16px}
  .tvid__step + .tvid__step{margin-top:12px}
  .tvid__n{width:24px;height:24px;font-size:12px}
  .tvid__stept{margin-top:2px;font-size:14.5px}
}

/* ==== Entries drawer + order rows/details (entry_history.js), Cintage parity 2026-10-03 ==== */
/* ═══════════════════════════════════════════════════════════════════════════
   ENTRIES DRAWER + ORDER-ID ENTRY ROWS/DETAILS (entry_history.js)
   Ports widgets/entries_drawer.dart (EntriesDrawerHost / EntriesDrawerPanel)
   and widgets/order_id_entry_details.dart (OrderIdEntryRow /
   OrderIdEntryDetails). New rules only; the existing .ehist / .ehimg rules
   are reused as they are.

   Curves: Curves.easeInOutCubic = Cubic(.645,.045,.355,1) (flutter
   curves.dart:1765) going forward; the reverse runs the SAME curve backwards,
   which in CSS is its point reflection (.645,0,.355,.955). A transition takes
   the timing of the state it is heading to, so each "on" state carries the
   forward curve and each base state the reflected one.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ---- the drawer (EntriesDrawerHost, entries_drawer.dart:35) -------------- */

/* Page scroll lock while the drawer is up (the app's scrim takes every
   gesture). Its own class, so another sheet releasing `lb-open` cannot unlock
   the page under the drawer. */
.edr-lock{overflow:hidden}

/* Scrim: black at 0.4·t (_kScrimAlpha, :26); tap closes. z-index is set from
   JS to one under the bar. 520ms open / 420ms close (:13-14). */
.edr-scrim{
  position:fixed;inset:0;z-index:39;
  background:rgba(0,0,0,.4);
  opacity:0;touch-action:none;
  transition:opacity 420ms cubic-bezier(.645,0,.355,.955);
}
.edr-scrim.is-open{opacity:1;transition:opacity 520ms cubic-bezier(.645,.045,.355,1)}

/* The panel: white (the sheet's ColoredBox, :244), laid out at its FINAL
   height from the first frame (:210-215) and parked --edr-travel down; JS sets
   left/width/height from the bar and z-index to one over it. Never a height
   animation: only the transform moves. */
.edr{
  position:fixed;left:0;bottom:0;z-index:41;
  display:flex;flex-direction:column;
  background:#fff;color:#1A1A1A;
  transform:translateY(var(--edr-travel,100%));
  transition:transform 420ms cubic-bezier(.645,0,.355,.955);
}
.edr.is-open{transform:translateY(0);transition:transform 520ms cubic-bezier(.645,.045,.355,1)}

/* Content opacity = Interval(0.25, 1) of the CURVED t (:256), not of time.
   easeInOutCubic reaches 0.25 at 38.68% of the run, so opening waits
   0.3868 × 520 = 201ms and fades over the remaining 319ms on a fitted curve
   (max error 0.0005); closing fades out over the first 0.6132 × 420 = 258ms
   on that curve's reflection. */
.edr__fade{
  flex:1;min-height:0;display:flex;flex-direction:column;
  opacity:0;
  transition:opacity 258ms cubic-bezier(.725,0,.728,.611);
}
.edr.is-open .edr__fade{opacity:1;transition:opacity 319ms cubic-bezier(.272,.389,.275,1) 201ms}

/* ---- panel head (EntriesDrawerPanel, :376-432) ---------------------------- */
.edr__div{flex:none;height:1px;background:rgba(0,0,0,.06)}   /* Divider 1, black 6% */
.edr__head{
  flex:none;position:relative;
  display:flex;align-items:center;
  padding:10px 8px 4px;                                        /* LTRB 8,10,8,4 */
}
.edr__head button{
  margin:0;border:0;background:none;font:inherit;color:inherit;
  cursor:pointer;-webkit-tap-highlight-color:transparent;
}
/* Back arrow: IconButton compact = 40×40, arrow_back_ios_new_rounded 18
   #1A1A1A. The app grows its width 0→40 (Align widthFactor in a ClipRect)
   and fades it in with the detail (:386-404). Here it is laid out at 40 and
   revealed left-to-right with clip-path while the title slides 40px right,
   the same picture without re-laying the row out every frame. */
.edr__back{
  position:absolute;left:8px;top:calc(10px + (100% - 14px) / 2 - 20px);
  width:40px;height:40px;padding:11px;
  opacity:0;clip-path:inset(0 100% 0 0);pointer-events:none;
  transition:opacity 320ms cubic-bezier(.645,0,.355,.955),
             clip-path 320ms cubic-bezier(.645,0,.355,.955);
}
.edr__back svg{display:block;width:18px;height:18px;fill:#1A1A1A}
.edr.is-det .edr__back{
  opacity:1;clip-path:inset(0 0 0 0);pointer-events:auto;
  transition:opacity 320ms cubic-bezier(.645,.045,.355,1),
             clip-path 320ms cubic-bezier(.645,.045,.355,1);
}
/* Title: 12 gap after the arrow; 16 w900 #1A1A1A; tap = back (:406-421). */
.edr .edr__title{
  flex:1;min-width:0;margin-left:12px;padding:0;
  text-align:left;font-size:16px;font-weight:900;color:#1A1A1A;
  transform:translateX(0);
  transition:transform 320ms cubic-bezier(.645,0,.355,.955);
}
.edr.is-det .edr__title{transform:translateX(40px);transition:transform 320ms cubic-bezier(.645,.045,.355,1)}
/* Close: keyboard_arrow_down_rounded 26 black45, compact IconButton (:423). */
.edr__x{position:relative;z-index:1;flex:none;width:40px;height:40px;padding:7px}
.edr__x svg{display:block;width:26px;height:26px;fill:rgba(0,0,0,.45)}

/* ---- list ↔ detail (:433-475) -------------------------------------------- */
.edr__body{flex:1;min-height:0;position:relative;overflow:hidden}
/* The list stays mounted under a detail, so going back keeps its scroll.
   Bottom safe area OUTSIDE the scroll view (Padding around the ListView). */
.edr__list{
  position:absolute;top:0;left:0;right:0;bottom:env(safe-area-inset-bottom,0px);
  overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
  transform:translateX(0);opacity:1;
  transition:transform 320ms cubic-bezier(.645,0,.355,.955),
             opacity 320ms cubic-bezier(.645,0,.355,.955);
}
/* list: translate −25%·t, opacity 1 − 0.6·t, ignores taps (:439-448). */
.edr.is-det .edr__list{
  transform:translateX(-25%);opacity:.4;pointer-events:none;
  transition:transform 320ms cubic-bezier(.645,.045,.355,1),
             opacity 320ms cubic-bezier(.645,.045,.355,1);
}
.edr__listin{padding:4px 20px 20px}                            /* LTRB 20,4,20,20 (:8090) */
/* Detail: slides in from Offset(1, 0) on white (:460-466). */
.edr__det{
  position:absolute;inset:0;
  display:flex;flex-direction:column;
  background:#fff;padding-bottom:env(safe-area-inset-bottom,0px);
  transform:translateX(100%);
  transition:transform 320ms cubic-bezier(.645,0,.355,.955);
}
.edr.is-det .edr__det{transform:translateX(0);transition:transform 320ms cubic-bezier(.645,.045,.355,1)}
/* An order in the drawer: SingleChildScrollView, LTRB 20,4,12,18 (:8285). */
.edr__detscroll{
  flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
  padding:4px 12px 18px 20px;
}
/* A scanned receipt's photo in place (_receiptPhotoBody :8140): LTRB
   16,4,16,16, radius 16 (plain), #F4F4F4, contained. */
.edr__photo{flex:1;min-height:0;position:relative;display:flex;align-items:center;justify-content:center}
.edr__photobox{
  position:absolute;top:4px;right:16px;bottom:16px;left:16px;
  display:flex;align-items:center;justify-content:center;
  border-radius:16px;background:#F4F4F4;overflow:hidden;
}
.edr__photobox img{display:block;width:100%;height:100%;object-fit:contain}
.edr__broken{width:40px;height:40px;fill:rgba(0,0,0,.26)}     /* broken_image_outlined 40 black26 */
.edr__none{margin:0;padding:0 20px;text-align:center;font-size:13px;font-weight:500;color:rgba(0,0,0,.54)}

/* ---- OrderIdEntryRow (order_id_entry_details.dart:367) -------------------- */
.ehist-new__row{
  display:flex;align-items:center;width:100%;
  padding:8px 0;border-radius:10px;                            /* v-pad 8, InkWell r10 */
  color:#1A1A1A;cursor:pointer;-webkit-tap-highlight-color:transparent;
}
/* OrderIdPlatformTile: radius size × 0.24, cover (:157). */
.ehist-new__tile{flex:none;display:block;width:40px;height:40px;border-radius:9.6px;object-fit:cover}
.ehist-new__tile--48{width:48px;height:48px;border-radius:11.52px}
.ehist-new__info{flex:1;min-width:0;margin-left:10px}
.ehist-new__store{font-size:14px;font-weight:700;color:#1A1A1A}
.ehist-new__sub{font-size:11.5px;font-weight:500;color:rgba(0,0,0,.45)}
.ehist-new__n{flex:none;margin-left:10px;font-size:16px;font-weight:900;color:var(--eh)}
.ehist-new__tk{flex:none;margin-left:6px;width:30px;height:24px;object-fit:contain}   /* 30×24 contain */
.ehist-new__tkx{flex:none;margin-left:6px;width:18px;height:18px;fill:var(--eh)}     /* confirmation_number 18 */
.ehist-new__chev{flex:none;margin-left:4px;width:18px;height:18px;fill:rgba(0,0,0,.26)}

/* _CopyableOrderId (:304): v-pad 2; text w600 black54 tabular; copy_rounded
   black38. Font size, icon size (fs + 2) and the 5px gap are written inline
   by JS so FittedBox(scaleDown) can scale all three together. */
.ehist-new__oid{
  display:block;width:100%;margin:0;padding:2px 0;border:0;background:none;
  font-family:inherit;line-height:inherit;color:inherit;text-align:left;
  overflow:hidden;cursor:pointer;-webkit-tap-highlight-color:transparent;
}
.ehist-new__oidin{display:inline-flex;align-items:center;white-space:nowrap;vertical-align:top}
.ehist-new__oidt{font-weight:600;color:rgba(0,0,0,.54);font-variant-numeric:tabular-nums}
.ehist-new__oidic{flex:none;display:block;fill:rgba(0,0,0,.38)}

/* ---- OrderIdEntryDetails (:446) ------------------------------------------ */
.ehist-new__det{display:flex;flex-direction:column;color:#1A1A1A}
.ehist-new__dtitle{font-size:12px;font-weight:700;color:rgba(0,0,0,.45)}
.ehist-new__dhead{display:flex;align-items:center;margin-top:10px}
.ehist-new__dinfo{flex:1;min-width:0;margin-left:12px}
.ehist-new__dstore{font-size:17px;font-weight:800}
.ehist-new__ddate{font-size:11.5px;font-weight:500;color:rgba(0,0,0,.45)}
/* close_rounded black45, compact IconButton: 40×40 around the 24 glyph. */
.ehist-new__dx{
  flex:none;width:40px;height:40px;margin:0;padding:8px;border:0;background:none;
  cursor:pointer;-webkit-tap-highlight-color:transparent;
}
.ehist-new__dx svg{display:block;width:24px;height:24px;fill:rgba(0,0,0,.45)}
.ehist-new__ditems{margin-top:16px;font-size:13px;font-weight:800}
.ehist-new__dlist{margin-top:4px}
.ehist-new__dlist .ehist__spin{margin:24px auto}               /* v-pad 24 (:543) */
.ehist-new__dfail{margin:0;padding:16px 0;font-size:13px;font-weight:500;color:rgba(0,0,0,.54)}

/* One product line (_line :611): v-pad 9, top-aligned; 1px #F0F0F0 between. */
.ehist-new__ln{display:flex;align-items:flex-start;padding:9px 0}
.ehist-new__ln + .ehist-new__ln{border-top:1px solid #F0F0F0}
/* OrderItemPhoto (:179): 48, radius 10 (plain), #F2F2F2 holding
   image_outlined at 40% (19.2) black26 until the photo paints. */
.ehist-new__ph{
  flex:none;position:relative;width:48px;height:48px;margin:0 10px 0 0;padding:0;
  border:0;border-radius:10px;overflow:hidden;background:#F2F2F2;
  display:flex;align-items:center;justify-content:center;
  cursor:pointer;-webkit-tap-highlight-color:transparent;
}
.ehist-new__ph svg{width:19.2px;height:19.2px;fill:rgba(0,0,0,.26)}
.ehist-new__ph img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0}
.ehist-new__ph.is-ok img{opacity:1}
.ehist-new__ph.is-ok svg{visibility:hidden}
.ehist-new__lnm{flex:1;min-width:0}
.ehist-new__lnn{
  font-size:12.5px;font-weight:600;line-height:1.3;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}
.ehist-new__lnu{margin-top:2px;font-size:11.5px;font-weight:500;color:rgba(0,0,0,.45)}
.ehist-new__lnr{flex:none;margin-left:12px;text-align:right}
.ehist-new__lnp{font-size:13px;font-weight:800}
.ehist-new__lnl{font-size:11px;font-weight:500;color:rgba(0,0,0,.38);text-decoration:line-through}
/* 8 · Divider #E6E6E6 · 8 (:570-572). */
.ehist-new__rule{height:1px;margin:8px 0;background:#E6E6E6}

/* _sum (:679): v-pad 3; 13 w500 / 13 w600, bold 14 w800 / 15 w900. */
.ehist-new__sum{display:flex;align-items:center;padding:3px 0;color:#1A1A1A}
.ehist-new__sum.is-theme{color:var(--eh)}
.ehist-new__suml{flex:1;min-width:0;font-size:13px;font-weight:500}
.ehist-new__sumv{flex:none;font-size:13px;font-weight:600}
.ehist-new__sum.is-bold .ehist-new__suml{font-size:14px;font-weight:800}
.ehist-new__sum.is-bold .ehist-new__sumv{font-size:15px;font-weight:900}
/* "Counted" box (:584): 14 above, LTRB 14,10,14,10, themeColor 8%, radius 14
   (plain BorderRadius.circular — no squircle inflation). --eh8 is written by
   JS as rgba so old WebViews without color-mix still paint it. */
.ehist-new__cnt{margin-top:14px;padding:10px 14px;border-radius:14px;background:var(--eh8)}
/* EntryTicketImage(width 44): 44 × 27 (44 / (1038/637)), contain, left;
   error glyph confirmation_number_rounded at 0.65 × 27 = 17.55, centred. */
.ehist-new__tk44{
  flex:none;margin-left:6px;width:44px;height:27px;
  display:flex;align-items:center;justify-content:center;
}
.ehist-new__tk44 img{display:block;width:100%;height:100%;object-fit:contain;object-position:left center}
.ehist-new__tk44 svg{width:17.55px;height:17.55px;fill:var(--eh)}

/* ---- the order-details pop-up (_showOrderIdEntryDetails :8374) ------------
   Dialog: white, insetPadding h24 v40, Flutter's default black54 barrier,
   SmoothRectangleBorder(24, smoothing 1.0) → the fitted pair 55px +
   superellipse(2.2), fallback 27px; at most 80% of the screen tall. Above
   the history dialog (.ehist 200), under the photo viewer (.ehimg 212).
   max-width 472 = the 520px column minus the inset: the Dialog has no cap,
   but the mini never lays out wider than 520. */
.eho{
  position:fixed;inset:0;z-index:208;
  display:flex;align-items:center;justify-content:center;
  padding:40px 24px;
  background:rgba(0,0,0,.54);
}
.eho__card{
  width:100%;max-width:472px;max-height:min(80vh,100%);
  display:flex;flex-direction:column;overflow:hidden;
  background:#fff;border-radius:27px;
}
@supports (corner-shape: superellipse(2.2)){
  .eho__card{border-radius:55px;corner-shape:superellipse(2.2)}
}
.eho__scroll{
  flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
  padding:16px 12px 18px 20px;                                 /* LTRB 20,16,12,18 */
}

/* ==========================================================================
   NO-ENTRY RESULT PAGE for a VERIFIED order id (order_no_entry.js), a port of
   OrderIdNoEntryPage (lib/features/brandsSpecific/widgets/
   order_id_no_entry_page.dart, owner 2026-10-03). Full screen over
   everything, like the app's route; the receipt result's layer (z 95).
   ========================================================================== */
.one{
  position:fixed;inset:0;z-index:95;overflow-y:auto;-webkit-overflow-scrolling:touch;
  /* kReceiptResultBackground (batchReceiptResult.dart:92), stops evenly spaced. */
  background:linear-gradient(180deg,#FF6B6B 0%,#FFBD71 50%,#EEDC82 100%);
  color:#1A1A1A;font-family:var(--sans);
  animation:rrIn .2s ease-out both;
}
body.one-open{overflow:hidden}
/* SafeArea, then ONE block held together and centred a little above the
   middle: Spacer(2), 16, card, 20, actions, Spacer(3) (:177-189). It scrolls
   only when a long order makes it taller than the screen. */
.one__col{
  min-height:100%;display:flex;flex-direction:column;align-items:center;
  padding:env(safe-area-inset-top,0px) 0 env(safe-area-inset-bottom,0px);
}
.one__col::before{content:'';flex:2 1 0}
.one__col::after{content:'';flex:3 1 0}
/* cardWidth = w < 360 ? w - 40 : 325 (:166), the result page's card width. */
.one__card,.one__acts{flex:none;width:325px}
@media (max-width:359px){ .one__card,.one__acts{width:calc(100% - 40px)} }
/* `_card` (:262): white, SmoothRectangleBorder 28 @ smoothing 1, clipped. */
.one__card{margin-top:16px;background:#fff;overflow:hidden;border-radius:32px}
/* `_band` (:221): the accent at 14% over white (set as --one-band), padding
   LTRB 20,20,20,16; title red #E53935 20 w800 h1.25, 6 gap, description
   #1A1A1A 13.5 w500 h1.45, both centred. */
.one__band{padding:20px 20px 16px;background:var(--one-band);text-align:center}
.one__title{margin:0;color:#E53935;font-size:20px;font-weight:800;line-height:1.25}
.one__body{margin:6px 0 0;color:#1A1A1A;font-size:13.5px;font-weight:500;line-height:1.45}
/* OrderIdEntryDetails in Padding LTRB 20,16,20,20 (:279). */
.one__det{padding:16px 20px 20px}
/* `_actions` (:337): 20 above (:184), 16 below, two Expanded buttons 12 apart. */
.one__acts{margin-top:20px;padding-bottom:16px}
.one__row{display:flex;gap:12px}
.one__cell{flex:1 1 0;min-width:0}
/* `_resultButton` (:384): ElevatedButton, white, black type, min 52 tall, a
   14pt squircle, label 18 w700 (`_resultLabel` :437) scaled down to fit.
   Material 3 default elevation 1 while enabled (approximated with M3's
   level-1 shadow) and 0 when disabled; disabled type black38, or the sent
   green #1B8A4B. The waiting "ask" button's 2px accent side is drawn INSIDE
   the shape, as Flutter strokes a shape's side, so the height never moves. */
.one__btn{
  width:100%;min-height:52px;padding:0 10px;border:0;border-radius:16px;
  display:flex;align-items:center;justify-content:center;gap:6px;
  background:#fff;color:#000;font:inherit;font-size:18px;font-weight:700;line-height:1.2;
  white-space:nowrap;cursor:pointer;
  box-shadow:0 1px 2px rgba(0,0,0,.3),0 1px 3px 1px rgba(0,0,0,.15);
}
.one__btn:disabled{cursor:default;color:rgba(0,0,0,.38);box-shadow:none}
.one__btn.is-done:disabled{color:#1B8A4B}
.one__btn--ask.is-waiting{
  box-shadow:inset 0 0 0 2px var(--one-accent),0 1px 2px rgba(0,0,0,.3),0 1px 3px 1px rgba(0,0,0,.15);
}
/* Disabled while still waiting (today's report limit): elevation 0, but the
   side is part of the shape, so the ring stays (`border:` only drops once
   sending or done, :392). */
.one__btn--ask.is-waiting:disabled{box-shadow:inset 0 0 0 2px var(--one-accent)}
.one__ok{flex:none;width:20px;height:20px;fill:currentColor}   /* check_circle_rounded 20, 6 gap */
/* CircularProgressIndicator 22, stroke 2.4, black (:405). */
.one__spin{
  width:22px;height:22px;border-radius:50%;box-sizing:border-box;
  border:2.4px solid transparent;border-top-color:#000;border-right-color:#000;
  animation:oneSpin .8s linear infinite;
}
@keyframes oneSpin{to{transform:rotate(360deg)}}
/* The pulse on "ask for a check" while it waits (:71-88, :420): scale 1 to
   1.05 and back, 900ms easeInOut, reversing, with a campaign-colour glow
   breathing with it (accent .18 + .32 g, blur 6 + 12 g, spread .5 + 1.5 g;
   the two ends are --one-g0 / --one-g1). Stops for good once tapped; never
   runs under reduced motion. */
.one__pulse{border-radius:16px}
.one__pulse.is-on{animation:onePulse .9s ease-in-out infinite alternate}
@keyframes onePulse{
  from{transform:scale(1);box-shadow:0 0 6px .5px var(--one-g0)}
  to{transform:scale(1.05);box-shadow:0 0 18px 2px var(--one-g1)}
}
@media (prefers-reduced-motion: reduce){ .one__pulse.is-on{animation:none} }
/* The outcome under the buttons: 10 gap, then a slot ALWAYS 40 tall so a tap
   never shifts the centred block; 13 w600 h1.4, black 72%, 2 lines (:367). */
.one__note{
  margin:10px 0 0;height:40px;overflow:hidden;text-align:center;
  font-size:13px;font-weight:600;line-height:1.4;color:rgba(0,0,0,.72);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
}
/* figma_squircle: R x 2.29 with superellipse(2.2) where drawn, R x 1.14 above. */
@supports (corner-shape: superellipse(2.2)){
  .one__card{border-radius:64px;corner-shape:superellipse(2.2)}
  .one__btn,.one__pulse{border-radius:32px;corner-shape:superellipse(2.2)}
}

/* ── no LINE mini app: the "open in your browser" screen (app.js) ──────────
   Owner, 2026-10-05: the campaign runs only on the website. Inside LINE every
   page is covered by this one card on the result screen's gradient. */
.leave-line{
  position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;
  padding:24px;background:linear-gradient(to bottom,#FF6B6B 0%,#FFBD71 50%,#EEDC82 100%);
}
body.leave-line-open{overflow:hidden}
.leave-line__card{
  width:100%;max-width:360px;box-sizing:border-box;padding:28px 24px 24px;
  background:#fff;border-radius:32px;text-align:center;color:#1A1A1A;
  box-shadow:0 18px 50px rgba(0,0,0,.15);
}
@supports (corner-shape: superellipse(2.2)){
  .leave-line__card{border-radius:64px;corner-shape:superellipse(2.2)}
}
.leave-line__logo{height:44px;width:auto;margin:0 auto 18px;display:block}
.leave-line h1{margin:0 0 8px;font-size:22px;font-weight:900;line-height:1.25}
.leave-line p{margin:0 0 22px;font-size:14px;line-height:1.5;color:#6B7280}
.leave-line__go,.leave-line__copy{
  display:block;width:100%;border:0;cursor:pointer;font:inherit;
  -webkit-tap-highlight-color:transparent;
}
.leave-line__go{height:52px;border-radius:16px;background:#E8504F;color:#fff;font-size:17px;font-weight:800}
.leave-line__copy{margin-top:10px;height:44px;border-radius:14px;background:#F5F6F8;color:#1A1A1A;font-size:15px;font-weight:700}
