/* ========================================================================
   Odic — design tokens
   ======================================================================== */
:root{
	--odic-bg:#faf8ef;
	--odic-text:#1c1c1e;
	--odic-text-soft:#2c2c2c;
	--odic-muted:#888888;
	--odic-border:#eeeeee;
	--odic-pill:#eeeeee;
	--odic-pill-active:#009edf;
	--odic-accent:#007aff;
	--odic-dark:rgba(0,0,0,.64);
	--odic-gradient-warm:linear-gradient(90deg,#d8ebdd 0%,#fff3ca 100%);
	--odic-radius-pill:50px;
	--odic-radius-lg:32px;
	--odic-radius-md:16px;
	--odic-shadow-soft:0 0 26px rgba(0,0,0,.08);
	--odic-container:1216px;
	--odic-font:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
	/* Product-detail-page-only tokens (assets/css/main.css .odic-pdp-*
	   rules) — a warmer, slightly darker cream than --odic-bg/--odic-border
	   for the alternating section bands and their borders, distinct enough
	   from the rest of the site's flat --odic-bg to read as visual rhythm
	   without introducing a whole new color into the palette. */
	--odic-pdp-bg-alt:#f0ead9;
	--odic-pdp-border:#e6e2d4;
}

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-tap-highlight-color:transparent}
a,button{-webkit-tap-highlight-color:transparent}
body{
	margin:0;
	/* +env(safe-area-inset-top): the fixed header is now that much
	   taller on notch devices too (see .odic-header) — this has to grow
	   by the same amount so scrolled content still starts right below
	   the header instead of partly hidden under it. 0 everywhere else. */
	padding-top:calc(48px + env(safe-area-inset-top));
	font-family:var(--odic-font);
	/* Bug fixed: this was #fff — every themed section paints its own
	   background on top so it went unnoticed, but any page/section with
	   no explicit background of its own (WooCommerce's default cart,
	   checkout, my-account templates) fell through to plain white instead
	   of the site's cream. */
	background:var(--odic-bg);
	color:var(--odic-text);
	-webkit-font-smoothing:antialiased;
	line-height:1.5;
	/* Bug fixed: on short pages (the lean Order page especially) the
	   footer just sat wherever the content happened to end, leaving a
	   gap of bare page background below it before the real bottom of the
	   viewport. min-height:100vh + flex column here, with .odic-footer's
	   own margin-top:auto below, means the footer now always reaches the
	   actual bottom of the viewport regardless of how little content is
	   above it — and on tall pages this has no effect at all, since
	   margin-top:auto only pushes the footer down as far as there's free
	   space left to consume. .odic-header is position:fixed (taken out
	   of normal flow already) so it isn't affected by becoming a flex
	   item here. 100dvh alongside 100vh accounts for mobile browser
	   chrome (address bar) resizing the *visual* viewport without a
	   matching resize of the older 100vh unit, on browsers that support it.
	*/
	display:flex;flex-direction:column;
	min-height:100vh;min-height:100dvh;
}
img{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
button{font-family:inherit;cursor:pointer}
.odic-container{max-width:var(--odic-container);margin:0 auto;padding:0 24px}
.screen-reader-text{position:absolute;left:-9999px}

/* ========================================================================
   Buttons
   ======================================================================== */
/* height:48px explicit (not just padding+line-height, which drifts a
   few px depending on the element — a <button> and an <a> landed at
   different heights off the same padding, found while unifying every
   pill button sitewide to this one height on request) so every
   .odic-btn consumer comes out exactly the same height regardless of
   tag or nested content. */
.odic-btn{
	display:inline-flex;align-items:center;justify-content:center;
	height:48px;box-sizing:border-box;padding:0 32px;border-radius:var(--odic-radius-pill);
	font-size:16px;font-weight:500;border:none;white-space:nowrap;
	transition:transform .15s ease,opacity .15s ease;
}
.odic-btn:hover{opacity:.88}
.odic-btn:active{transform:scale(.97)}
.odic-btn--dark{background:#1a1a1a;color:#fff;box-shadow:var(--odic-shadow-soft)}
.odic-btn--light{background:var(--odic-pill);color:var(--odic-text)}
.odic-btn--blue{
	background:var(--odic-accent);color:#fff;
	background-image:linear-gradient(-4deg,rgba(127,177,235,.6) 2%,rgba(174,199,245,0) 17%,rgba(219,239,255,.5) 87%);
}
.odic-btn--outline{
	background:transparent;border:1px solid #2c2c2c;color:#2c2c2c;
	border-radius:24px;
}
.odic-btn--hero-dark{background:#333;color:#fff;border-radius:24px}
.odic-btn--block{width:100%}
/* Shrunk site-wide on mobile, on request — the full desktop-size 48px
   pills read as oversized/competing for attention on a phone screen
   (first flagged on the hero, where the tablet mockup is meant to be
   the star, not the buttons). Went through a couple of passes (36px,
   then 44px matching iOS's own HIG tap-target minimum) before settling
   on 40px on request — an actual 8px step (44 wasn't), still close to
   Apple's real mobile pill height, still clearly smaller than desktop's
   48px. .odic-pay-btn (the separate blue "Order now" pill class used on
   Learn More/PDP) gets the same 40px treatment right below, for the
   same reason and to look consistent with .odic-btn everywhere both
   appear.
   Excluded on purpose: the checkout "Place order" submit and cart
   "Apply coupon" buttons (below) — those are real transactional commits,
   not marketing/nav pills, and shrinking a pay button hurts tap-target
   accuracy and conversion, not just aesthetics. Desktop is untouched. */
@media (max-width:640px){
	.odic-btn{height:40px;padding:0 22px;font-size:15px}
	.odic-pay-btn{height:40px;padding:0 22px;font-size:15px}
}
@media (max-width:360px){
	.odic-btn{padding:0 18px}
	.odic-pay-btn{padding:0 18px}
}
@media (max-width:640px){
	.odic-checkout__place-order-btn,.odic-cart-coupon__btn{height:48px;padding:0 32px;font-size:16px}
}

/* ========================================================================
   Header
   ======================================================================== */
/* Bug fixed: on notch/Dynamic-Island iPhones (Safari, with the
   viewport-fit=cover meta tag this now needs — see header.php), the
   status bar/island area sits ABOVE where this fixed header's own
   48px-tall box started, so the page's own content (the hero heading)
   could show through underneath it there instead of the header's glass
   background covering it. padding-top:env(safe-area-inset-top) grows
   the header UPWARD by exactly that inset (0 on every device without
   one, so nothing changes elsewhere) without moving .odic-header__inner
   or anything in it — that child is still its own normal 48px, just
   pushed down by the new padding, so the logo/nav/cart keep the exact
   position they already had; only the glass background (inset:0 on
   this box) extends to actually cover the taller total area now. */
.odic-header{
	position:fixed;top:0;left:0;right:0;z-index:100;
	padding-top:env(safe-area-inset-top);
}
/*
 * The blur/glass layer is a separate sibling from the nav content on
 * purpose: backdrop-filter creates an isolated compositing context, so
 * if the mix-blend-mode text lived inside the filtered element it could
 * only "see" that element's own near-transparent background — never the
 * real page behind it — which is why it rendered permanently white.
 * Keeping .odic-header__inner outside the filter lets its blend-mode
 * text read the actual scrolled content behind the glass.
 */
/* Bug fixed: rgba(255,255,255,.01) here relied entirely on backdrop-
   filter:blur to read as anything at all — on a browser where that
   filter silently no-ops (reported: content from further down the page
   showing through crisp and unblurred, right in the status-bar/Dynamic-
   Island strip above the nav row — "苹果手机的浏览器这里是露出来的，是
   灵动岛的位置"), this near-zero alpha is effectively just "invisible",
   nothing covering that area at all. Exact same failure mode already
   diagnosed and fixed once on .odic-header__nav-menu below (that fix's
   own comment has the full story) — same remedy applies here: bumped to
   .75, still genuinely translucent/frosted wherever backdrop-filter does
   work, but opaque enough on its own that the safe-area strip (and the
   rest of the header) stays covered even where it doesn't. */
.odic-header__glass{
	position:absolute;inset:0;z-index:0;
	background:rgba(255,255,255,.75);
	backdrop-filter:blur(18px);
	-webkit-backdrop-filter:blur(18px);
	box-shadow:
		0 0 26px rgba(0,0,0,.1),
		inset 0 1px 0 rgba(255,255,255,.8),
		inset 0 -1px 0 rgba(255,255,255,.5);
}
.odic-header__inner{
	position:relative;z-index:1;
	width:100%;padding:0 32px;
	height:48px;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
}
.odic-nav{grid-column:2;display:flex;align-items:center;gap:64px;justify-self:center}
.odic-nav a,.odic-cart,.odic-login-link{
	color:var(--odic-text);
}
.odic-nav a{
	position:relative;font-size:16px;
	padding-bottom:4px;display:inline-flex;align-items:center;
}
.odic-nav a::after{
	content:"";position:absolute;left:50%;bottom:0;width:3px;height:3px;
	border-radius:50%;background:currentColor;opacity:0;transform:translateX(-50%);
}
.odic-nav a:hover,.odic-nav a.is-active{font-weight:600}
.odic-nav a.is-active::after{opacity:.6}
/* Home (logo) and Cart are icon swap (solid/hollow), not the dot+bold treatment.
   Selector must include ".odic-nav a" to out-specificity the ".odic-nav a::after"
   rule above — a bare ".odic-nav__home::after" loses that fight and the dot
   silently keeps rendering even with content:none. */
.odic-nav a.odic-nav__home::after,.odic-nav a.odic-cart::after{content:none}
.odic-nav__home:hover,.odic-nav__home.is-active,.odic-cart:hover,.odic-cart.is-active{font-weight:400}
.odic-cart{position:relative}
.odic-cart img{display:block}
.odic-cart__count{
	position:absolute;top:-6px;right:-8px;background:var(--odic-accent);color:#fff;
	font-size:10px;line-height:1;padding:3px 5px;border-radius:10px;font-weight:700;
}
.odic-nav__home img{display:block;height:16px;width:16px}
/* Mobile header: Home on the left; Web App and Cart on the right. */
@media (max-width:860px){
	.odic-header__inner{display:flex;align-items:center;gap:24px}
	.odic-nav{flex:1;display:flex;align-items:center;gap:24px;justify-self:unset}
	.odic-nav a,.odic-nav a::after{display:none}
	.odic-nav a.odic-nav__home,
	.odic-nav a.odic-nav__webapp,
	.odic-nav a.odic-cart{display:inline-flex}
	.odic-nav a.odic-nav__home{margin-right:auto}
}

/* ========================================================================
   Order-status ticker — a second header row shown when the current
   visitor has a recent order (odic_get_tracked_order() in functions.php,
   markup in header.php), modeled on the Figma reference (node 2633:1636
   collapsed, node 2616:2117 expanded) on request. Same proven pattern as
   .odic-mobile-nav just above: a normal-flow child of the fixed
   .odic-header, so its box growing on expand doesn't reflow body content
   (position:fixed elements never do) — it just overlays whatever's
   underneath, and .odic-header__glass (inset:0) auto-covers whatever
   height .odic-header ends up at. Same reason the open/close state has
   to be a class-triggered @keyframes animation rather than a transition
   — see the Chromium-bug writeup on .odic-header.is-open .odic-mobile-nav
   above; a transition here would hit the exact same "stuck at pre-toggle
   value" bug.
   ======================================================================== */
body.has-order-bar{padding-top:96px}

/* Centred like .odic-header__inner's own 1fr/auto/1fr nav row just above
   it (same grid trick) — was a plain left-anchored flex row before, which
   read as lopsided/ugly with a big empty gap before the far-right
   control ("折叠栏中的信息你居中吧，左对齐太丑"). The kebab-dot cluster
   on the right is the Figma reference's own control there (node
   2633:1636) — not an expand/collapse chevron, which was a guess that
   didn't match ("你做的也不像呀，你照抄我的即可"); the whole row is
   still one click target regardless of which part gets clicked. */
.odic-order-bar{
	position:relative;z-index:1;height:48px;padding:0 32px;
	display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
	cursor:pointer;user-select:none;
}
.odic-order-bar__content{
	grid-column:2;display:flex;align-items:center;gap:10px;
	min-width:0;max-width:100%;white-space:nowrap;
}
/* Bug fixed twice: sizing the two source SVGs' bounding boxes to look
   equal ("两个图标不一样大小...还是不一样大") kept failing because
   each asset had a different amount of built-in canvas padding around
   its actual artwork, so no width/height pair could equalize them —
   compensating in CSS was chasing the wrong problem. Fixed at the
   source instead ("不行你删了重新做"): both icon-3dbox.svg and
   icon-truck.svg were redrawn from scratch sharing one 24×24 viewBox
   with matched ink coverage (~75%/~72% of the canvas height), so they
   can now just take the same box dimensions here and actually read as
   the same size. */
.odic-order-bar__icon{width:20px;height:20px;flex:none;opacity:.75}
.odic-order-bar__number{font-size:15px;font-weight:500;color:var(--odic-text);white-space:nowrap;flex:none}
/* Frosted glass chip — same material as .odic-header__glass, matched to
   the Figma reference's own copy-button pill (node 2633:1636: a 40x24
   blurred #e6e6e6 chip with a soft inset shadow), not a plain flat icon
   button. Standalone control — stopPropagation in main.js keeps it from
   also toggling the row open/closed. */
/* Shared frosted-glass pill — the collapsed bar's own copy button and
   the DELIVERY panel's tracking-number copy button are the exact same
   component in Figma too (both reuse "Rectangle 3731" + "Vector"), just
   at two different real sizes (34×22 vs 40×24), so the visual treatment
   lives here once instead of being duplicated per instance. */
.odic-order-bar__copy,
.odic-order-panel__tracking-copy{
	flex:none;display:inline-flex;align-items:center;justify-content:center;
	border-radius:50px;
	background:rgba(230,230,230,.65);border:1px solid #eee;
	backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);
	box-shadow:0 -.5px .5px rgba(0,0,0,.1),0 2px 2px rgba(0,0,0,.15),inset 0 -3px 6px rgba(0,0,0,.12);
}
.odic-order-bar__copy:hover,
.odic-order-panel__tracking-copy:hover{background:rgba(222,222,222,.8)}
.odic-order-bar__copy img,
.odic-order-panel__tracking-copy img{width:12px;height:12px;display:block}
.odic-order-bar__copy{width:34px;height:22px}
.odic-order-bar__copy.is-copied img,
.odic-order-panel__tracking-copy.is-copied img{opacity:.35}
.odic-order-bar__dot{width:3px;height:3px;border-radius:50%;background:#ccc;flex:none}
/* Bug fixed: height:20px;width:auto here lost to WooCommerce's own
   `.woocommerce-page img{height:auto;max-width:100%}` (class+type
   selector beats this rule's single class on specificity), so the
   truck silently fell back to its native 24×24 size — invisible in the
   isolated test page used to verify the icon redraw (no WooCommerce CSS
   loaded there to conflict with), only caught by checking the real
   header. Both icon SVGs now share one square 24×24 canvas (see
   .odic-order-bar__icon's comment), so fixing width AND height here,
   same as the box icon, sidesteps the auto-vs-auto fight entirely
   instead of trying to out-specificity a WooCommerce core rule. */
.odic-order-bar__truck{width:20px;height:20px;flex:none;opacity:.75}
.odic-order-bar__status{font-size:14px;font-weight:600;white-space:nowrap;flex:none}
.odic-order-bar__eta{font-size:14px;color:var(--odic-text-soft);white-space:nowrap;flex:none}
.odic-order-bar__eta-days{color:var(--odic-muted);font-weight:400}
/* "•••" — a static affordance (not interactive; the row's own click
   handles everything, see main.js), matched to the 3 small dots the
   Figma reference pins to the far right of this row. */
.odic-order-bar__menu{grid-column:3;justify-self:end;display:flex;align-items:center;gap:3px;flex:none}
.odic-order-bar__menu span{width:3px;height:3px;border-radius:50%;background:#999}

.odic-order-detail{
	position:relative;z-index:1;
	max-height:0;overflow:hidden;opacity:0;
}
.odic-header.is-order-open .odic-order-detail{
	max-height:800px;opacity:1;
	animation:odicOrderDetailIn .32s cubic-bezier(.34,1.56,.64,1) forwards;
}
@keyframes odicOrderDetailIn{
	from{transform:translateY(-6px);}
	to{transform:translateY(0);}
}
/* Row of fixed-size (416×256, the Figma reference's own card size)
   panels, centred as a group — not stretched into a responsive N-column
   grid, on request ("看我做的这个尺寸...1比1还原"). 56px gap is the real
   Figma spacing between the 4 panel frames (measured centre-to-centre
   in the node 2616:2117 layout: 988−516=472=416+56, etc.) — corrected
   from a guessed 16px ("间距好像不对。四个看板间距是56px"). Narrower
   than the full 4-panel row (4×416+3×56=1832px) needs to scroll rather
   than squeeze, same swipe-not-shrink approach as the collapsed bar
   above. */
.odic-order-detail__row{
	display:flex;gap:56px;justify-content:center;flex-wrap:wrap;
	max-width:1832px;margin:0 auto;padding:20px 24px 28px;
	box-sizing:content-box;
}
.odic-order-panel{
	position:relative;flex:none;width:416px;height:256px;
	border-radius:18px;overflow:hidden;box-sizing:border-box;
}
.odic-order-panel--order{background:#fff}
.odic-order-panel--placeholder{background:#f6f5f0}

/* ORDER panel — every element positioned to match the Figma reference's
   own absolute coordinates within its 416×256 frame (node 2616:2119)
   exactly, rather than an auto-flow approximation. */
.odic-order-panel__bg{position:absolute;inset:0;overflow:hidden;pointer-events:none}
/* Fade is a plain white gradient painted OVER the photo+glow, not a CSS
   mask — a mask-image gradient here (mathematically the same fade
   Figma's own mask shape produces: fully transparent up to 40.02% of
   the card's width, ramping to 62%-opacity from there to the right
   edge, the "渐隐" effect requested) rendered unreliably in testing —
   most of the photo came out fully invisible instead of partially
   faded, even with mask-size/position matched exactly to spec. Fading
   to the card's own white via an overlay achieves the same *look* (this
   card is solid white, so "faded to X% transparent" and "faded to X%
   opaque white" are the same pixels) through a plain background
   gradient instead, which every browser agrees on. */
/* Bug fixed: the rotated device's own screen-to-bezel edge (a real,
   sharp line on the source asset — confirmed clean, no baked-in shadow)
   was landing right at the card's top/bottom edges, close enough to
   read as a stray grey diagonal smudge against the plain white corners
   there ("这两个地方是灰色的，我做的是白色的...就是平白以外的地方都是
   白色"). Layering a second, vertical fade on top of the existing
   horizontal one keeps the device visible only in a safe central band,
   clear of the card's own top/bottom edges, rather than trying to
   re-aim the rotation/crop to dodge that line exactly. */
.odic-order-panel__bg::after{
	content:"";position:absolute;inset:0;
	background:
		linear-gradient(to bottom,#fff 0,transparent 18%,transparent 82%,#fff 100%),
		linear-gradient(to right,#fff 0,#fff 40.02%,rgba(255,255,255,.38) 100%);
}
/* The decorative background is the SAME .odic-device-card component the
   homepage hero already uses (front-page.php — real Figma exports for
   the tablet frame and all 11 task bubbles), not a separate hand-built
   photo+mask+glow approximation — that approximation was exactly the
   mismatch flagged ("泡泡上有素材，图片或者文字，不要自己生成，要抄我
   作业"). Sized to roughly the same footprint the earlier crop used
   (612:370 is coincidentally almost the same ratio as that crop,
   293.967:177.812), rotated -15° and positioned the same way, then
   faded left-to-right via .odic-order-panel__bg::after above — .odic-
   device-card itself is untouched, only wrapped/rotated/sized here. */
.odic-order-panel__bg-wrap{
	position:absolute;left:140px;top:4.04px;width:329.972px;height:247.837px;
	display:flex;align-items:center;justify-content:center;
}
/* Bug fixed: .odic-device-card's own drop-shadow(0 30px 60px rgba(0,0,
   0,.18)) — right for a large floating hero device, wrong here, where
   it bled a soft grey haze past the rotated device's own edges onto the
   panel's plain white corners ("这两个地方是灰色的，我做的是白色的，是
   不是放阴影了"). filter:none on the single .odic-order-panel__device
   class was overridden anyway: equal specificity to .odic-device-card's
   own rule, and that one sits later in the file, so it won the source-
   order tiebreak regardless of a `filter:none` here. The compound
   selector below (both classes, higher specificity than either alone)
   wins outright instead of depending on where either rule happens to
   sit in the file. */
.odic-order-panel__device{width:293.967px;flex:none;transform:rotate(-15deg)}
.odic-device-card.odic-order-panel__device{filter:none}

.odic-order-panel__icon{position:absolute;left:24px;top:16px;width:32px;height:32px}
.odic-order-panel__title{
	position:absolute;left:72px;top:32px;transform:translateY(-50%);margin:0;
	font-size:18px;font-weight:600;color:var(--odic-text);
}
.odic-order-panel__item-name{
	position:absolute;left:72px;top:92px;transform:translateY(-50%);margin:0;
	font-size:16px;font-weight:400;color:var(--odic-text);white-space:nowrap;
}
.odic-order-panel__item-name span{color:var(--odic-muted);font-size:14px}
.odic-order-panel__item-extra{margin-left:6px;color:var(--odic-accent);font-size:13px}
.odic-order-panel__item-meta{
	position:absolute;left:72px;top:127px;transform:translateY(-50%);margin:0;
	font-size:14px;font-weight:500;color:var(--odic-muted);
}
.odic-order-panel__item-price{
	position:absolute;right:32px;top:127px;transform:translateY(-50%);margin:0;
	font-size:14px;font-weight:500;color:var(--odic-muted);white-space:nowrap;
}
.odic-order-panel__coupon{
	position:absolute;left:72px;top:156px;height:16px;box-sizing:border-box;
	display:inline-flex;align-items:center;
	background:#dedeb0;color:#244008;border-radius:30px;padding:0 10px;
	font-size:12px;font-weight:400;white-space:nowrap;
}
.odic-order-panel__discount{
	position:absolute;right:32px;top:164px;transform:translateY(-50%);margin:0;
	font-size:14px;font-weight:400;color:#244008;white-space:nowrap;
}
.odic-order-panel__total-label{
	position:absolute;left:72px;top:217px;transform:translateY(-50%);margin:0;
	font-size:18px;font-weight:500;color:#2c2c2c;
}
.odic-order-panel__total-value{
	position:absolute;right:32px;top:217px;transform:translateY(-50%);margin:0;
	font-size:18px;font-weight:500;color:#2c2c2c;white-space:nowrap;
}

@media (max-width:1832px){
	.odic-order-detail__row{justify-content:flex-start;overflow-x:auto;-webkit-overflow-scrolling:touch}
}

/* PAYMENT panel — 1:1 rebuild of the Figma reference (node 2616:2201),
   same fixed-position convention as ORDER. */
.odic-order-panel--payment{background:#fff}
.odic-order-panel__payment-line{
	position:absolute;left:72px;top:92px;transform:translateY(-50%);margin:0;
	font-size:16px;font-weight:400;color:var(--odic-text);white-space:nowrap;
}
.odic-order-panel__brand-chip{
	position:absolute;right:32px;top:76px;width:32px;height:32px;box-sizing:border-box;
	background:#fcfcfc;border:1px solid #f8f8f8;border-radius:8px;overflow:hidden;
}
.odic-order-panel__brand-chip img{position:absolute;inset:3px;width:26px;height:26px;object-fit:contain}
.odic-order-panel__paid-label{
	position:absolute;left:72px;top:137px;transform:translateY(-50%);margin:0;
	font-size:14px;font-weight:500;color:var(--odic-muted);line-height:20px;
}
.odic-order-panel__receipt-link{
	position:absolute;right:32px;top:219px;transform:translateY(-50%);
	font-size:14px;font-weight:400;color:var(--odic-muted);white-space:nowrap;
}
.odic-order-panel__receipt-link:hover{color:var(--odic-accent)}

/* DELIVERY panel — 1:1 rebuild of the Figma reference (node 2616:2167),
   same fixed-position convention as ORDER/PAYMENT. #eee background per
   spec (unlike the two white panels before it). */
.odic-order-panel--delivery{background:#eee}
/* object-fit:contain added — icon-truck.svg's own canvas changed from a
   27:21.6 aspect (matching this exact Figma-verified 27×21.6 box) to a
   square 24×24 one when it was redrawn for the order-bar icon-size fix
   (shared asset, header.php). Without this, the new square artwork would
   get stretched to fill this non-square box instead of just fitting
   inside it — same Figma position/footprint, undistorted icon. */
.odic-order-panel--delivery .odic-order-panel__icon{left:27px;top:21px;width:27px;height:21.6px;object-fit:contain}
.odic-order-panel__customer-name{
	position:absolute;left:72px;top:92px;transform:translateY(-50%);margin:0;
	font-size:16px;font-weight:400;color:var(--odic-text);white-space:nowrap;
}
.odic-order-panel__eta-days{
	position:absolute;right:32px;top:92px;transform:translateY(-50%);margin:0;
	font-size:16px;font-weight:400;color:var(--odic-text);white-space:nowrap;
}
.odic-order-panel__address{
	position:absolute;left:72px;top:137px;transform:translateY(-50%);margin:0;
	font-size:14px;font-weight:500;color:var(--odic-muted);line-height:20px;
}
.odic-order-panel__country{
	position:absolute;right:32px;top:147px;transform:translateY(-50%);margin:0;
	font-size:14px;font-weight:400;color:var(--odic-muted);white-space:nowrap;
}
.odic-order-panel__tracking-label{
	position:absolute;left:72px;top:183px;transform:translateY(-50%);margin:0;
	font-size:18px;font-weight:500;color:#2c2c2c;
}
.odic-order-panel__tracking-value{
	position:absolute;left:72px;top:219px;transform:translateY(-50%);margin:0;
	font-size:14px;font-weight:400;color:var(--odic-muted);white-space:nowrap;
	max-width:260px;overflow:hidden;text-overflow:ellipsis;
}
.odic-order-panel__tracking-copy{position:absolute;right:32px;top:207px;width:40px;height:24px}

/* ORDER STATUS panel — 1:1 rebuild of the Figma reference (node
   2616:2182). A genuinely scrollable progress list under a sticky-
   overlay header, not a static composition with a faked translucent top
   ("这是一个根据用户当前订单状态的进展图，可以上下滑动查看不同的进展，
   默认显示当前进展" / "不是模糊效果，请看设计稿" — the previous version
   tried to fake the peek-through look with an always-static layout and
   it read as a smear instead of real scrolled content). Every step sits
   at a fixed 80px cadence (84/164/244…, set inline per step in
   header.php) regardless of which is "current" — main.js scrolls the
   viewport on load so the current step (the last done=true one,
   $od['timeline_current']) lands in the slot right under the header,
   which is what produces Figma's exact 5/84/164 numbers for whichever
   step that happens to be (confirmed: for "Shipped" as current, this
   scroll math reproduces those numbers exactly, not approximately). */
/* Bug fixed: was a flat #eee (a cold, pure grey) — every other panel/
   surface in this design, including the status header's own FCFCFB→
   FBFAF9 tint gradient right below, is a warm near-white. Blurring
   scrolled content against that cold grey backdrop is what read as
   "dirty" rather than premium ("这个地方不是模糊，怎么感觉这么脏 没
   有那种高级感呢") — the frosted bands were tinting the header gradient
   toward grey instead of blending into a clean warm-white frost. */
.odic-order-panel--status{background:#fcfcfb}
.odic-order-panel__timeline-viewport{
	position:absolute;inset:0;overflow-y:auto;overflow-x:hidden;
	-webkit-overflow-scrolling:touch;scrollbar-width:none;-ms-overflow-style:none;
}
.odic-order-panel__timeline-viewport::-webkit-scrollbar{display:none}
.odic-order-timeline{position:relative;list-style:none;margin:0;padding:0;height:340px}
.odic-order-timeline__step{position:absolute;left:0;width:100%;height:1px}
/* Connecting line between steps — not on the last one. */
.odic-order-timeline__step::after{
	content:"";position:absolute;left:40px;top:24px;width:1px;height:55px;background:#ddd;
}
.odic-order-timeline__step:last-child::after{display:none}
.odic-order-timeline__icon{position:absolute;left:28px;top:0;width:24px;height:24px;display:block}
.odic-order-timeline__dot{position:absolute;left:16px;top:10px;width:4px;height:4px;display:block}
.odic-order-timeline__label{
	position:absolute;left:72px;top:12px;transform:translateY(-50%);margin:0;
	font-size:16px;font-weight:400;color:var(--odic-text);white-space:nowrap;
}
.odic-order-timeline__date{
	position:absolute;left:72px;top:47px;transform:translateY(-50%);margin:0;
	font-size:14px;font-weight:400;color:var(--odic-muted);white-space:nowrap;
}
/* Sticky overlay header — a frosted fade strip that sits ON TOP of the
   scrollable timeline (not part of its scroll flow), matching Figma's
   own layering (it sits later in the layer stack, i.e. painted above
   step content) — this is what makes a scrolled-behind step read as
   genuinely peeking through translucency instead of a flat blur. */
/* Real Figma layer values (given directly): 416×64px, fill gradient
   FCFCFB at 62% opacity fading to FBFAF9 at 0%, Background blur START 8
   / END 0. A single backdrop-filter:blur() is one fixed radius for the
   whole element — it can't itself vary from 8px to 0px — so that 8→0
   falloff is approximated as stacked bands (odic-order-panel__status-
   blur, header.php), each blurring only its own slice at a decreasing
   radius, sitting under this element's own tint gradient. Bug fixed: 4
   bands (16px each) left a visible seam where two adjacent blur radii
   met, most obvious wherever text happened to scroll behind that exact
   step ("这里为何有条看不清的横杠呢...当文字在下方经过时会有很明显的
   横杠") — now 16 thinner bands (4px each, positioned/sized inline per
   band in header.php), small enough steps to read as a smooth falloff
   instead of a banded one. */
.odic-order-panel__status-header{
	position:absolute;left:0;top:1px;width:416px;height:64px;overflow:hidden;pointer-events:none;
	background:linear-gradient(to bottom,rgba(252,252,251,.62) 0%,rgba(251,250,249,0) 100%);
}
.odic-order-panel__status-blur{position:absolute;left:0;width:100%}
.odic-order-panel--status .odic-order-panel__icon{position:absolute;left:24px;top:15px;width:32px;height:32px}
.odic-order-panel--status .odic-order-panel__title{left:72px;top:31px}

/* Narrow screens: nothing gets hidden, it swipes — the full summary
   (icon → order# → copy → status → eta) becomes one horizontally
   scrollable strip instead of the row trying to fit or drop pieces
   ("手机端短就横划查看更多简要"). Switches the bar from the centred
   grid to a plain flex row (same grid→flex swap .odic-header__inner
   itself already does at its own mobile breakpoint) since a scrolling
   region needs to be a flexible track, not a centred auto column — the
   kebab dots stay pinned outside the scroll area. Tapping to open the
   detail view still works the same everywhere; a real swipe gesture
   doesn't fire a click on release, so the two never conflict. */
@media (max-width:640px){
	body.has-order-bar{padding-top:80px}
	.odic-order-bar{
		height:32px;padding:0 12px;gap:6px;
		display:flex;align-items:center;
	}
	.odic-order-bar__content{
		flex:1;min-width:0;max-width:none;gap:6px;
		overflow-x:auto;-webkit-overflow-scrolling:touch;
		scrollbar-width:none;-ms-overflow-style:none;
	}
	.odic-order-bar__content::-webkit-scrollbar{display:none}
	/* Scaled down again on request ("手机端的胶囊还是太大，小点") — the
	   40px-tall bar above was already a reduction from desktop's 48px,
	   but still read as oversized on an actual phone; icons/text/the copy
	   chip all step down together here so it stays one coherent small
	   capsule instead of just a shorter bar around full-size contents.
	   !important on the icons: WooCommerce's own `.woocommerce-page
	   img{height:auto}` outranks a plain single-class selector on
	   specificity (the desktop-size fix only reads correctly without it
	   because both icons happen to be drawn on a 1:1 square canvas, which
	   makes WC's auto-height-from-fixed-width math land on the same
	   number anyway) — forcing it here is the real override, not a
	   coincidence riding on that aspect ratio. */
	.odic-order-bar__icon,
	.odic-order-bar__truck{width:16px!important;height:16px!important}
	.odic-order-bar__number,
	.odic-order-bar__status{font-size:13px}
	.odic-order-bar__copy{width:28px;height:18px}
	.odic-order-bar__copy img{width:10px;height:10px}

	/* Order-detail popup on phones — was a horizontal-scroll row, but a
	   phone user's natural swipe on it is VERTICAL, and that gesture was
	   reaching the page underneath instead of the popup, since nothing
	   here captured vertical touch input ("当前手势是给底部的页面用的
	   ...即便你在弹窗里滑动，他也不动"). Switches to a vertically-
	   stacked, vertically-scrollable list instead — the natural swipe
	   direction now matches the popup's own scroll axis. Also capped to
	   ~1.2 cards tall (not the full 4-card list) so a sliver of the next
	   card peeks in as a "there's more, scroll" hint and real page
	   content stays visible/tappable below it to dismiss
	   ("这个窗口太高了，应该只显示1.2个卡片...点击下部任意空白处就可以
	   取消弹窗") — the existing outside-click handler in main.js already
	   closes the popup on any tap outside .odic-header, which now
	   actually has room to land on real content instead of the popup
	   itself covering nearly the whole screen. */
	/* 32px = the row's own 16px top+bottom padding; 307.2px = 1.2 scaled
	   cards' worth of height (256px × 1.2) at the SAME zoom ratio the
	   cards themselves use just below — kept in sync with that ratio via
	   the same calc() rather than a fixed px number, since a fixed value
	   would only be right at one specific phone width. */
	.odic-header.is-order-open .odic-order-detail{
		max-height:calc(32px + 307.2px * (100vw - 32px) / 416px);
		overflow-y:auto;-webkit-overflow-scrolling:touch;
	}
	.odic-order-detail__row{
		flex-direction:column;align-items:center;justify-content:flex-start;
		overflow-x:visible;gap:16px;padding:16px 16px 20px;max-width:100%;
	}
	/* Cards stay real 416×256 content (every absolute-positioned child
	   inside them keeps the exact same coordinates — "1:1还原" isn't
	   abandoned here) but are scaled down to fit the viewport width
	   instead of overflowing it uncentred ("手机端没有居中" — a 416px-
	   wide flex item in a 375px-wide row was overflowing to the right,
	   not actually centred despite align-items:center on the row).
	   zoom (not transform:scale) because it shrinks the element's own
	   LAYOUT box too, not just its painted pixels — transform:scale
	   would still reserve the full 416px of flex-row space, leaving the
	   overflow/centring problem exactly as-is. calc() dividing two
	   lengths (viewport minus side padding, over the card's real 416px)
	   yields a plain ratio, so this scales correctly at any phone width
	   instead of one hard-coded number. */
	.odic-order-panel{zoom:calc((100vw - 32px) / 416px)}
}

/* ========================================================================
   Footer
   ======================================================================== */
/* margin-top:auto (was 0) is what actually pins this to the bottom of
   the viewport on short pages — see the body rule's comment above for
   the full explanation. flex:none stops it shrinking as a flex item. */
/* Black footer, on request ("这里改为黑色吧") — uses --odic-text (the
   site's standard near-black, #1c1c1e) rather than pure #000 so it reads
   as the same "black" already used elsewhere (nav icon fill, buttons),
   not a harsher one-off. Link/copy colors below are flipped to light-on-
   dark to match. */
/* padding-bottom:env(safe-area-inset-bottom) — same class of bug as the
   header's top safe-area fix, reported alongside it ("下方黑色底部工具
   栏的不能到底"): without this, the footer's own black background ends
   at its natural content height, so the home-indicator safe area below
   it on notched iPhones falls through to the page's own cream
   background instead of staying black all the way to the true bottom
   edge. Padding (not margin) so the extra space stays inside the footer
   and paints with its own background, exactly mirroring how .odic-header
   handles the top inset. */
.odic-footer{background:var(--odic-text);box-shadow:0 0 26px rgba(0,0,0,.08);margin-top:auto;flex:none;padding-bottom:env(safe-area-inset-bottom)}
.odic-footer__inner{
	width:100%;padding:28px 32px;
	display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;
}
.odic-footer__links{display:flex;gap:64px}
.odic-footer__links a{font-size:16px;color:#fff;opacity:1}
.odic-footer__links a:hover{font-weight:600}
/* Fixed width + centered text: getting bolder on hover widens the string a
   little, and without this the right edge (pinned by the flex layout) stays
   put while the whole thing visibly grows leftward. Centering it inside a
   box sized for the bold width lets that growth split evenly both ways
   instead, so the weight change still reads but the text doesn't drift. */
/* Muted, not full-strength text — copyright is low-priority boilerplate
   ("这是个不太重要的信息"), so it's translucent white instead of the
   solid white nav links next to it (--odic-muted's grey was tuned for
   the old light footer and reads too dark on the new black one). */
.odic-footer__copy{font-size:16px;color:rgba(255,255,255,.55);opacity:1;margin:0;width:256px;text-align:center}
@media (max-width:420px){.odic-footer__copy{width:auto}}
.odic-footer__copy a{color:inherit;text-decoration:none}
.odic-footer__copy a:hover{font-weight:600}

/* ========================================================================
   Hero (front page)
   ======================================================================== */
/* -env(safe-area-inset-top) alongside the existing -48px: has to pull
   up by the header's full new height (see .odic-header/body above) on
   notch devices too, or this would stop just short of the header on
   those and leave a sliver of body background peeking out above it. */
.odic-hero-a{position:relative;width:100%;overflow:hidden;margin-top:calc(-48px - env(safe-area-inset-top))}
/* Bug fixed: this used to fade to a cool grey (211,215,220) at its own
   bottom edge regardless of what came after it — invisible back when a
   fixed aspect-ratio made the box a predictable height, but once the
   section started sizing to its content (see the hero-height fix
   above) that grey edge could land right at the boundary with the next
   section, and .odic-moments right after this one is a distinctly
   warmer #f4f3ee — the mismatch showed up as a visible seam/band
   ("错层") at the join. Solid #f4f3ee base color + the gradient's own
   last stop retinted to the same tone (was a cool grey) so the fade
   actually lands on the exact color the next section already uses,
   instead of two different tones meeting at a hard edge. */
/* Rebuilt from scratch as ONE single gradient, on request, after two
   separate decorative layers (a rotated diagonal "light beam" and a
   bottom "contact shadow" band, both removed) each turned out to size
   themselves as a % of THIS box's own dimensions — fine at the one
   fixed aspect ratio they were originally tuned against, but each was
   its own independent formula, so once the box started sizing to its
   content instead of a fixed ratio (see the hero-height fix elsewhere
   in this file) and its proportions started varying across the 10
   breakpoints this section supports, the layers could drift out of
   sync with each other and with the box edges — that drift was the
   actual "错层" seam, twice, from two different layers.
   This version is deliberately just the one radial-gradient, and
   deliberately omits an explicit size (no "75% 95%" etc.) — with no
   size keyword, a CSS radial-gradient defaults to farthest-corner,
   which by definition always reaches every corner of whatever box it's
   painted on, at any aspect ratio. That's what actually guarantees no
   more seams: it's not that the numbers are tuned better this time —
   there's no independent second layer left to drift relative to, and
   the gradient's own last stop is mathematically guaranteed to reach
   the edge instead of possibly falling short of it. */
.odic-hero-a__bg{
	position:absolute;top:0;left:0;width:100%;height:100%;inset:0;overflow:hidden;
	background:
		radial-gradient(
			ellipse at 43% 40%,
			rgba(255,255,255,.98) 0%,
			rgba(250,251,252,.94) 30%,
			rgba(240,239,233,.65) 60%,
			#f4f3ee 100%
		);
}
/* padding-bottom is THE spec for how far the tablet's own frame sits
   above the section boundary (and so above whatever comes next — the
   carousel) — pinned to one explicit value per breakpoint (below),
   reusing the same 9 breakpoints already established elsewhere in this
   file (900/860/820/760/640/600/520/420/360) rather than inventing a
   new set, so this doesn't drift out of step with how the rest of the
   site already segments device sizes. Every value is a clean 8px step,
   tapering 96→24 as the viewport narrows.
   This is safe to vary per breakpoint without ever reintroducing the
   seam bug precisely because .odic-hero-a__bg's gradient (the actual
   background) is ONE formula defined entirely in %/em-relative terms —
   ellipse size, position, and every stop are percentages of the box,
   not fixed pixels — so it re-fills whatever height each breakpoint's
   padding produces automatically. Nothing here needs to "match" the
   padding value by hand; the fix that actually mattered was retinting
   the gradient's own end color to #f4f3ee (see that rule) so it always
   lands on .odic-moments's exact color regardless of box height. */
.odic-hero-a__content{
	position:relative;z-index:1;width:100%;
	padding:101px 24px 96px;text-align:center;box-sizing:border-box;
}
@media (max-width:900px){.odic-hero-a__content{padding-bottom:88px}}
@media (max-width:860px){.odic-hero-a__content{padding-bottom:80px}}
@media (max-width:820px){.odic-hero-a__content{padding-bottom:72px}}
@media (max-width:760px){.odic-hero-a__content{padding-bottom:64px}}
.odic-hero-a__content h1{font-size:clamp(32px,9vw,48px);font-weight:600;color:#1c1c1e;margin:0 0 21px;line-height:1.05}
.odic-hero-a__content h1 .odic-hero__year{font-weight:400}
.odic-hero__sub{font-size:16px;font-weight:400;color:#2c2c2c;margin:0 0 31px}
.odic-hero__actions{display:flex;gap:40px;justify-content:center;margin-bottom:60px}
/* Mobile hero rhythm redone against several real Apple.com mobile
   screenshots the user sent (iPhone, Apple Card, iPad Pro, MacBook Air,
   iPad Air hero rows) — every one of them follows the same 3-step gap
   ladder: heading→tagline tightest, tagline→buttons a step looser,
   buttons→product image loosest by far. Matched that ladder (24 / 32 /
   80, each a clean 8px step, the last one widened further once more
   references confirmed how much Apple lets that specific gap breathe)
   and bumped the heading to Apple's heavier weight — theirs reads
   noticeably bolder than our semibold. Deliberately NOT copied: Apple's
   blue button color — that's their brand identifier, not a layout
   choice, so .odic-btn's own dark/outline colors stay as they are.
   Desktop keeps the exact Figma-sourced values above, untouched. */
@media (max-width:640px){
	.odic-hero-a__content{padding-top:104px;padding-bottom:56px}
	.odic-hero-a__content h1{font-weight:700;margin-bottom:24px}
	.odic-hero__sub{font-size:20px;margin-bottom:32px}
	/* 80px→32px — pulled in hard on request, twice ("平板和胶囊的距离
	   ...是不是太远了" then, after a first 80→56 pass still read as
	   unchanged, "没改...平板没有上移"): now matches the tagline→buttons
	   tier above instead of being the loosest gap in the ladder, so
	   buttons→device is unmistakably tighter, not just marginally so.
	   Space AFTER the device (.odic-hero-a__content's own padding-bottom,
	   below) stays untouched on request either way. */
	.odic-hero__actions{margin-bottom:32px}
}
@media (max-width:600px){.odic-hero-a__content{padding-bottom:48px}}
@media (max-width:520px){.odic-hero-a__content{padding-bottom:40px}}
@media (max-width:420px){.odic-hero-a__content{padding-bottom:32px}}
@media (max-width:360px){.odic-hero-a__content{padding-bottom:24px}}
.odic-hero__device{max-width:562px;margin:0 auto}
/* Desktop's 40px gap is generous by design (matches the roomy hero
   layout), but on a phone-width screen the same 40px between two
   moderately-sized pills reads as unnecessarily far apart. Tightened to
   24px on mobile, and again to 16px under 360px alongside smaller
   button padding — .odic-hero-a clips overflow (see its own rule), and
   the full-size pills plus a 40px gap genuinely didn't fit that narrow
   (measured ~30-40px overflow), silently clipping "Order now"'s edge.
   Both values kept as multiples of 8 (24, 16) per spacing convention. */
/* Pill sizing itself now shrinks globally on mobile (.odic-btn's own
   base rule, further down this file) — only the gap between the two
   hero pills stays a hero-specific override here. */
@media (max-width:640px){
	.odic-hero__actions{gap:16px}
}
@media (max-width:360px){
	.odic-hero__actions{gap:12px}
}
/* Bug fixed (originally only on mobile, now applies at every width): a
   fixed aspect-ratio box (1920:800) with overflow:hidden sized itself
   from viewport WIDTH alone, not from what the stacked heading/buttons/
   device-mockup content actually needs. That's fine right at 16:9, but
   resize the window to any other ratio (still desktop-width, not just
   phone-narrow) and the box gets shorter than its content — silently
   clipping the bottom of the device card and letting the next section
   start overlapping it, with no gap. Confirmed live at 900x1000: the
   tablet's task bubbles were clipped and "The moments that make us who
   we are" was already overlapping the tablet's lower half. Not a
   mobile-only problem, so the fix that used to be mobile-only isn't
   either — .odic-hero-a now always sizes to its content instead of a
   fixed ratio; .odic-hero-a__bg fills whatever height that ends up
   being via inset:0 (a height:135%-of-parent trick needs a definite
   parent height to resolve, which an auto-height section won't reliably
   give it, so plain inset:0 fill replaces it unconditionally too). */

/*
 * Device screen mockup — rebuilt from Figma node 2616:857 ("Today/tomorrow
 * -bubble-个人"). The source is ~80 absolutely-positioned layers (masked
 * glow rims, per-blob gradient matrices, container-query sizing); ported
 * 1:1 that would be hundreds of near-duplicate rules for a purely
 * decorative rim-light effect. Kept exact from the real data: every
 * bubble's position, size, category color, glow tint and text content,
 * and the 4 real photo assets (downloaded from the Figma file, not
 * stock substitutes). Simplified: the rim-light glow is one CSS
 * box-shadow per bubble instead of Figma's per-bubble SVG luminance
 * mask, and the 12-blob background wash is approximated with the same
 * real hex colors at qualitatively matching positions rather than the
 * exact gradient matrices (verified against the Figma screenshot).
 */
/*
 * REVERTED on request — the 1024x730-based "fix" distorted the frame
 * worse than the original. Back to the Figma layout node's own 612x370
 * ratio (2616:856) with the screen inset at 2616:857's real offset
 * within it, pending a proper re-check of the tablet-frame asset.
 */
.odic-device-card{
	position:relative;width:100%;aspect-ratio:612/370;container-type:inline-size;
	filter:drop-shadow(0 30px 60px rgba(0,0,0,.18));
}
.odic-device-card__frame{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
.odic-device-card__screen{position:absolute;left:4.08%;top:7.3%;width:91.83%;height:85.95%;overflow:hidden;background:#faf8ef}
.odic-device-card__bg{
	position:absolute;inset:0;
	background-image:
		radial-gradient(18% 24% at 14% 82%,rgba(255,203,170,.7),transparent 100%),
		radial-gradient(22% 30% at 33% 48%,rgba(255,217,217,.65),transparent 100%),
		radial-gradient(16% 22% at 8% 30%,rgba(254,255,212,.7),transparent 100%),
		radial-gradient(16% 22% at 46% 88%,rgba(255,250,219,.7),transparent 100%),
		radial-gradient(16% 22% at 78% 65%,rgba(242,255,213,.7),transparent 100%),
		radial-gradient(16% 22% at 82% 90%,rgba(197,255,248,.7),transparent 100%),
		radial-gradient(20% 27% at 68% 22%,rgba(219,235,255,.7),transparent 100%),
		radial-gradient(19% 26% at 92% 12%,rgba(235,239,255,.7),transparent 100%),
		radial-gradient(20% 27% at 22% 8%,rgba(238,188,255,.55),transparent 100%),
		radial-gradient(18% 24% at 60% 6%,rgba(255,250,216,.7),transparent 100%),
		radial-gradient(16% 22% at 90% 55%,rgba(255,217,156,.6),transparent 100%);
}
.odic-device-card__watermark{
	/*
	 * Re-pulled straight from the Figma node (2616:865) via the REST API —
	 * fontSize 122, letterSpacing 4.88 (so .04em, not the .1em guessed
	 * earlier from a mis-read Tailwind class), fill #E6E5EB at opacity
	 * 0.2 exactly. Opacity really is 0.2 — the "too faint"/"too dark"
	 * back-and-forth was from other bugs (font not loading, wrong
	 * letter-spacing spreading it out), not the opacity number itself.
	 */
	position:absolute;left:50.02%;top:50.16%;transform:translate(-50%,-50%);
	font-family:Aleo,Georgia,serif;font-weight:600;font-size:19.96cqw;letter-spacing:.04em;
	color:rgba(230,229,235,.2);white-space:nowrap;pointer-events:none;
}
/*
 * Bug fixed: this is a hairline ruler along the very top edge of the
 * SCREEN (the real Figma layers are 0.587px tall bars, rotated 90deg —
 * i.e. thin horizontal segments, not tall vertical blocks — that's what
 * height:100% against the old 7cqw-tall rule container turned into solid
 * colour blocks). Re-measured against 2616:857 directly (not its 862
 * child, which was the wrong reference frame): the three ticks actually
 * overlap each other slightly, they are not sequential — reproduced as
 * measured, not straightened out.
 */
.odic-device-card__rule{position:absolute;left:0;right:0;top:0;height:2px;pointer-events:none}
.odic-device-card__rule::before{content:"";position:absolute;left:-0.1%;top:0;width:98.75%;height:1px;background:#ddd}
.odic-device-card__tick{position:absolute;top:0;height:1px;opacity:.64}
/*
 * Shifted flush to the screen's left edge on request — this is a progress
 * bar, it reads wrong floating away from the corner. Kept the three
 * segments' relative widths/overlap as measured, just moved the whole
 * group so the first one starts at 0 instead of 15.14%.
 */
.odic-device-card__tick--green{left:0%;width:15.3%;background:#378e0e}
.odic-device-card__tick--blue{left:12.57%;width:12.48%;background:#1b568e}
.odic-device-card__tick--orange{left:18.11%;width:5.43%;background:#ba5d0c}
/*
 * Pins (2616:886/887) are children of the 562x318 screen frame in Figma,
 * at y=178.07..215.62 (top 55.99% / height 11.8% of that frame) — placed
 * here as children of the outer 612x370 card so the % is against the
 * frame photo they visually sit on: left = (25 + pin.x) / 612, top =
 * (27 + pin.y) / 370. Real thickness is 1.17px; nudged to 2px on request
 * (sub-2px hairlines render inconsistently across browsers/DPI) — flagged,
 * not silently "corrected".
 */
.odic-device-card__pin{position:absolute;top:55.42%;height:10.15%;width:2px;border-radius:3px;background:#8d8d92;box-shadow:inset 0 0 1px rgba(0,0,0,.5)}
.odic-device-card__pin--l{left:4.08%}
.odic-device-card__pin--r{left:95.75%}
/* Real Figma export (node 2616:880, REST API) — not a CSS-drawn circle. */
/*
 * Bug fixed: the export (menu-button.png) has real canvas padding for its
 * drop-shadow bleed — measured 128px natural size against a 65.7px core
 * (16.43px node width x4 scale), a ~1.95x ratio, same class of bug as the
 * bubble rings/glass earlier. Sizing the <img> straight to the node's
 * width squeezed that padding in and made the visible circle render
 * undersized. Anchor div stays at the real node size/position; the image
 * inside is oversized/centred to show the full shadow instead of
 * cropping it. z-index above bubbles per request — stays on top of
 * everything.
 */
/*
 * Bug fixed: right/bottom were both ~3.87%/3.89% — nearly identical
 * numbers, but % resolves against a DIFFERENT axis for each (right is %
 * of the screen's width, bottom is % of its height), and the screen is
 * 562x318, nowhere near square. So the actual pixel gaps were ~3.55cqw
 * on the right vs ~2.02cqw on the bottom — visibly closer to the bottom
 * edge, not equidistant. cqw is always relative to the same axis (card
 * width) regardless of which side it's applied to, so the same cqw
 * value on both right and bottom gives true equal pixel margins.
 */
.odic-device-card__menu{position:absolute;right:3cqw;bottom:3cqw;width:2.68cqw;aspect-ratio:1/1;z-index:2}
.odic-device-card__menu img{position:absolute;inset:-47.5%;width:195%;height:195%;max-width:none}

/*
 * Every bubble is now a single real Figma export (REST API, scale 3x) —
 * no hand-rebuilt rings/glass/curved-text layers. Each export's own
 * canvas has ~140% padding around its circle for Figma's glow bleed
 * (confirmed by measuring all 11: 310-320px/75.1px-native big bubbles,
 * 196-198px/46.9px-native small ones — consistently ~1.4x), so the img
 * is sized 140%/inset:-20% to show that bleed instead of cropping it.
 */
.odic-bubble{position:absolute;aspect-ratio:1/1;z-index:1;will-change:transform}
.odic-bubble img{position:absolute;inset:-20%;width:140%;height:140%;max-width:none;pointer-events:none}

.odic-hero-b{position:relative;width:100%}
.odic-carousel__stage{
	position:relative;width:100%;aspect-ratio:1920/800;overflow:hidden;
	touch-action:pan-y;cursor:grab;user-select:none;
}
/* 1920:800 (2.4:1, cinematic-wide) makes sense on desktop but on a phone
   it letterboxes down to a very short strip. Both slides now have a
   purpose-built mobile image (Figma nodes 2893:4638/2893:4641, swapped in
   via <picture><source> below) that's a flattened 1:1 square with its own
   heading/quote already laid out inside it — matching the stage to that
   same 1:1 ratio is what actually fits it without letterboxing or
   cropping, rather than picking an arbitrary "tall enough" ratio and
   hoping object-fit:cover frames it well. */
@media (max-width:640px){
	.odic-carousel__stage{aspect-ratio:1/1}
}
.odic-carousel__stage:active{cursor:grabbing}
.odic-carousel__slide{
	position:absolute;inset:0;transform:translateX(100%);
	/* Gentle ease-in-out: slow to start, steady through the middle, slow to
	   rest — reads as an unhurried, deliberate drift rather than a sprint
	   that slams the brakes, and gives the resting frame a calmer "settle". */
	transition:transform 1.9s cubic-bezier(.65,0,.35,1);
}
.odic-carousel__slide.is-active{transform:translateX(0)}
.odic-carousel__slide.is-prev{transform:translateX(-100%)}
.odic-carousel__slide.is-next{transform:translateX(100%)}
.odic-carousel__slide.no-transition{transition:none}
.odic-hero-b__frame{position:absolute;inset:0;overflow:hidden}
.odic-hero-b__frame img{
	position:absolute;top:0;left:0;width:100%;height:100%;
	object-fit:cover;object-position:50% 59%;display:block;
}
.odic-hero-b__overlay{
	position:absolute;left:0;right:0;top:61.8%;transform:translateY(-50%);
	padding:0 24px;text-align:center;
}

/* Second carousel slide — "The moments that make us who we are" */
.odic-moments{position:absolute;inset:0;background:#f4f3ee}
.odic-moments__photo-crop{position:absolute;inset:0;overflow:hidden}
.odic-moments__photo{
	position:absolute;top:-5%;left:-37.25%;width:213.33%;height:119.5%;
	max-width:none;display:block;
}
.odic-moments__text{position:absolute;left:128px;top:50%;transform:translateY(-50%);max-width:640px}
.odic-moments__text h2{font-size:48px;font-weight:600;line-height:1.15;color:#1c1c1e;margin:0 0 16px}
.odic-moments__text p{font-size:24px;font-weight:400;color:#2c2c2c;line-height:1.5;margin:0}
@media (max-width:900px){
	.odic-moments__text{left:24px;right:24px;max-width:none}
	.odic-moments__text h2{font-size:clamp(22px,5vw,32px)}
	.odic-moments__text p{font-size:clamp(14px,3vw,18px)}
}
/* Same story as the first slide: this scrim/reposition treatment (kept
   above for desktop) is superseded on mobile by a purpose-built image
   (Figma node 2893:4641, via <picture><source> in the markup) with its
   own heading/copy already laid out above the photo inside it — hide the
   separate text layer so it doesn't double up.
   The old crop math below it (-37.25%/213.33%, hand-tuned for the source
   photo's actual 4096x956 panorama against the phone's 3:4 stage) is gone
   too — that was working around not having a real mobile asset at all;
   now there is one, sized to exactly match the stage's own aspect ratio,
   so it just needs a plain full-bleed fill like the desktop rule already
   does, not a re-derived crop window. */
@media (max-width:640px){
	.odic-moments__text{display:none}
	/* Without this, the mobile image would inherit the desktop crop rule
	   above (-37.25%/213.33%, tuned for the old wide-panorama source) and
	   get badly mis-cropped — this image is already the correct square
	   composition, it just needs to fill the box plainly. */
	.odic-moments__photo{top:0;left:0;width:100%;height:100%}
}

.odic-carousel__progress{
	position:absolute;left:50%;bottom:5%;transform:translateX(-50%);z-index:2;
	display:flex;align-items:center;gap:8px;
}
/* Bug fixed: both width and background are toggled by JS
   (classList.toggle('is-active', ...) in main.js) — a transition on
   either got permanently stuck at the inactive dot's value the moment
   JS (not :hover/:focus) triggered the class change, a real Chromium
   quirk (see .odic-login-continue for the full writeup of how this was
   diagnosed). Found while building the Login page and checking whether
   the same pattern existed elsewhere — it did, here and in two other
   spots below. Dropped the transition; the dot still snaps to active
   correctly, just without the animated grow. */
.odic-carousel__progress button{
	border:none;padding:0;cursor:pointer;height:6px;border-radius:3px;
	background-color:rgba(28,28,30,.35);width:6px;
}
.odic-carousel__progress button.is-active{width:32px;background-color:#fff}
.odic-hero__quote{position:relative;margin:0 auto;max-width:96%;width:max-content}
.odic-hero__quote p{
	margin:0;font-size:clamp(14px,2.6vw,48px);font-weight:600;line-height:1.2;
	font-family:var(--odic-font);white-space:nowrap;
}
.odic-hero__quote-glow{
	position:absolute;inset:0;filter:blur(12px);
	background-image:linear-gradient(90deg,#05a4ee 0%,#b84fe0 34.5%,#fa965f 69.5%,#e47791 100%);
	-webkit-background-clip:text;background-clip:text;color:transparent;
}
.odic-hero__quote-crisp{position:relative;color:#fff}
.odic-hero__tagline{margin:14px auto 0;max-width:900px;font-size:clamp(15px,1.4vw,24px);color:#fff;font-weight:500;text-shadow:0 0 2px #000}
/* Bug fixed: both this and the quote text declared font-weight:600, but
   the star used a totally different font stack ('SF Pro Rounded'/'SF Pro
   Display'/-apple-system — all Apple-only). On a system without those
   installed (e.g. Windows), it fell all the way through to a bare
   `sans-serif` — usually Arial, which only ships Regular/Bold and
   synthesizes/snaps 600 to its Bold face, rendering noticeably heavier
   than Inter's real 600 SemiBold. Same declared weight, different actual
   typeface drawing it — that's the SB-vs-EB mismatch. Matching the same
   font stack as the surrounding text (not just the same weight number)
   is what actually makes them render identically. */
/* Star specifically requested back on 'SF Pro Rounded' Bold ("星星使用
   SF Pro Rounded BOLD的粗细" — "单独的星星使用 其它不使用": only the
   star, nothing else on the page). This is the exact font stack the bug
   note above warns about — real on Apple devices, Apple-only everywhere
   else — so the fallback chain matters: 'SF Pro Rounded' renders for
   real on Mac/iOS (Safari, and Chrome/Firefox there also resolve San
   Francisco by name); everywhere else (Windows, Android, Linux — most
   visitors) it falls through -apple-system/BlinkMacSystemFont (both
   no-ops off Apple platforms) to this site's own Inter, at a real,
   actually-loaded 700 Bold — not the generic `sans-serif` the original
   bug fell into, which is what caused that mismatch in the first place.
   The surrounding quote/tagline text stays untouched at 600. */
.odic-star{font-family:'SF Pro Rounded','SF Pro Display',-apple-system,BlinkMacSystemFont,var(--odic-font);font-weight:700}
/* Bug fixed: .odic-hero__quote was `width:max-content` + the <p> was
   `white-space:nowrap` — fine on desktop where the quote comfortably fits
   one line, but on a narrow phone that line is simply longer than the
   screen, and since .odic-hero-b__frame clips overflow, the excess just
   vanished off the edge instead of wrapping (confirmed via
   scrollWidth-clientWidth, not eyeballing a screenshot: ~40-70px of
   invisible overflow under 400px). Letting it wrap here fixes that.
   (First attempt at this put the override up near .odic-hero-b__overlay,
   earlier in the file than these actual base rules — same specificity,
   later source order wins, so the unscoped nowrap below was quietly
   winning back regardless of viewport. Has to come after these.)
   That whole scrim/reposition treatment (kept above for reference/desktop
   fallback) is now superseded on mobile by a purpose-built image instead
   (Figma node 2893:4638, swapped in via <picture><source> in the markup):
   quote, star and tagline are already laid out inside it, positioned by
   an actual designer rather than a CSS scrim approximating "somewhere
   that isn't a face". Hiding the separate text layer here so it doesn't
   double up with the text baked into that image. */
@media (max-width:640px){
	.odic-hero-b__overlay{display:none}
}

/* ========================================================================
   Always in sync
   ======================================================================== */
.odic-sync{padding:128px 0 96px;background:#f5f5f7}
/* Same weight/size as .odic-privacy h2 / .odic-pricing h2 (48px/600) —
   kept centered here since this section's whole layout is centered,
   unlike those two-column ones, but the type itself now matches. */
.odic-sync h2{text-align:center;font-size:48px;font-weight:600;margin:0 0 8px}
.odic-sync__caption{text-align:center;font-size:19px;font-weight:400;color:var(--odic-text-soft);margin:0 0 48px}
/* Bug fixed: .odic-sync had zero horizontal padding and a fixed 48px
   h2 with no mobile override — fine on desktop where the section is
   wide enough that the centered text never gets close to the edge, but
   on a 375px phone "Always in sync." at 48px bold was nearly touching
   both edges of its own card with no breathing room. (First attempt at
   this put the override BEFORE the base .odic-sync h2 rule above —
   same specificity, later source order wins, so the unscoped 48px was
   quietly winning regardless of viewport. Has to come after it — same
   class of bug this file has hit more than once before.) */
@media (max-width:640px){
	.odic-sync{padding:96px 24px 64px}
	.odic-sync h2{font-size:clamp(28px,9vw,40px)}
}
.odic-sync__viewport{
	position:relative;overflow:hidden;max-width:1400px;margin:0 auto 32px;
	mask-image:linear-gradient(90deg,transparent 0%,#000 20%,#000 80%,transparent 100%);
	-webkit-mask-image:linear-gradient(90deg,transparent 0%,#000 20%,#000 80%,transparent 100%);
}
/* Bug fixed: no touch-action was set here, so on mobile a horizontal drag
   anywhere on this row — not just from the screen edge — got interpreted
   as the browser's own swipe-navigation gesture (back/forward) instead of
   reaching our pointer handlers, since the default `touch-action:auto`
   leaves the browser free to claim horizontal gestures for itself.
   `pan-y` tells it vertical scrolling is still native, but horizontal
   input on this element is ours to handle — same fix already applied to
   .odic-carousel__stage above, just missing here. */
.odic-sync__track{display:flex;gap:32px;width:max-content;will-change:transform;cursor:grab;user-select:none;-webkit-user-select:none;touch-action:pan-y}
.odic-sync__track:active{cursor:grabbing}
.odic-sync__track--left{animation:odic-marquee-left 90s linear infinite}
.odic-sync__track--right{animation:odic-marquee-right 78s linear infinite}
@keyframes odic-marquee-left{from{transform:translateX(0)}to{transform:translateX(-25%)}}
@keyframes odic-marquee-right{from{transform:translateX(-25%)}to{transform:translateX(0)}}
@media (prefers-reduced-motion:reduce){
	.odic-sync__track--left,.odic-sync__track--right{animation:none}
}
.odic-sync__card{
	background:#fff;padding:72px 16px 0;position:relative;
	text-align:center;box-shadow:none;border-radius:24px;overflow:hidden;
	width:512px;height:288px;flex:none;
}
/* Matches the Figma card exactly: a second, inset (4px), smaller-radius
   (20px) rounded rect with only a stroke — no fill — at 10% opacity, sitting
   just inside the card's own edge. Not a full-bleed border on the card
   itself. */
.odic-sync__card::after{
	content:"";position:absolute;inset:4px;border-radius:20px;
	border:1px solid rgba(55,55,55,.1);pointer-events:none;
}
/* No border-radius here — these are official third-party app icons, each
   already shaped/rounded (or not) however that vendor draws it; clipping
   our own corner radius on top cropped into the real artwork instead of
   just framing it ("你是不是把三方app加上圆角了，去掉圆角，用我的原图
   即可"). The white card behind it already supplies the rounded-rect
   frame (.odic-sync__card, above). */
.odic-sync__card img{width:64px;height:64px;margin:0 auto 20px;object-fit:contain;display:block}
/* Bug fixed: this lived only inside the max-width:900px block below, so
   desktop (which renders the exact same .odic-sync__card-inner box, just
   unscaled — see the big comment above that block) never got it at all,
   showing the Yahoo icon's real square corners instead of the rounded
   ones mobile already had ("手机端他是圆角，电脑端它是直角"). Every other
   third-party icon here is deliberately left un-rounded (its own artwork
   already has whatever shape the vendor drew), but Yahoo's specifically
   needed this — moved out so it's one rule applying everywhere alongside
   that same "no separate mobile version" rule, not a mobile-only patch. */
.odic-sync__card img.odic-sync__icon--yahoo{border-radius:22%}
.odic-sync__card span{font-size:32px;color:#2c2c2c;letter-spacing:-.64px;display:block}
/*
 * Bug fixed twice ("电脑端正常，手机端给加了个圆角 都不一样了" — even
 * after pre-rendering a dedicated 40px icon file, mobile was still a
 * genuinely separate render, not the desktop one). On request ("先清楚
 * 这个板块，然后重新从桌面端拉去，并保持不变，只是缩放"): mobile no
 * longer has its own card/icon/text sizes at all. .odic-sync__card-inner
 * always renders at the fixed 512×288 desktop dimensions — same DOM,
 * same 64px icon box, same 32px text — and is only ever visually shrunk
 * via transform:scale() below; the outer .odic-sync__card just becomes a
 * smaller cropping window (overflow:hidden) sized to match. This is the
 * exact same rendered pixels as desktop, GPU-scaled down, not a second
 * independently-sized version that could ever drift from it again.
 * .odic-sync__card-inner has no rules of its own at desktop size — it's
 * a transparent pass-through there, so desktop is genuinely untouched;
 * only the mobile block below gives it real dimensions + padding
 * (recreating .odic-sync__card's own desktop box exactly) before scaling
 * the whole thing down as one unit.
 */
@media (max-width:900px){
	.odic-sync__card{width:320px;height:180px;padding:0;overflow:hidden}
	.odic-sync__card-inner{
		width:512px;height:288px;padding:72px 16px 0;box-sizing:border-box;
		transform:scale(0.625);transform-origin:top left;
	}
}

/* "Some things are just yours" and "One price. Forever yours." now reuse
   the shared .odic-story-row component (see the Order/Learn-More page
   block above) instead of their own dedicated CSS — same layout/type as
   the story rows, on request. */

/* ========================================================================
   FAQ
   ======================================================================== */
.odic-faq{width:100%;background:#faf8ef}
.odic-faq__inner{padding:140px 50px 110px;max-width:1400px;margin:0 auto}
.odic-faq h2{text-align:center;font-size:clamp(32px,4.5vw,44px);font-weight:700;letter-spacing:-.025em;margin:0 0 64px;color:#1c1c1e}

.odic-faq__grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}
.odic-faq__card{
	background:#fff;border:1px solid var(--odic-border);border-radius:24px;padding:32px 36px;
	transition:background .25s ease,box-shadow .25s ease,transform .25s ease;
}
.odic-faq__card:hover{background:#fff;box-shadow:0 12px 32px -16px rgba(28,28,30,.18)}
.odic-faq__card-q{
	width:100%;display:flex;align-items:flex-start;gap:18px;
	background:none;border:none;padding:0;text-align:left;cursor:pointer;
	font-size:19px;font-weight:600;color:#1c1c1e;line-height:1.4;
}
/* Bug fixed: this whole accordion's three JS-toggled properties (the
   mark's transform + background, the answer's max-height below) were
   all stuck at their closed-state value the moment .is-open got added
   via JS — same Chromium quirk as .odic-carousel__progress above, but
   worse here: with max-height stuck at 0, the FAQ answers never
   actually appeared at all when clicked, not just a missing animation.
   Transitions dropped from all three; the accordion still opens/closes
   correctly, just as an instant snap instead of animated. */
.odic-faq__card-mark{
	flex:none;width:28px;height:28px;border-radius:50%;background-color:#1c1c1e;color:#fff;
	display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:400;
	line-height:1;margin-top:1px;
}
.odic-faq__card.is-open .odic-faq__card-mark{transform:rotate(135deg);background-color:var(--odic-accent)}
.odic-faq__card-a{max-height:0;overflow:hidden}
.odic-faq__card-a p{margin:14px 0 0 46px;color:#5a5a5a;font-size:15px;line-height:1.65}
.odic-faq__card.is-open .odic-faq__card-a{max-height:260px}

/* Height/font unified with every other pill button sitewide (48px /
   16px / 500), matching the homepage's Order now/Learn more buttons —
   was 15px/600 with no explicit height before, on request. */
.odic-faq__more-btn{
	display:flex;align-items:center;justify-content:center;gap:8px;margin:56px auto 0;
	background:#fff;border:1px solid var(--odic-border);border-radius:50px;cursor:pointer;
	height:48px;padding:0 28px;font-size:16px;font-weight:500;color:var(--odic-text);
	transition:background .2s ease,transform .2s ease,border-color .2s ease;
}
.odic-faq__more-btn:hover{background:#fff;border-color:#ccc;transform:translateY(-1px)}
/* transitions dropped on both rules below — same JS-toggle-vs-CSS-
   transition Chromium quirk documented on .odic-header.is-open
   .odic-mobile-nav above (moreBtn.classList.toggle('is-open', ...) and
   card.classList.add('is-shown') in main.js are both JS-driven). */
.odic-faq__more-btn.is-open svg{transform:rotate(180deg)}
.odic-faq__card--more{opacity:0;transform:translateY(8px)}
.odic-faq__card--more.is-shown{opacity:1;transform:translateY(0)}

@media (max-width:820px){
	.odic-faq__inner{padding:80px 24px}
	.odic-faq__grid{grid-template-columns:1fr}
	.odic-faq__card{padding:26px 24px}
}

/* ========================================================================
   Back to top
   ======================================================================== */
.odic-back-to-top{
	position:fixed;right:24px;bottom:24px;width:80px;height:48px;z-index:200;
	background:rgba(255,255,255,.38);border:none;border-radius:100px;
	box-shadow:0 0 26px rgba(0,0,0,.1);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
	display:flex;align-items:center;justify-content:center;color:#1c1c1e;cursor:pointer;
	opacity:0;pointer-events:none;
}
/* transition dropped — same JS-toggle-vs-CSS-transition Chromium quirk
   documented in full on .odic-header.is-open .odic-mobile-nav above;
   this one toggles on scroll (backToTop.classList.toggle('is-visible',
   ...)) rather than click, but the same fix applies. */
.odic-back-to-top.is-visible{opacity:1;pointer-events:auto}
@media (max-width:600px){.odic-back-to-top{right:16px;bottom:16px;width:56px;height:40px}}

/* ========================================================================
   Generic content page (About us / Learn more placeholder)
   ======================================================================== */
.odic-page-hero{
	background:var(--odic-gradient-warm);padding:96px 24px 64px;text-align:center;
}
.odic-page-hero h1{font-size:clamp(32px,5vw,48px);font-weight:600;margin:0}
.odic-content{max-width:900px;margin:0 auto;padding:64px 24px}
.odic-content h2{font-size:24px;font-weight:600;margin:0 0 16px}
.odic-content p{color:var(--odic-text-soft);font-size:16px;line-height:1.9;margin:0 0 8px}
.odic-signature{margin-top:32px;color:var(--odic-text-soft)}

.odic-placeholder{
	max-width:640px;margin:0 auto;padding:96px 24px 140px;text-align:center;
}
.odic-placeholder__icon{
	width:72px;height:72px;margin:0 auto 24px;border-radius:50%;
	background:var(--odic-pill);display:flex;align-items:center;justify-content:center;
}
.odic-placeholder h1{font-size:28px;font-weight:600;margin:0 0 12px}
.odic-placeholder p{color:var(--odic-muted);margin:0 0 32px}

/* ========================================================================
   Web App page
   ======================================================================== */
/* Reverted: an earlier pass pulled these three gaps (91.56/72/91.58px)
   from a Figma node search by text match, which — unlike the
   tablet→section gap below (confirmed correct against both the frame
   coordinates and a fresh reference export) — turned out to be pulling
   from the wrong instance somewhere in the file. Flagged as visibly too
   loose against the real design. Back to the original, never-complained-
   about values rather than guessing a new number a second time. */
.odic-webapp-hero{padding:56px 24px 0;text-align:center}
/* Bug fixed: same wrong-node problem, opposite direction — 32px was
   roughly HALF the real size. Confirmed properly this time by measuring
   actual rendered pixels in a fresh Figma export of this exact frame
   (not a name-matched node lookup): the dots cluster's own bounding box
   in that render is ~63x63px against the page's 1920px reference width,
   i.e. close to its native SVG canvas size (65.6x65.6) — it's placed at
   almost exactly 1:1, not scaled down to half size. */
.odic-webapp-hero__dots{width:64px;height:64px;margin:0 auto 20px;display:block}
.odic-webapp-hero h1{font-size:clamp(28px,4vw,34px);font-weight:600;margin:0 0 24px}
/* Black capsule, same height as the homepage "Learn more" pill
   (.odic-btn--hero-dark), on request ("这里改成和LEAR MORE一样的胶囊
   黑色 同高"). The badge is one flattened SVG (rect + play-triangle +
   wordmark baked together, not live HTML text), so rather than rebuild
   it as a real .odic-btn and re-source a matching icon, the simplest
   exact match is recoloring that same SVG's rect fill to #333 (this
   file, see the rect right below the <svg> tag) and scaling the whole
   badge to .odic-btn's own height — the rect's rx was already exactly
   half its height (17/34), so scaling it uniformly keeps it a true
   capsule at any size, same as .odic-btn--hero-dark's border-radius:24px
   on its own 48px height. */
.odic-play-badge{display:inline-block;margin-bottom:32px}
.odic-play-badge img{height:48px;width:auto}
/* Bug fixed: the tablet here used to be built from two separate layers —
   a frame SVG (device-ipad-frame.svg) laid out via CSS width:100% in
   normal flow, with a screen image absolutely positioned inset:3.5% of
   whatever box that frame's own aspect ratio produced. Except that frame
   SVG's own intrinsic size (449.6x586 — taller than wide, a PORTRAIT
   phone/tablet proportion) doesn't match this section's actual landscape
   tablet at all, so at width:100% of a 700px container it rendered ~912px
   tall — the giant, frameless-looking gradient block this was reported
   as. Re-exported the real landscape mockup straight from Figma (node
   2616:1032) as one flattened image instead — frame, screen content and
   the "Christmas Day" card all baked in together — so there's no second
   layer to mis-match sizes with in the first place. */
.odic-webapp-preview{max-width:700px;margin:0 auto}
/* Bug fixed (the specific complaint): gap from the tablet down into the
   gradient section was a flat 32px/64px guess, reported as not even
   fitting one card's height — measured against the real Figma frame, the
   actual gap is 138.07px against a 660.46px-wide tablet node, i.e.
   ~21% of the tablet's own width (the "Christmas Day" card in that same
   frame is ~88.5px tall, so this gap works out to ~1.56x the card's
   height — comfortably past the "at least 1.2x" ask, because that's
   what's actually in the design, not because 1.2x was the target).
   Set as padding-bottom *on the image*, not margin on the wrapping div:
   percentage padding/margin resolves against the CONTAINING BLOCK's
   width, and the image's containing block is .odic-webapp-preview (which
   actually carries the max-width:700px cap) — putting it on the preview
   div itself would resolve against ITS parent (.odic-webapp-hero, full
   section width) instead, the wrong basis, and silently overshoot on
   wide screens. Padding also can't collapse away the way margin can. */
.odic-webapp-preview img{width:100%;display:block;padding-bottom:20.91%}
/* padding stays 0 on the bottom — on request, that's an iron rule: the
   gradient box's own bottom edge IS the devices' cut line, at any
   viewport size ("渐变背景框的底就是设备的底 这是条铁律"), so nothing
   inside this section may push them apart. The breathing room the
   devices still need before the footer ("三台设备离着底部空出点距离
   来...直接怼在底部工具栏上不好看") comes from margin-bottom on the
   whole section instead — a gap of the page's own cream background
   between the gradient block ending and the footer starting, so the
   device-to-gradient-edge relationship stays untouched while the
   gradient-to-footer relationship gets its own separate space. */
/* Top padding + the gaps below (h2/paragraph/device-stage margins,
   further down) retuned to actually feel like the same page as the hero
   above it ("间距差的太大，不同一") — the hero's own rhythm is 56 (section
   top) → 20 (dots→heading) → 24 (heading→badge) → 32 (badge→tablet), all
   fairly tight; this section had drifted to 128/40/90, visibly heavier
   for no real reason once put side by side. Not copied number-for-number
   (this section opens a new block after a scroll, and the devices are a
   bigger visual than the hero's tablet, so somewhat more room is fine)
   but brought back into the same rough scale instead of 2-3x it. */
.odic-coming-soon{
	background:var(--odic-gradient-warm);padding:64px 24px 0;margin-bottom:80px;text-align:center;
}
/* Same fix, same reasoning: the three "coming soon" devices used to be
   three separately-sourced images hand-positioned with percentage
   left/top/width/height guesses. Figma already composites them as one
   overlapping group (node 2616:1373) — exporting that directly is both
   simpler and guaranteed to match the actual design instead of an
   approximation of it. */
/* Bug fixed: markup order was backwards — devices image before the
   "Coming soon" heading/copy, when Figma has the heading and copy first
   and the devices sitting below them. Reordered in the markup.
   Paragraph→devices gap re-measured against the Figma frame (unique
   node names — "Frame 161136", "Native apps are coming" — unlike the
   generic "Component 440" that turned out to be pulling the wrong
   instance for the dots-cluster icon above, so this one's trustworthy):
   90px, fixed rather than percentage since it's spacing from a *text*
   block, not sized relative to this image.
   Bug fixed: the devices were shown in full, leaving a bare gap of
   gradient background under them before the section ended — reported as
   "not aligned", should instead read as the devices getting truncated by
   the bottom of the gradient background, like the design has it. Figma's
   own numbers: the devices group is 449.6px tall but the gradient
   section only has 308px of room left under them before its own edge —
   so only the top ~68.5% of the actual device content is meant to show.
   Bug fixed: first attempt at the crop used that 68.5% straight against
   the Figma NODE's bounding box (586x449.6) — but the exported image file
   isn't tightly cropped to that box, it carries its own extra transparent
   canvas padding (measured the real file: 2372x680, with the actual
   device pixels only occupying y:64..678 of that — mostly at the bottom,
   almost none above). Sizing the wrapper off the node box instead of the
   real file left the image rendering far short of the wrapper's height,
   which read as a bare gap of gradient background before the footer
   instead of a crop. Re-derived against the real file: showing the top
   68.5% of the actual device pixels (not the canvas) works out to the
   top ~485px of the full 680px-tall file, i.e. aspect-ratio 2372/485.
   Bug fixed ("手机端的不对") — that 2372-wide crop kept the FULL source
   canvas width even though the actual devices (measured via GD, scanning
   the real alpha channel) only occupy x:502-1876 of it — the remaining
   ~42% was transparent dead space to the right of the devices. Harder to
   notice at a 900px desktop max-width, obvious once the same crop
   shrinks for mobile: the devices end up small and left-heavy inside a
   mostly-empty strip. webapp-coming-devices-tight.webp trims that dead
   horizontal space (keeping the same vertical 0-485 crop/truncation as
   before — that part was correct, it's what makes the devices read as
   sinking into the section's own bottom edge, per Figma); aspect-ratio
   below now matches that file's real 1414:485 exactly instead of the
   untrimmed canvas's 2372:485. */
/* margin-top 90→48 — same rhythm-unification pass as .odic-coming-soon's
   own comment above (this was one of the two gaps specifically flagged
   as out of step with the hero's tighter spacing). */
.odic-device-stage{max-width:900px;margin:48px auto 0;overflow:hidden;aspect-ratio:1414/485}
.odic-device-stage img{width:100%;display:block}
.odic-coming-soon h2{font-size:clamp(24px,4vw,32px);font-weight:600;margin:0 0 24px}
.odic-coming-soon p{color:var(--odic-text-soft);max-width:520px;margin:0 auto}
/* Bug fixed: the Figma design has "Web App" in this sentence as a real
   link treatment — blue, underlined — not just plain inherited text
   (the global a{color:inherit;text-decoration:none} reset was winning
   by default since nothing here overrode it for this specific link). */
.odic-coming-soon p a{color:var(--odic-accent);text-decoration:underline}
/* No separate mobile frame exists in Figma for this page, so these are a
   proportionally-scaled-down version of the real desktop measurements
   above, not independently measured — the tablet→section gap and the
   devices crop don't need their own mobile rule at all: both are already
   expressed as aspect-ratio/percentage of the image's own responsive
   width, so they scale down (and keep cropping the same fraction of the
   devices image) automatically at any screen size. */
@media (max-width:640px){
	.odic-webapp-hero{padding:40px 20px 0}
	.odic-webapp-hero__dots{width:44px;height:44px;margin-bottom:40px}
	.odic-webapp-hero h1{margin-bottom:32px}
	.odic-play-badge{margin-bottom:40px}
	.odic-play-badge img{height:40px}
	.odic-device-stage{max-width:380px;margin-top:32px}
	/* padding-bottom stays 0 here too, same iron rule as the desktop
	   rule's own comment — the gap before the footer is margin-bottom
	   below. Top padding matches the hero's own mobile value (40px) —
	   same rhythm-unification pass as the desktop rule's comment. */
	.odic-coming-soon{padding:40px 20px 0;margin-bottom:40px}
	.odic-coming-soon h2{margin-bottom:20px}
}

/* ========================================================================
   Support page
   ======================================================================== */
.odic-support-hero{padding:80px 24px 40px;text-align:center}
.odic-support-hero h1{font-size:clamp(30px,5vw,48px);font-weight:600;margin:0 0 12px}
.odic-support-hero p{color:var(--odic-text-soft);font-size:18px;max-width:560px;margin:0 auto}
.odic-support-form{max-width:950px;margin:0 auto;padding:0 24px 96px}
.odic-support-form label.odic-field-label{display:block;font-size:22px;font-weight:600;margin:36px 0 16px}
/* Topic picker restyled from pill capsules to the same rounded-rectangle
   field language as the inputs right below it, on request ("胶囊改为圆
   角矩形和下方填空的一样...你再拍拍版好看的") — a flex-wrap row of
   variable-width capsules read fine as pills, but next to flat bordered
   fields it looked like a leftover from a different design system. A
   fixed grid (5 desktop columns → exactly 2 tidy rows for these 10
   topics, narrowing at the breakpoints below) reads more like a
   deliberate form control than the old organic wrap, and a small radio
   dot on each option makes the "pick one" behaviour legible now that
   selection is a border+tint change rather than a bold colour swap. */
.odic-topic-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:12px}
.odic-topic{
	display:inline-flex;align-items:center;gap:10px;justify-content:flex-start;
	width:100%;height:48px;padding:0 18px;box-sizing:border-box;
	border:1px solid #dddddd;border-radius:8px;background:#f7f8fa;
	font-size:15px;font-weight:500;color:var(--odic-text);text-align:left;
	transition:border-color .15s ease,background-color .15s ease;
}
.odic-topic:hover{border-color:#ccc}
.odic-topic::before{
	content:"";flex:none;width:16px;height:16px;box-sizing:border-box;
	border-radius:50%;border:1.5px solid #ccc;background:#fff;
	transition:border-color .15s ease,background-color .15s ease;
}
.odic-topic.is-selected{border-color:var(--odic-accent);background:rgba(0,122,255,.06)}
.odic-topic.is-selected::before{
	border-color:var(--odic-accent);background:var(--odic-accent);
	box-shadow:inset 0 0 0 3px #f7f8fa;
}
@media (max-width:760px){.odic-topic-grid{grid-template-columns:repeat(3,1fr)}}
@media (max-width:520px){.odic-topic-grid{grid-template-columns:repeat(2,1fr)}}
/* Flat field style matched to the checkout rebuild (woocommerce/checkout/
   form-billing.php's .odic-checkout__section-body input.input-text) on
   request — "也改为checkout的那种输入格子和格式，要不前后不统一": same
   #F7F8FA background, 1px #DDDDDD border, 8px radius, 16px/500 text and
   accent-colour focus border, replacing the old glossy inset-shadow pill
   this section used to have its own bespoke reference for. */
.odic-support-form input[type=text],
.odic-support-form input[type=email],
.odic-support-form textarea{
	width:100%;box-sizing:border-box;
	border:1px solid #dddddd;border-radius:8px;background:#f7f8fa;
	padding:0 16px;font-size:16px;font-weight:500;font-family:inherit;color:var(--odic-text);
}
.odic-support-form input[type=text]:focus,
.odic-support-form input[type=email]:focus,
.odic-support-form textarea:focus{outline:none;border-color:var(--odic-accent)}
.odic-support-form input[type=text],
.odic-support-form input[type=email]{height:48px}
.odic-support-form textarea{min-height:168px;padding:14px 16px;resize:vertical}
.odic-attachments{display:flex;gap:16px;margin-top:16px;flex-wrap:wrap}
.odic-attachment-thumb{width:128px;height:128px;border-radius:var(--odic-radius-md);object-fit:cover}
.odic-attachment-add{
	width:128px;height:128px;border-radius:var(--odic-radius-md);background:var(--odic-pill);
	display:flex;align-items:center;justify-content:center;border:none;position:relative;
}
.odic-attachment-add img{width:24px;height:24px}
.odic-attachment-add input{position:absolute;inset:0;opacity:0;cursor:pointer}
.odic-support-submit{margin-top:56px;text-align:center}
/* Height/font unified with every other pill button sitewide (48px /
   16px / 500) — was 20px 0 padding at 18px/600, its own bespoke size,
   until the global standardization request overrode that. min-width
   kept so Send still reads as a deliberate, prominent submit rather
   than shrinking to its own text width. */
.odic-support-submit .odic-btn{min-width:280px}
.odic-form-notice{max-width:950px;margin:24px auto 0;padding:16px 24px;border-radius:16px;text-align:center}
.odic-form-notice--success{background:#e4f6ea;color:#1c7a37}
.odic-form-notice--error{background:#fde8e8;color:#b3261e}

/* ========================================================================
   WooCommerce — Shop / Product / Cart / Checkout / My Account
   ======================================================================== */
.odic-shop{max-width:var(--odic-container);margin:0 auto;padding:48px 24px 96px}
/* The product-detail page (woocommerce/content-single-product.php) is a
   full rewrite — original narrative copy and layout instead of a Figma
   port — so it renders its own full-bleed sections and opts out of
   .odic-shop's max-width/padding box entirely; each .odic-pdp-* section
   below manages its own width via .odic-wrap internally. */
body.single-product .odic-shop{max-width:none;padding:0;margin:0}
/* Bug fixed: it's actually .price (the wrapper span get_price_html()
   prints around .woocommerce-Price-amount) that WooCommerce's own
   stylesheet colors olive/yellow (~#958e09) by default, via
   ".woocommerce div.product span.price" — three classes + two type
   selectors. "color:inherit" on .woocommerce-Price-amount alone did
   nothing because inherit just re-fetches that same olive from its
   .price parent regardless of specificity; needs a real value, and
   enough specificity on .price itself (not just its child) to beat that
   selector. Tried the specificity route first — matching or beating
   3 classes + 2 type selectors from every context this prints in
   (hero/pricing-card/order/final) got unreadable fast for a one-line
   color reset, so !important here instead of chasing it further. */
.price{color:inherit!important}

/* ========================================================================
   Product detail page — original copy/layout, not a Figma port. Every
   section is full-bleed (so an alt background reads as a real band, not
   a centered card) with .odic-wrap/.odic-pdp-narrow as the only
   width-constrained layer inside. All rules are scoped under .odic-pdp-*
   specifically so nothing here can leak onto other pages' headings.
   ======================================================================== */
.odic-wrap{max-width:1120px;margin:0 auto;padding:0 32px}
.odic-pdp-narrow{max-width:720px;margin:0 auto;padding:0 32px}
.odic-pdp-alt{background:var(--odic-pdp-bg-alt)}
.center{text-align:center}

.odic-ph{
	background:repeating-linear-gradient(135deg,#eee,#eee 10px,var(--odic-pdp-border) 10px,var(--odic-pdp-border) 20px);
	border:1px solid var(--odic-pdp-border);border-radius:20px;
	display:flex;align-items:center;justify-content:center;color:#9a9689;font-size:13px;font-weight:500;
}
.odic-pdp-eyebrow{font-size:13px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--odic-accent);margin:0 0 16px}

.odic-pdp-hero{padding:120px 0 96px;text-align:center}
.odic-pdp-hero h1{font-size:76px;line-height:1.04;font-weight:700;letter-spacing:-.02em;margin:0 0 24px;color:#000}
.odic-pdp-lead{font-size:22px;line-height:1.5;color:var(--odic-text-soft);max-width:620px;margin:0 auto 40px}
/* Three real "sense of humor" examples, replacing a price/Order-now CTA
   that read as a sales push on what's meant to be a read-more page (see
   the comment in page-learn-more.php). Rows are left-aligned within a
   centered column — centering each row's text individually reads oddly
   once the sound-name/phrase lengths vary, a left-aligned block feels
   calmer to read as a list of three. */
.odic-pdp-hero .odic-ph{height:560px;max-width:960px;margin:56px auto 0}
/* Real photo test-fit — same box as the placeholder above (inherits its
   height/width/margin), just without the placeholder's striped
   background/border, which shouldn't show through/around a real image. */
.odic-pdp-hero__photo{overflow:hidden;border:none;background:none}
/* Bug fixed: the mobile <picture><source>...</picture> wrapper added for
   the mobile crop defaults to display:inline with no size of its own —
   that broke this parent's border-radius+overflow:hidden clip at the
   corners ("现在强制刷新后仍然是直角"), even though the <img> inside it
   was still correctly sized/clipped-looking everywhere except right at
   the rounded corners themselves. Forcing the wrapper to fill the same
   box as the image used to alone removes that gap between "what's
   clipped" and "what the parent's shape actually is". */
.odic-pdp-hero__photo picture{display:block;width:100%;height:100%}
.odic-pdp-hero__photo img{width:100%;height:100%;object-fit:cover;display:block}
/* Bug fixed: this used to have margin-left:20px left over from when it
   sat inline right next to an "Order now" button — a separate, later
   override (margin-left:0) was added below to fix it for the sound-
   list layout at the time, but same-specificity + later source order
   meant that 20px here silently kept winning the whole time. Both
   sounds list and CTA moved on since (the list relocated to the "02"
   feature row entirely), so this is just fixed directly now instead of
   patched with another override. */
.odic-pdp-cta-ghost{display:inline-block;margin-left:0;color:#1c1c1e;font-size:15px;font-weight:500;text-decoration:none;border-bottom:1px solid #1c1c1e}

/* Problem → fix, one continuous beat instead of two separate stops. */
.odic-pdp-insight{padding:112px 0}
.odic-pdp-insight h2{max-width:760px;margin:0 auto 20px;font-size:44px;line-height:1.12;font-weight:700;letter-spacing:-.01em}
.odic-pdp-insight p{max-width:640px;margin:0 auto;font-size:18px;line-height:1.6;color:var(--odic-text-soft)}
.odic-pdp-tags{display:flex;flex-wrap:wrap;justify-content:center;gap:12px;margin:40px auto 0;max-width:680px}
.odic-pdp-tags span{background:#fff;border:1px solid var(--odic-pdp-border);border-radius:50px;padding:10px 20px;font-size:14px;color:var(--odic-muted);text-decoration:line-through;text-decoration-color:#c7826b}
.odic-pdp-arrow{margin:64px auto;width:40px;height:40px;border-radius:50%;background:#fff;border:1px solid var(--odic-pdp-border);display:flex;align-items:center;justify-content:center;color:var(--odic-muted)}
.odic-pdp-insight__fix h2{max-width:700px}

/* Feature rows — alternating text/media position and background. */
.odic-story-row{padding:112px 0}
.odic-story-row__grid{display:grid;grid-template-columns:1fr 1fr;gap:96px;align-items:center}
.odic-story-row__grid.reverse .odic-story-row__text{order:2}
.odic-story-row__grid.reverse .odic-story-row__media{order:1}
.odic-story-row__eyebrow{font-size:13px;font-weight:600;color:var(--odic-muted);letter-spacing:.06em;text-transform:uppercase;margin:0 0 14px}
.odic-story-row__text h2,.odic-story-row__text h3{font-size:28px;line-height:1.2;font-weight:600;margin:0 0 14px}
.odic-story-row__text p{font-size:18px;line-height:1.6;color:var(--odic-text-soft)}
/* Three real "sense of humor" examples — moved here from the hero on
   request, next to the copy they actually demonstrate. Simpler than
   the hero's old version of this list: no centering tricks needed
   since .odic-story-row__text is already a normal left-aligned column. */
.odic-story-row__sounds{list-style:none;margin:20px 0 0;padding:0;display:flex;flex-direction:column;gap:14px}
.odic-story-row__sounds li{display:flex;align-items:flex-start;gap:12px;font-size:16px;line-height:1.5;color:var(--odic-text-soft)}
.odic-story-row__sounds strong{color:#1c1c1e;font-weight:600}
.odic-story-row__sound-icon{width:20px;height:20px;flex:none;margin-top:2px}
/* Original 80×80 SVG app icons, displayed directly with no added frame,
   background, border, radius or shadow. */
.odic-story-row__apps{margin-top:28px;display:flex;flex-direction:column;gap:10px}
.odic-story-row__apps-row{display:flex;flex-wrap:wrap;gap:10px}
.odic-app-icon{width:40px;height:40px;flex:none;object-fit:contain;display:block}
.odic-story-row--app-cards .odic-story-row__grid:not(.reverse){grid-template-columns:minmax(0,664px) minmax(0,472px);gap:24px}
.odic-story-row--app-cards .odic-story-row__text>h3{max-width:520px}
.odic-story-row__apps--cards{gap:9px;width:min(397.8px,calc((100vw - 160px)*.45));max-width:100%;margin-inline:auto}
.odic-story-row__apps--cards .odic-story-row__apps-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px}
.odic-story-row__apps--cards .odic-story-row__apps-row:last-child .odic-app-card:only-child{grid-column:1;justify-self:start;width:100%}
.odic-app-card{container-type:inline-size;position:relative;box-sizing:border-box;width:100%;aspect-ratio:512/144;height:auto;background:#fff;border-radius:4.6875% / 16.666667%;overflow:hidden}
.odic-app-card::after{content:"";position:absolute;left:.78125%;right:.78125%;top:2.7778%;bottom:2.7778%;box-sizing:border-box;border:.1953125cqi solid rgba(55,55,55,.10);border-radius:3.90625cqi;filter:blur(.09765625cqi);pointer-events:none}
.odic-app-card__icon{position:absolute;left:6.25%;top:22.2222%;width:15.625%;aspect-ratio:1;height:auto;object-fit:contain;display:block}
.odic-app-card__name{position:absolute;left:26.5625%;top:52.777778%;max-width:70.3125%;transform:translateY(-50%);color:#2c2c2c;font-size:6.25cqi;font-weight:400;line-height:1;letter-spacing:-.02em;white-space:nowrap}
.odic-app-card__icon[src$="figma-card-yahoo-calendar.svg"]{left:7.2265625%}
.odic-app-card__brand{position:absolute;right:4.6875%;bottom:16.6667%;width:6.25%;height:22.222222%;object-fit:contain}
@media (max-width:480px){
	.odic-app-icon{width:34px;height:34px}
	.odic-story-row__apps--cards{gap:9px;width:252px;max-width:100%}
	.odic-story-row__apps--cards .odic-story-row__apps-row{grid-template-columns:1fr;gap:9px}
	.odic-story-row__apps--cards .odic-story-row__apps-row:last-child .odic-app-card{grid-column:auto;width:100%}
}
@media (max-width:900px){
	.odic-story-row--app-cards .odic-story-row__grid:not(.reverse){grid-template-columns:1fr;gap:40px}
}
.odic-story-row__media .odic-ph{height:460px}
/* Real (non-placeholder) media in a square frame — used on the homepage's
   privacy/pricing rows, which reuse this same component with actual
   cropped photos instead of a placeholder box. */
.odic-story-row__media{aspect-ratio:1/1;border-radius:20px;overflow:hidden}
/* Bug fixed: this generic rule's specificity (class+type) beat
   .odic-feature-carousel__slide's (single class) regardless of source
   order, silently forcing object-fit back to cover on the carousel's
   slides no matter what that rule said. Scoped away from the carousel
   variant so it only touches the plain single-<img> case it was
   actually written for. */
.odic-story-row__media:not(.odic-story-row__media--carousel) img{width:100%;height:100%;object-fit:cover;display:block}
/* Carousel variant — the square/overflow-hidden treatment above moves
   down onto .odic-feature-carousel__stage instead, since the dot strip
   below needs to sit in normal flow under the square, not get clipped
   by it. */
.odic-story-row__media--carousel{aspect-ratio:auto;overflow:visible;border-radius:0}
.odic-feature-carousel{width:100%}
/* Push transition now, on request — matches the homepage hero
   carousel's own mechanism exactly (the "Born to win" slide pair,
   .odic-carousel__slide above), not the crossfade this used before.
   overflow:hidden moved back onto the stage since push needs a real
   clip boundary for the off-stage slide to disappear behind — safe to
   clip here because each slide's own shadow/margin is already baked
   inside its PNG's canvas edges (see main.js), well short of this
   box's own edge, so nothing gets cut off. Tilt is OFF (这四张图不要偏
   转了，就是静态的) — see the (disabled, values preserved for reuse)
   randomTilt block in main.js. */
/* border-radius added here (not just overflow:hidden) on request — on
   its own, section 04's plain rectangular AI photos kept their sharp
   corners while section 01's device-mockup slides already had rounding
   baked into their own artwork, so the two carousels read as
   inconsistent side by side (04 做的很好...他们两个板块的图片都要有全
   局一致的圆角). Same 20px radius as .odic-story-row__media elsewhere.
   Safe for 01's transparent PNGs too — their drop shadow sits well
   inside each canvas's own padding (confirmed when those were built),
   comfortably inside this radius, so nothing gets clipped. */
.odic-feature-carousel__stage{position:relative;width:100%;aspect-ratio:1/1;overflow:hidden;border-radius:20px;touch-action:pan-y;cursor:grab;user-select:none}
.odic-feature-carousel__stage:active{cursor:grabbing}
.odic-feature-carousel__card{position:relative;width:100%;height:100%}
/* Same translateX(-100%/0/100%) push + 1.9s cubic-bezier(.65,0,.35,1)
   ease as .odic-carousel__slide, and the same no-transition escape
   hatch for snapping the incoming slide to its starting edge before
   releasing it (see the goTo() comment on the hero carousel in
   main.js for why that snap-then-release two-step matters). */
/* object-fit:cover (was contain) on request — row 01's device-mockup
   slides sat noticeably smaller/inset than row 04's full-bleed photos
   at the same contain setting, since 01's own PNGs carry transparent
   shadow-margin inside their canvas and 04's don't. cover crops that
   margin away so both rows fill the frame the same way (图片做成和04
   一致大小) — the stage's own 20px radius above still clips it. */
.odic-feature-carousel__slide{
	position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
	transform:translateX(100%);
	transition:transform 1.9s cubic-bezier(.65,0,.35,1);
}
.odic-feature-carousel__slide.is-active{transform:translateX(0)}
.odic-feature-carousel__slide.is-prev{transform:translateX(-100%)}
.odic-feature-carousel__slide.is-next{transform:translateX(100%)}
.odic-feature-carousel__slide.no-transition{transition:none}
/* Overlaid on the card's own bottom edge now, not below the stage in
   normal flow (把进度条做到图片的底部，不要在图片的外围外边的底部) —
   sits on the slide's own light label-bar area, which already gives it
   plenty of contrast without needing a backing scrim. Same dot spec as
   the Order page's product gallery otherwise (6px dot → 32px active
   pill, 3px radius, 8px gap, #1c1c1e active). */
.odic-feature-carousel__dots{position:absolute;left:50%;bottom:22px;transform:translateX(-50%);z-index:2;width:max-content;display:flex;align-items:center;gap:8px}
.odic-feature-carousel__dots span{width:6px;height:6px;border-radius:3px;background:var(--odic-pdp-border);cursor:pointer}
.odic-feature-carousel__dots span.is-active{width:32px;background:#1c1c1e}

/* Icon+label+note list — same row shape as the PDP's specs cells (icon,
   16px label, 14px muted note), used wherever a story row's body is a
   short list of points instead of a paragraph. */
.odic-story-row__list{list-style:none;margin:20px 0 0;padding:0;display:flex;flex-direction:column}
/* Bug/layout fixed: every item on this page is now a single line (the
   <small> subtitle is gone — "精简一点，副标题不要，稍微加在主标题上"),
   so align-items:flex-start (tuned for a 2-line block, icon pinned to the
   first line) left the icon sitting visibly above-centre next to a lone
   line of text. Centered instead, and the icon's own +2px nudge (which
   only existed to match that first line's cap-height) is gone with it. */
.odic-story-row__list li{display:flex;align-items:center;gap:16px;padding:18px 0;border-bottom:1px solid var(--odic-border)}
.odic-story-row__list li:last-child{border-bottom:none}
.odic-story-row__list li img{width:24px;height:24px;flex:none;object-fit:contain}
/* Weight corrected back to 600 on request ("和上方的主标题对应起来") —
   700 (tried first) actually rendered BOLDER than the section's own h2
   right above it (.odic-story-row__text h2 is 28px/600), which reads
   backwards: a list item shouldn't out-weigh the heading that introduces
   it. 600 matches that h2 exactly, so this genuinely echoes it instead
   of just picking an arbitrary "heavier" number; kept at 17px (vs. the
   original 16px) since it's still carrying a full sentence alone now. */
.odic-story-row__list li strong{display:block;font-size:17px;font-weight:600;color:#1c1c1e;line-height:1.4}

/* 06 · Performance video row — deliberately NOT the 2-col
   .odic-story-row__grid used by 01-04: single centered column, text on
   top, the real device card underneath at its native 16:9 width instead
   of forced into the square carousel frame (see the PHP comment above
   this row's markup for why). 61.8% width on request (golden ratio). */
.odic-story-row--video .odic-wrap{display:flex;flex-direction:column;align-items:center;text-align:center}
.odic-story-row--video .odic-story-row__text{max-width:640px}
.odic-story-row--video .odic-story-row__stage{width:61.8%;margin:56px auto 0}
.odic-story-row--video .odic-device-card__screen video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;background:#000}
/* Click-to-pause/resume hint (main.js) — cursor:pointer only, no icon
   overlay added (not asked for); the tablet is the only clickable thing
   here besides the separate scrub bar below it. */
.odic-story-row--video .odic-device-card{cursor:pointer}
/* Custom scrub bar (page-learn-more.php's own comment has the full
   reasoning for why this isn't just <video controls>) — sits below the
   tablet mockup, not inside .odic-device-card, since that card's only
   sized by aspect-ratio with fully-absolute children; anything dropped
   inside it in normal flow would overlap the video instead of landing
   below it.
   Bug fixed ("靠平板太近了" / "加个胶囊的底...灰色的即可") — pulled
   further down from the tablet (margin-top 16→40) and given an actual
   grey capsule "base" (background+radius+shadow) instead of floating as
   a bare line directly on the cream page background, which read as
   unfinished. The capsule's own horizontal padding is what insets the
   track from its rounded ends; .odic-video-progress__track is the real
   click/drag target's visual line, sized to the capsule's full content
   width so the fill's percentage math stays simple (relative to the
   track, not the wider padded capsule). */
.odic-video-progress{
	position:relative;width:100%;margin-top:40px;padding:16px 20px;cursor:pointer;touch-action:none;
	background:#e8e8e6;border-radius:9999px;box-shadow:0 6px 16px rgba(0,0,0,.12);
	box-sizing:border-box;
}
.odic-video-progress__track{position:relative;width:100%;height:4px;border-radius:2px;background:#d3d3d0}
.odic-video-progress__fill{
	position:absolute;left:0;top:0;height:4px;width:0%;border-radius:2px;background:#1c1c1e;pointer-events:none;
}
/* The draggable "thumb" — a plain dot, matching how minimal every other
   custom control on this site is (the qty stepper is just −/+ text, no
   skeuomorphic chrome), rather than trying to reproduce a native
   scrubber's knob in detail. */
.odic-video-progress__fill::after{
	content:"";position:absolute;right:-6px;top:50%;transform:translateY(-50%);
	width:12px;height:12px;border-radius:50%;background:#1c1c1e;
	box-shadow:0 1px 3px rgba(0,0,0,.3);
}
@media (max-width:900px){
	.odic-story-row--video .odic-story-row__stage{width:100%;margin-top:40px}
}

.odic-pdp-facts{padding:64px 0;border-top:1px solid var(--odic-pdp-border);border-bottom:1px solid var(--odic-pdp-border)}
.odic-pdp-facts__row{display:flex;flex-wrap:wrap;justify-content:center;gap:0}
.odic-pdp-facts__item{flex:1 1 220px;text-align:center;padding:16px 24px;border-left:1px solid var(--odic-pdp-border)}
.odic-pdp-facts__item:first-child{border-left:none}
.odic-pdp-facts__item b{display:block;font-size:26px;font-weight:700;margin-bottom:6px}
.odic-pdp-facts__item span{font-size:14px;color:var(--odic-muted)}

.odic-pdp-specs{padding:120px 0}
.odic-pdp-specs h2{max-width:640px;margin:0 auto 64px;font-size:44px;line-height:1.12;font-weight:700;letter-spacing:-.01em}
.odic-pdp-specs__grid{display:grid;grid-template-columns:repeat(2,1fr);gap:1px;background:var(--odic-pdp-border);border:1px solid var(--odic-pdp-border);border-radius:24px;overflow:hidden}
.odic-pdp-specs__cell{background:var(--odic-bg);padding:36px 40px;display:flex;gap:18px}
.odic-pdp-icon{width:24px;height:24px;flex:none;margin-top:2px;color:#1c1c1e}
.odic-pdp-specs__cell h4{margin:0 0 10px;font-size:15px;font-weight:600;color:var(--odic-muted);text-transform:uppercase;letter-spacing:.06em}
.odic-pdp-specs__cell p{font-size:17px;color:#1c1c1e;line-height:1.5}

.odic-pdp-pricing{padding:120px 0;text-align:center}
.odic-pdp-pricing h2{max-width:640px;margin:0 auto 20px;font-size:44px;line-height:1.12;font-weight:700;letter-spacing:-.01em}
.odic-pdp-pricing>.odic-wrap>p{max-width:560px;margin:0 auto 40px;font-size:18px;line-height:1.6;color:var(--odic-text-soft)}
.odic-pdp-pricing-card{
	max-width:420px;margin:0 auto;background:#fff;border-radius:28px;padding:48px;
	box-shadow:0 30px 60px -30px rgba(0,0,0,.15);
}
.odic-pdp-pricing-card__amount{font-size:56px;font-weight:700;margin:0 0 6px}
.odic-pdp-pricing-card__note{font-size:14px;color:var(--odic-muted);margin:0 0 28px}
.odic-pdp-pricing-card ul{list-style:none;padding:0;margin:0 0 32px;text-align:left;display:flex;flex-direction:column;gap:14px}
.odic-pdp-pricing-card li{display:flex;gap:10px;font-size:15px;color:var(--odic-text-soft)}
.odic-pdp-pricing-card li::before{content:"✓";color:var(--odic-accent);font-weight:700}

/* Size — a plain pill toggle inside the pricing card (there's no photo
   gallery on this page any more for it to sit alongside). */
/* Bug fixed: justify-content:center was left over from an earlier design
   where this sat inside a centered pricing card on the Learn More page;
   that card no longer has a size selector at all (only the Order page
   does now, in its left-aligned panel), so centering here just pulled
   the SIZE row out of alignment with QUANTITY below it, which was never
   centered. */
/* SIZE stacks — label on its own line, pills on the line below — on
   request; QUANTITY (below) stays inline (label + stepper same row).
   Both labels still share the same font/tracking so they read as a
   matching pair despite the different layouts. */
.odic-pdp-size{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0 0 20px}
.odic-pdp-size__label{font-size:13px;font-weight:600;color:var(--odic-muted);text-transform:uppercase;letter-spacing:.06em}
.odic-pdp-size__pills{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
/* transition dropped — same Chromium quirk as .odic-login-continue
   (JS classList.toggle('is-selected', ...) in main.js), confirmed stuck
   here too: background stayed transparent after selecting a size. */
.odic-pdp-size__pill{
	position:relative;cursor:pointer;border:1px solid var(--odic-pdp-border);border-radius:50px;
	padding:8px 18px;font-size:14px;font-weight:600;color:#1c1c1e;
}
.odic-pdp-size__pill input{position:absolute;opacity:0;pointer-events:none}
.odic-pdp-size__pill.is-selected{background-color:#1c1c1e;border-color:#1c1c1e;color:#fff}

/* Pay — a real add-to-cart submit (see the PHP template + functions.php),
   not decorative markup. Black pill, matching every other CTA sitewide
   (was blue, #007AFF); the price sits inside the button itself so
   there's no cost surprise between "Pay" and the next screen. */
.odic-pay-form{margin:0}
/* Height/font unified with every other pill button sitewide (48px /
   16px / 500, matching the homepage's Order now/Learn more) — was 64px/
   18px/600, matched to the Figma frame's own literal size, until the
   global standardization request overrode that in favor of one
   consistent height everywhere. (odic-pay-btn--auto, the Google Play
   variant, was removed with that button's revert back to the stock
   badge image — see page-webapp.php.) */
/* Black capsule, matching Learn More/Google Play/Send/Continue, on
   request ("这吧蓝色按钮改了 pay") — was the accent-blue fill with a
   blue-tinted border/shadow/hover glow; all three re-tuned to neutral
   black/grey tones since they were built specifically around the old
   blue (a blue-tinted shadow under a black button reads as a mistake,
   not a highlight). Diagonal sheen dropped too, same reasoning as
   .odic-login-continue right above. */
.odic-pay-btn{
	display:flex;align-items:center;justify-content:center;gap:8px;
	width:100%;height:48px;padding:0 32px;border-radius:50px;border:none;
	background:#333;
	color:#fff;font-size:16px;font-weight:500;font-family:inherit;line-height:1;
	cursor:pointer;box-shadow:0 10px 24px -6px rgba(0,0,0,.45);
	transition:transform .15s ease,box-shadow .15s ease;text-decoration:none;
	box-sizing:border-box;
}
.odic-pay-btn:hover{transform:translateY(-1px);box-shadow:0 14px 28px -6px rgba(0,0,0,.5)}
.odic-pay-btn:active{transform:translateY(0)}
.odic-pay-btn__price{font-weight:600}
.odic-pay-trust{display:flex;align-items:center;justify-content:center;gap:8px;margin:16px 0 0;font-size:13px;color:var(--odic-muted)}
.odic-pay-trust img{width:16px;height:16px;flex:none;opacity:.7}
.odic-pdp-hero .odic-pay-btn,.odic-pdp-final .odic-pay-btn{display:inline-flex;width:auto}

.odic-pdp-roadmap{padding:96px 0}
.odic-pdp-roadmap h3{max-width:600px;margin:0 auto 14px;font-size:28px;line-height:1.2;font-weight:600}
.odic-pdp-roadmap p{max-width:560px;margin:0 auto;font-size:18px;line-height:1.6;color:var(--odic-text-soft)}

.odic-pdp-faq{padding:120px 0}
.odic-pdp-faq h2{margin:0 0 56px;font-size:44px;line-height:1.12;font-weight:700;letter-spacing:-.01em}
.odic-pdp-faq-item{border-top:1px solid var(--odic-pdp-border);padding:28px 0}
.odic-pdp-faq-item:last-child{border-bottom:1px solid var(--odic-pdp-border)}
.odic-pdp-faq-item summary{font-size:19px;font-weight:600;cursor:pointer;list-style:none;display:flex;justify-content:space-between;align-items:center;color:#1c1c1e}
.odic-pdp-faq-item summary::-webkit-details-marker{display:none}
.odic-pdp-faq-item summary::after{content:"+";font-size:22px;font-weight:400;color:var(--odic-muted)}
.odic-pdp-faq-item[open] summary::after{content:"–"}
.odic-pdp-faq-item p{margin:14px 0 0;max-width:640px;font-size:16px;line-height:1.6;color:var(--odic-text-soft)}

.odic-pdp-final{padding:140px 0;text-align:center}
.odic-pdp-final h2{max-width:600px;margin:0 auto 16px;font-size:44px;line-height:1.12;font-weight:700;letter-spacing:-.01em}
.odic-pdp-final p{max-width:480px;margin:0 auto 40px;font-size:18px;line-height:1.6;color:var(--odic-text-soft)}

@media (max-width:900px){
	.odic-pdp-hero h1{font-size:48px}
	.odic-pdp-insight h2,.odic-pdp-specs h2,.odic-pdp-pricing h2,.odic-pdp-faq h2,.odic-pdp-final h2{font-size:32px}
	.odic-story-row__grid{grid-template-columns:1fr;gap:40px}
	.odic-story-row__grid.reverse .odic-story-row__text{order:1}
	.odic-story-row__grid.reverse .odic-story-row__media{order:2}
	.odic-pdp-specs__grid{grid-template-columns:1fr}
	.odic-pdp-facts__item{flex:1 1 45%;border-left:none;border-top:1px solid var(--odic-pdp-border)}
}
@media (max-width:640px){
	/* Bottom padding pulled in from 64px ("这图和下一图的空挡太大了，
	   缩小点") and the photo's own top margin from the base 56px
	   ("上方的也有点大") — both scoped to just this hero section, not
	   the shared .odic-pdp-insight/etc padding below, which other
	   sections also rely on. */
	.odic-pdp-hero{padding:80px 0 32px}
	.odic-pdp-hero .odic-ph{margin-top:28px}
	.odic-pdp-insight,.odic-story-row,.odic-pdp-specs,.odic-pdp-pricing,.odic-pdp-roadmap,.odic-pdp-faq{padding:72px 0}
	.odic-pdp-final{padding:96px 0}
	.odic-pdp-cta-ghost{display:block;margin:16px 0 0}
}

/* ========================================================================
   Order page (woocommerce/content-single-product.php) — lean purchase-only
   layout: photo, name, size (real WC variations), quantity, Pay. The full
   story/FAQ content lives on page-learn-more.php instead.
   ======================================================================== */
.odic-pdp-order{padding:64px 0 96px}
.odic-pdp-order__grid{display:grid;grid-template-columns:1fr 1fr;gap:80px;align-items:start}
/* min-width:0 on the actual grid item (.odic-order-gallery, one level
   down, isn't the grid item itself and fixing it there alone did
   nothing) — needed once the thumbnail strip's flex row was added.
   Grid items default to min-width:auto, same min-content blowout flex
   items get: without this, .odic-pdp-order__media was sizing to fit
   the thumbnail strip's full un-wrapped width instead of the grid
   track's actual width, pushing the whole column (photo included)
   past the right edge of the viewport on mobile instead of letting the
   strip's own overflow-x:auto scroll internally like it's supposed to. */
.odic-pdp-order__media{min-width:0}
.odic-pdp-order__media .odic-ph{aspect-ratio:1/1;width:100%}

/* Product photo gallery — the 6 real Figma exports (frames "1"-"6"),
   back on the Order page now that it has a real photo slot again.
   Dots live outside .odic-order-gallery__track (the clipped square) in
   normal flow below the photo, not absolutely positioned inside it —
   gallery-1 (the "AI photo restore" mockup) has its own baked-in
   decorative dots near the bottom of the frame, so overlaying real
   functional dots on top of the image nearly doubled up on top of them.
   No transitions on .is-active toggles below (slide crossfade, dot
   width/color) — both are JS classList.toggle-driven, and a transition
   on a JS-toggled property gets stuck at its pre-toggle value in this
   Chromium build (full diagnosis on .odic-login-continue further down);
   instant snap instead, but correct every time. */
.odic-order-gallery{position:relative;width:100%}
.odic-order-gallery__track{position:relative;width:100%;aspect-ratio:1/1;background:#fff;border-radius:20px;overflow:hidden}
.odic-order-gallery__slide{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;pointer-events:none}
.odic-order-gallery__slide.is-active{opacity:1;pointer-events:auto}
/* Thumbnail strip replaces the old overlaid prev/next arrows + dot
   indicator, on request, matching a real reference (DJI's own store
   product gallery) — the strip is both indicator and navigation now,
   so a second dot indicator would just be redundant. Light-background
   version of the lightbox's own thumbnail strip further down (dark
   overlay there vs. plain white/cream page background here), same
   sizing/behavior otherwise. */
/* Bug fixed: overflow-x:auto with no scrollbar suppression left the
   browser's own native scroll thumb visible under the strip, which only
   fades out mid-drag and otherwise just sits there as a stray line
   ("只有拖动了他才会渐隐，不拖动他不消失，太bug了") — there are only a
   handful of thumbnails, not enough to need a scroll-position indicator
   at all, so it's suppressed outright here rather than restyled, same
   as every other scrollable strip in this file (.odic-order-bar__content,
   .odic-order-panel__timeline-viewport). */
.odic-order-gallery__thumbs{
	display:flex;gap:12px;margin:16px auto 0;overflow-x:auto;
	scrollbar-width:none;-ms-overflow-style:none;
}
.odic-order-gallery__thumbs::-webkit-scrollbar{display:none}
.odic-order-gallery__thumb{
	flex:none;width:64px;height:64px;padding:0;border-radius:12px;
	border:2px solid transparent;background:var(--odic-pill);cursor:pointer;
	overflow:hidden;opacity:.6;transition:opacity .15s ease,border-color .15s ease;
}
.odic-order-gallery__thumb img{width:100%;height:100%;object-fit:cover;display:block}
.odic-order-gallery__thumb:hover{opacity:.85}
.odic-order-gallery__thumb.is-active{opacity:1;border-color:#1c1c1e}

/* Magnifying-glass cursor on hover (desktop/mouse) — click opens the
   immersive fullscreen preview below, same as touch's tap-to-open. Used
   to be a cursor-tracked zoom lens instead (scaling the image in place);
   replaced on request with a real reference in hand (DJI's own store
   product gallery: hover shows a magnifier cursor, click opens a
   fullscreen preview with prev/next arrows and a thumbnail strip). */
@media (hover:hover) and (pointer:fine){
	.odic-order-gallery__track{cursor:zoom-in}
}

.odic-gallery-lightbox{
	position:fixed;inset:0;z-index:300;
	background:rgba(10,10,10,.92);
	display:flex;flex-direction:column;align-items:center;justify-content:center;
	touch-action:none;overflow:hidden;
}
.odic-gallery-lightbox[hidden]{display:none}
/* "Train car" track — every photo sits side by side at a fixed 100%
   spacing (see content-single-product.php's own comment for the full
   reasoning); the viewport is what used to be the single image's own
   max-width/max-height box, now clipping ("overflow:hidden") whichever
   one car-width slice is currently scrolled into view. */
.odic-gallery-lightbox__viewport{
	max-width:92vw;max-height:76vh;width:92vw;height:76vh;
	overflow:hidden;position:relative;
}
.odic-gallery-lightbox__track{
	display:flex;height:100%;
	transition:transform .32s ease;
	touch-action:none;
}
.odic-gallery-lightbox__img{
	flex:0 0 100%;width:100%;height:100%;object-fit:contain;
	touch-action:none;user-select:none;-webkit-user-drag:none;
}
/* Bottom-center on touch, not top-right — on request, so it doesn't sit
   where a pinch gesture's fingers usually land near the top corners.
   Round background per request ("圆底的即可"). Moves to the top-right
   corner on real mouse users (DJI's own placement) since there's no
   pinch gesture to avoid there. */
.odic-gallery-lightbox__close{
	position:absolute;left:50%;bottom:28px;transform:translateX(-50%);
	width:48px;height:48px;padding:0;
	border-radius:50%;border:none;background:rgba(255,255,255,.14);
	backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
	color:#fff;font-size:24px;line-height:1;cursor:pointer;
	display:flex;align-items:center;justify-content:center;
	z-index:1;
}
@media (hover:hover) and (pointer:fine){
	.odic-gallery-lightbox__close{left:auto;bottom:auto;top:28px;right:28px;transform:none}
}
/* Hidden on touch on request ("把叉号去掉吧...不要叉号 干净点") — tap
   anywhere to close is the touch gesture now (main.js), same reasoning
   as the prev/next arrows already being touch-hidden below: the button
   duplicates a gesture touch already has, more cleanly. */
@media (hover:none){.odic-gallery-lightbox__close{display:none}}
/* Prev/next — hidden on touch (swipe/pinch is the touch interaction,
   same as the small inline gallery hides its own arrows there too). */
.odic-gallery-lightbox__arrow{
	position:absolute;top:50%;transform:translateY(-50%);width:56px;height:56px;
	border-radius:50%;border:none;background:rgba(255,255,255,.14);
	backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
	color:#fff;font-size:26px;line-height:1;cursor:pointer;
	display:flex;align-items:center;justify-content:center;
	z-index:1;
}
.odic-gallery-lightbox__arrow--prev{left:28px}
.odic-gallery-lightbox__arrow--next{right:28px}
@media (hover:none){.odic-gallery-lightbox__arrow{display:none}}
.odic-gallery-lightbox__thumbs{
	display:flex;gap:12px;margin-top:24px;padding:0 24px;max-width:92vw;overflow-x:auto;
	scrollbar-width:none;-ms-overflow-style:none;
}
.odic-gallery-lightbox__thumbs::-webkit-scrollbar{display:none}
.odic-gallery-lightbox__thumb{
	flex:none;width:64px;height:64px;padding:0;border-radius:12px;
	border:2px solid transparent;background:rgba(255,255,255,.08);cursor:pointer;
	overflow:hidden;opacity:.6;transition:opacity .15s ease,border-color .15s ease;
}
.odic-gallery-lightbox__thumb img{width:100%;height:100%;object-fit:cover;display:block}
.odic-gallery-lightbox__thumb:hover{opacity:.85}
.odic-gallery-lightbox__thumb.is-active{opacity:1;border-color:#fff}
@media (max-width:640px){.odic-gallery-lightbox__thumbs{display:none}}
.odic-pdp-order__panel{padding-top:8px}
.odic-pdp-order__panel h1{font-size:40px;font-weight:600;margin:0 0 10px;color:#000}
.odic-pdp-order__tagline{font-size:17px;color:var(--odic-text-soft);margin:0 0 18px;line-height:1.5}
/* Bug fixed: 32px/700 here was a guess, not measured — the real Figma
   price text (node 2905:1818's frame, the "$799" on the original 购物
   page) is 24px/500. Also tightened the margins on this whole block on
   request, so the purchase panel's core (title → price → delivery →
   size → quantity → Pay) comes in closer to the same height as the
   photo next to it — the trust line / includes / learn-more links below
   the button are explicitly fine extending past that. */
.odic-pdp-order__price{font-size:24px;font-weight:500;margin:0 0 6px;color:#1c1c1e}
.odic-pdp-order__delivery{font-size:14px;color:var(--odic-muted);margin:0 0 24px}
.odic-pdp-order__includes{font-size:14px;color:var(--odic-muted);margin:20px 0 0}
.odic-pdp-order__learnmore{display:inline-block;margin-top:16px;font-size:14px;color:var(--odic-accent);text-decoration:none;border-bottom:1px solid transparent}
.odic-pdp-order__learnmore:hover{border-bottom-color:var(--odic-accent)}

.odic-pdp-qty{display:flex;align-items:center;gap:14px;margin:0 0 32px}
.odic-pdp-qty__label{font-size:13px;font-weight:600;color:var(--odic-muted);text-transform:uppercase;letter-spacing:.06em;min-width:76px}
.odic-pdp-qty__stepper{display:flex;align-items:center;border:1px solid var(--odic-pdp-border);border-radius:50px;overflow:hidden}
.odic-pdp-qty__btn{width:36px;height:36px;border:none;background:transparent;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#1c1c1e;font-family:inherit}
.odic-pdp-qty__btn:hover{background:var(--odic-pdp-bg-alt)}
.odic-pdp-qty__stepper input{width:40px;text-align:center;border:none;background:transparent;font-size:15px;font-weight:600;font-family:inherit;-moz-appearance:textfield}
.odic-pdp-qty__stepper input::-webkit-outer-spin-button,.odic-pdp-qty__stepper input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}

@media (max-width:900px){
	.odic-pdp-order__grid{grid-template-columns:1fr;gap:32px}
}
@media (max-width:640px){
	.odic-pdp-order{padding:40px 0 64px}
	.odic-pdp-order__panel h1{font-size:32px}
}

.odic-order-card{background:#fff;border-radius:var(--odic-radius-md);padding:24px;box-shadow:0 4px 20px rgba(0,0,0,.05)}
.odic-order-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:20px;margin-top:20px}
.odic-order-status-head{
	background:var(--odic-pill);border-radius:16px;padding:16px 24px;display:flex;
	align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-bottom:24px;font-size:14px;
}

/* Centers the login card in the space between the fixed header and the
   footer (which body's own flex column + .odic-footer's margin-top:auto
   already pin to the bottom of the viewport) instead of it just sitting
   wherever it fell in normal document flow. flex:1 so it actually claims
   that leftover space to center within, rather than shrinking to fit its
   own content and leaving the centering nowhere to happen. */
.odic-account-main--centered{flex:1;display:flex;align-items:center;justify-content:center}
.odic-account-main--centered .odic-account{padding:24px}
.odic-account{max-width:var(--odic-container);margin:0 auto;padding:64px 24px 96px}
.odic-account h1{text-align:center;font-size:32px;margin-bottom:40px}
.woocommerce-form-register{
	max-width:480px;margin:0 auto;background:#fff;border-radius:var(--odic-radius-lg);
	padding:48px;box-shadow:0 20px 60px -20px rgba(0,0,0,.15);
}
.woocommerce-form-register p{margin:0 0 18px}
.woocommerce-form-register input{
	width:100%;border:none;border-radius:var(--odic-radius-pill);background:var(--odic-pill);
	padding:16px 24px;font-size:16px;font-family:inherit;
}
.woocommerce-form-register button{
	width:100%;border:none;border-radius:var(--odic-radius-pill);
	background:var(--odic-accent);color:#fff;padding:16px;font-size:16px;font-weight:600;
}

/* ========================================================================
   Login — 1:1 from Figma "Login" (node 2616:1415) + its email/OTP states
   (2616:1416, 2616:2061, 2616:1954, 2616:2008). Sizes/colors/spacing below
   are all read from those frames, not eyeballed — see form-login.php for
   which two spelling typos ("Cotinue", "verfication") were corrected and
   which two hidden Figma layers were skipped as dead/legacy.
   ======================================================================== */
.odic-login-card{
	position:relative;max-width:800px;margin:0 auto;padding:56px 0 0;
	border-radius:32px;overflow:hidden;
	background:rgba(255,255,255,.64);backdrop-filter:blur(30px);-webkit-backdrop-filter:blur(30px);
	box-shadow:0 0 70px rgba(0,0,0,.1);
}
.odic-login-card__head{position:relative;text-align:center;padding:0 40px}
/* .odic-account .odic-login-card h1 (2 classes) rather than relying on
   source order to beat .odic-account h1's own (0,1,1) specificity. */
.odic-account .odic-login-card h1{margin:0;font-size:24px;font-weight:600;color:#2c2c2c}

/* The two inset shadows (dark bottom-right, light top-left, both 1px)
   are real — Figma has them on this exact rectangle, a subtle bevel/
   recessed-field look that a flat background didn't have before. */
.odic-login-field{
	display:flex;align-items:center;gap:14px;margin:64px 32px 0;padding:0 24px;
	height:48px;border-radius:50px;background:var(--odic-pill);
	box-shadow:inset 0 .5px 1px rgba(0,0,0,.5),inset -1px -1px 1px rgba(255,255,255,.5);
}
/* Bug fixed: forced into a 24x24 square, distorting the SVG's own
   native ~35:23 viewBox (real Figma size, node 2616:1954, is 34x22 —
   confirmed via the API, not eyeballed) — squashing it also made the
   lock icon below read as disproportionately larger than it really is
   in the design.
   Shrunk further on request, on top of that fix — sized down to a 20px
   height (was 22px) with width following its own 34:22 ratio (≈31px),
   not a forced square, so it still doesn't distort. See
   .odic-login-otp__lock below for the matching height-based resize. */
.odic-login-field__icon{width:31px;height:20px;flex:none}
.odic-login-field__input{
	flex:1;min-width:0;border:none;background:none;font-family:inherit;font-size:16px;color:#1c1c1e;
	padding:0;outline:none;
}
.odic-login-field__input::placeholder{color:#888}
.odic-login-field__clear{width:24px;height:24px;flex:none;padding:0;border:none;background:none;cursor:pointer}
.odic-login-field__clear img{width:24px;height:24px;display:block}

.odic-login-action{margin:32px 32px 0}

/* Continue — gray/disabled until the email field holds something that
   parses as a real address (main.js), then blue with a soft shadow. */
/* Bug fixed: any property named in this rule's own "transition" list
   could end up permanently stuck at its disabled-state value once
   .disabled is toggled off via JS (input event → continueBtn.disabled =
   false) — which one got stuck seemed to vary between reloads (moving
   background-color out of the list fixed background-color but then
   color got stuck instead), so this isn't one property's bug, it's
   Chromium mishandling a transition on a <button> across a JS-driven
   disabled toggle in general. Not even an inline
   background-color:red!important could override the stuck value while
   it was happening. Dropping the transition entirely (instant swap,
   no animation) is the only combination that came back correct on
   every reload. */
/* Bug fixed: flat colors before — Figma has a soft diagonal gloss
   (a white-to-transparent-to-white sheen, same technique as .odic-pay-btn
   elsewhere in this theme) layered over the base color on both states,
   not just a plain fill. background-image rather than the "background"
   shorthand so it can't collide with the background-color longhand this
   rule already needed for the disabled-toggle transition bug above. */
/* Height/font unified with every other pill button sitewide (48px /
   16px / 500, matching the homepage's Order now/Learn more) — was 64px/
   18px/600, matched to the Figma frame's own literal size, until the
   global standardization request overrode that in favor of one
   consistent height everywhere. */
.odic-login-continue{
	width:100%;height:48px;border:none;border-radius:50px;font-family:inherit;font-size:16px;font-weight:500;
	background-color:#ddd;
	background-image:linear-gradient(135deg,rgba(255,255,255,.5) 0%,rgba(255,255,255,0) 18%,rgba(255,255,255,.35) 83%);
	color:#bbb;cursor:not-allowed;
}
/* Black capsule, matching Learn More/Google Play/Support Send, on
   request ("Continue按钮...改为胶囊黑色") — was the accent-blue fill.
   Gradient sheen dropped too (it was tuned as a highlight over the
   blue, and just looks like an odd patch over a flat black). */
.odic-login-continue:not(:disabled){
	background-color:#333;background-image:none;
	color:#fff;cursor:pointer;
	box-shadow:0 4px 9px rgba(0,0,0,.13);
}

/* Bug fixed: the [hidden] attribute's implicit display:none was getting
   beaten by this rule's own explicit display:flex (a direct property
   declaration always wins over the UA stylesheet's attribute-selector
   default) — the OTP row was showing immediately instead of staying
   hidden until Continue is clicked. [hidden] here forces it back off. */
/* Bug fixed: the lock sat flush at .odic-login-action's own 32px
   margin, while the envelope icon above sits 24px further in (the
   email field's own internal padding) — a 24px left-edge mismatch that
   read as sloppy/uneven. padding-left here shifts the whole row in by
   that same 24px so the lock lines up with the envelope exactly,
   matching how close they already sit in the real Figma frame
   (2616:1954 measures them within 3px of each other). Lock's own size
   (27x32) was already correct/verified against that frame — it just
   looked oversized next to the envelope's previously-distorted 24x24
   box; see .odic-login-field__icon above for that fix.
   Shrunk further on request, alongside the envelope — 20px height (was
   32px) with width following its own 27:32 ratio (≈17px), matching the
   envelope's new height so the two read as one consistent icon scale
   without distorting either one's real proportions. */
.odic-login-otp{display:flex;align-items:center;gap:20px;flex-wrap:wrap;padding-left:24px}
.odic-login-otp[hidden]{display:none}
.odic-login-otp__lock{width:17px;height:20px;flex:none}
.odic-login-otp__boxes{display:flex;gap:24px}
/* background left out of the transition list — same Chromium quirk as
   .odic-login-continue above (a JS-driven state change, classList.toggle
   here rather than the disabled property, but the same class of bug:
   background stuck at its pre-toggle value). box-shadow still animates
   fine since it isn't part of a JS-toggled property. */
.odic-login-otp__box{
	width:40px;height:40px;border-radius:10px;border:none;background-color:#ddd;
	text-align:center;font-family:inherit;font-size:24px;font-weight:600;color:#fff;
	outline:none;transition:box-shadow .15s ease;caret-color:transparent;
}
.odic-login-otp__box.is-filled{background-color:var(--odic-pill-active)}
.odic-login-otp__box:focus{box-shadow:0 0 0 2px var(--odic-pill-active)}
.odic-login-otp__resend{margin-left:auto;font-size:16px;color:#1c1c1e}
.odic-login-resend-btn{border:none;background:none;padding:0;font-family:inherit;font-size:16px;color:var(--odic-pill-active);cursor:pointer;text-decoration:none}
.odic-login-resend-btn:hover{text-decoration:underline}

.odic-login-divider{height:1px;background:var(--odic-border);margin:64px 0 0}
.odic-login-sso{display:flex;align-items:center;justify-content:center;gap:128px;padding:24px 40px}
/* Bug fixed: these were bare near-transparent circles with no button
   chrome at all — Figma actually gives each a real glass-button
   treatment (backdrop blur + soft drop shadow), which read as "there's
   a button here" the flat version didn't. Google/Apple share one
   shadow/blur size; Microsoft's is tighter (radius 15 vs 30, blur 2 vs
   4) AND — this is the one that was flagged as a missed gradient —
   carries its own soft rainbow gradient wash at 20% opacity behind the
   logo, the other two don't have any color gradient at all. */
.odic-login-sso__btn{
	width:64px;height:64px;border-radius:50%;border:none;cursor:pointer;
	display:flex;align-items:center;justify-content:center;
	background-color:rgba(0,0,0,.004);
	backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);
	box-shadow:0 0 30px rgba(0,0,0,.1);
}
.odic-login-sso__btn--ms{
	backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);
	box-shadow:0 0 15px rgba(0,0,0,.1);
	background-image:linear-gradient(160deg,
		rgba(218,104,224,.2) 0%,
		rgba(84,210,213,.2) 20%,
		rgba(123,197,118,.2) 40%,
		rgba(230,170,0,.2) 60%,
		rgba(230,124,76,.2) 80%,
		rgba(200,111,103,.2) 100%);
}
.odic-login-sso__btn img{width:26px;height:26px;display:block}
/* Bug fixed: opacity:.5 here was my own addition, not in the Figma design
   — all three SSO icons render at full, vibrant color there even though
   they're not wired to real OAuth yet. Dimming them was a "this doesn't
   work yet" signal I invented; the design just doesn't show one. Kept
   the real disabled attribute (still not clickable) and cursor, dropped
   the opacity. */
.odic-login-sso[aria-disabled="true"] .odic-login-sso__btn{cursor:not-allowed}

@media (max-width:640px){
	.odic-login-card{margin:0 24px;padding-top:40px}
	.odic-login-card__head{padding:0 24px}
	.odic-login-field{margin:40px 20px 0;padding:0 18px}
	.odic-login-action{margin:24px 20px 0}
	/* Same lock/envelope alignment fix as desktop, recalculated for this
	   breakpoint's own smaller field padding (18px vs 24px). */
	.odic-login-otp{gap:14px;padding-left:18px}
	.odic-login-otp__boxes{gap:10px}
	.odic-login-otp__box{width:34px;height:34px;font-size:20px}
	.odic-login-otp__resend{width:100%;margin:16px 0 0;text-align:center}
	.odic-login-divider{margin-top:40px}
	.odic-login-sso{gap:48px;padding:20px 24px}
}

/* ========================================================================
   Legal pages — Privacy Policy, Terms
   Layout/spacing only: no font-size, font-family or font-weight changes.
   ======================================================================== */
.odic-legal{max-width:920px;margin:0 auto;padding:80px 24px 120px}
.odic-legal__card{
	background:#fff;border-radius:28px;box-shadow:var(--odic-shadow-soft);
	padding:64px 72px 72px;
}
.odic-legal__head{margin:0 0 40px;padding-bottom:32px;border-bottom:1px solid var(--odic-border)}
.odic-legal__head h1{margin:0}
.odic-legal__body{max-width:640px}
.odic-legal__body > *:first-child{margin-top:0}
.odic-legal__body p{color:var(--odic-text-soft);line-height:1.9;margin:0 0 20px}
.odic-legal__body h2{
	margin:52px 0 18px;padding-top:36px;border-top:1px solid var(--odic-border);
}
.odic-legal__body ul{list-style:none;margin:0 0 20px;padding:0}
.odic-legal__body li{
	position:relative;padding-left:24px;margin-bottom:12px;
	color:var(--odic-text-soft);line-height:1.9;
}
.odic-legal__body li::before{
	content:"";position:absolute;left:2px;top:0.8em;
	width:6px;height:6px;border-radius:50%;background:var(--odic-accent);
}
.odic-legal__body a{
	color:var(--odic-accent);text-decoration:none;
	border-bottom:1px solid rgba(0,122,255,.35);transition:border-color .15s ease;
}
.odic-legal__body a:hover{border-bottom-color:var(--odic-accent)}

@media (max-width:760px){
	.odic-legal{padding:48px 16px 72px}
	.odic-legal__card{padding:36px 24px 44px;border-radius:20px}
	.odic-legal__head{margin-bottom:28px;padding-bottom:24px}
	.odic-legal__body{max-width:none}
	.odic-legal__body h2{margin:40px 0 14px;padding-top:28px}
}

/* ========================================================================
   Checkout — classic [woocommerce_checkout] shortcode (switched from the
   Gutenberg Checkout block on request: "把checkout删除，然后仿照 DJI
   [store.dji.com/checkout] 做一个适合我们的。先复刻，而后我们再改").
   Two-column DJI-style layout: Shipping Address + Payment Method stacked
   in the main column (woocommerce/checkout/form-checkout.php,
   form-billing.php, payment.php — all overridden in this theme), a
   sticky Order Summary + Place Order in the right sidebar. No separate
   Shipping Method step — this store has nothing to choose between.
   Payment Method starts locked/greyed until Shipping Address is saved
   (assets/js/checkout-gating.js), matching DJI's own gated-section flow
   ("填写了...保存了，下方的...Payment Method才会从灰色变为正常色").
   ======================================================================== */
/* Bug fixed: body is display:flex (the sticky-footer pattern used
   sitewide), and this <main> — a flex item with no explicit width,
   only max-width — collapsed to just its own horizontal padding (48px)
   instead of the available width, since nothing forced it to grow.
   width:100% is the fix; max-width still caps it on wide screens. */
.odic-checkout-page{width:100%;max-width:var(--odic-container);margin:0 auto;padding:64px 24px 120px;box-sizing:border-box}
.odic-checkout-page h1{font-size:40px;font-weight:700;letter-spacing:-.01em;margin:0 0 40px;color:var(--odic-text)}

.odic-checkout__grid{display:grid;grid-template-columns:1fr 400px;gap:56px;align-items:start;width:100%}
.odic-checkout__main{width:100%;min-width:0;box-sizing:border-box;display:flex;flex-direction:column;gap:32px}

.odic-checkout__section{
	background:#fff;border-radius:var(--odic-radius-md);padding:32px;
	box-shadow:var(--odic-shadow-soft);transition:opacity .2s ease;
}
.odic-checkout__section-title{
	font-size:20px;font-weight:600;color:var(--odic-text);margin:0 0 20px;font-family:inherit;
}

/* Payment Method starts locked/greyed out until Shipping Address is
   saved — checkout-gating.js toggles this class off and re-enables the
   fields inside (native `disabled` on inputs, not just pointer-events,
   so a locked card's fields can never be submitted by mistake). */
.odic-checkout__section--locked{opacity:.45;pointer-events:none}

/* Text inputs / selects (classic form_field markup: .form-row wraps a
   label + .woocommerce-input-wrapper) — flat #F7F8FA fields, 1px
   #DDDDDD border, 8px radius, 48px tall, matching the site's existing
   checkout-field reference (Figma node 2978:8533). form-row-first/-last
   pair into 2-up rows via the woocommerce_checkout_fields filter
   (functions.php) that assigns those classes, same DJI row pairing
   (name/name, city/state, postcode/phone). */
.odic-checkout__section-body .form-row{margin:0 0 16px}
.odic-checkout__section-body .form-row-first,
.odic-checkout__section-body .form-row-last{width:calc(50% - 8px)}
.odic-checkout__section-body .form-row-first{float:left;clear:left}
.odic-checkout__section-body .form-row-last{float:right;clear:right}
.odic-checkout__section-body .form-row-wide{clear:both}
.odic-checkout__section-body .woocommerce-billing-fields__field-wrapper::after{content:"";display:block;clear:both}
/* "Billing address same as shipping address" toggle + its revealed
   sub-form — sits below the one address form above, so a light top
   border reads as a new (optional) block rather than more of the same
   fields ("收货地址最后一行加上与账单地址一致的选项"). */
.odic-billing-same-row{
	clear:both;margin:8px 0 0!important;padding-top:20px;
	border-top:1px solid var(--odic-border);
}
.odic-billing-same-row label{display:flex!important;align-items:center;gap:10px;font-size:14px;font-weight:500;color:var(--odic-text);cursor:pointer}
/* Radio-style circle glyph, not a hand-drawn shape — on request ("你可
   以找找现成的代码库直接调用，最好不要自己画" / "用字符也行"). "○"
   (U+25CB White Circle) unchecked, "◉" (U+25C9 Fisheye — a ring with a
   dot already baked into the one glyph) checked, exactly the ring+dot
   look asked for, not a fully-filled disc. appearance:none makes this
   checkbox a non-replaced element in current Chrome/Firefox/Safari,
   which is what lets ::before content render on an <input> at all here
   — still a real, focusable, screen-reader-announced checkbox
   underneath, just with a character instead of the native tick box. */
.odic-billing-same-row input[type="checkbox"]{
	appearance:none;-webkit-appearance:none;-moz-appearance:none;
	width:18px;height:18px;flex:none;margin:0;cursor:pointer;
	border:none;background:none;
	display:inline-flex;align-items:center;justify-content:center;
	font-size:18px;line-height:1;color:#ccc;
}
/* Bug fixed: "○" and "◉" are NOT drawn at the same visual size by fonts
   at a shared font-size — "○" (White Circle) renders as a noticeably
   smaller ring inside its own em-box than "◉" (Fisheye) does at the
   identical font-size ("这个怎么这么小...他俩明显不一样大"), a real
   metrics quirk of these two glyphs, not a sizing mistake in the CSS.
   First fix (bumping ::before's own font-size to 32px) matched the
   diameter but broke vertical centering — a bigger font-size also grows
   the glyph's line box, and this particular glyph's ink sits low within
   that taller box, so it visually dropped below the label text
   ("位置又偏了"). transform:scale() instead enlarges the glyph visually
   without changing its layout box at all, so the flex centering that
   already centers it at 18px keeps working unchanged. */
.odic-billing-same-row input[type="checkbox"]::before{content:"\25CB";display:inline-block;transform:scale(1.78)}
.odic-billing-same-row input[type="checkbox"]:checked{color:var(--odic-accent)}
.odic-billing-same-row input[type="checkbox"]:checked::before{content:"\25C9";transform:none}
.odic-billing-fields{margin-top:20px}
.odic-billing-fields::after{content:"";display:block;clear:both}
.odic-checkout__section-body label{
	display:block;font-size:14px;font-weight:500;color:var(--odic-muted);margin:0 0 6px;
}
.odic-checkout__section-body label .required{color:#e5484d;text-decoration:none}
.odic-checkout__section-body .woocommerce-input-wrapper{display:block}
.odic-checkout__section-body input.input-text,
.odic-checkout__section-body select{
	width:100%;height:48px;box-sizing:border-box;
	border:1px solid #dddddd;border-radius:8px;background:#f7f8fa;
	padding:0 16px;font-size:16px;font-weight:500;font-family:inherit;color:var(--odic-text);
}
.odic-checkout__section-body input.input-text:focus,
.odic-checkout__section-body select:focus{outline:none;border-color:var(--odic-accent)}
/* select2 (WC's country/state enhanced-select JS) renders its own
   markup over the <select> — matched to the same flat field language
   so it doesn't stand out as a different control. */
.odic-checkout__section-body .select2-container--default .select2-selection--single{
	height:48px;border:1px solid #dddddd;border-radius:8px;background:#f7f8fa;
}
.odic-checkout__section-body .select2-container--default .select2-selection--single .select2-selection__rendered{
	line-height:48px;padding:0 16px;font-size:16px;font-weight:500;color:var(--odic-text);
}
.odic-checkout__section-body .select2-container--default .select2-selection--single .select2-selection__arrow{height:46px;right:8px}

.odic-checkout__section-body .form-row.odic-invalid input.input-text,
.odic-checkout__section-body .form-row.odic-invalid select{border-color:#e5484d}

.odic-checkout__save-row{clear:both;padding-top:4px}
/* Grey/disabled-looking until every required field is filled, then a
   black capsule (matching .odic-btn--hero-dark elsewhere) once ready —
   on request ("没填写好时是灰色的，填写好了就要变成黑色的胶囊"). Still
   a real, clickable button either way (not the disabled attribute) —
   clicking while grey focuses the first empty field instead of doing
   nothing, same as before this change (checkout-gating.js). Same grey
   tone as .odic-login-continue's own disabled state, for consistency. */
/* Full width + centered, matching Place Order's own pill proportions
   one column over, on request ("SAVE放到中间 和 Place order的胶囊一致
   宽度也行？") — was sized to its own text and left-aligned in the row. */
.odic-checkout__save-address-btn{width:100%;height:44px;padding:0 28px;font-size:15px;background:#ddd;color:#bbb}
.odic-checkout__save-address-btn.is-ready{background:#333;color:#fff}

/* Saved-address summary + Edit link — shown in place of the field
   wrapper once Save Address is clicked (checkout-gating.js toggles
   [hidden] on each; the fields keep their real posted values, this is
   a read-only preview layered on top). */
.odic-checkout__address-summary{
	display:flex;align-items:flex-start;justify-content:space-between;gap:16px;
	border:1px solid #dddddd;border-radius:8px;background:#f7f8fa;
	padding:20px 24px;margin-top:8px;
}
/* Bug fixed: same class of bug documented elsewhere in this file (e.g.
   .odic-gallery-lightbox, .odic-login-otp) — this rule's own explicit
   display:flex beats the [hidden] attribute's implicit display:none, so
   the Edit box was showing up front, before Shipping Address had ever
   been saved once, with an empty summary and the full form still open
   above it at the same time ("这个EDIT的逻辑不对...没有出现EDIT的必要").
   [hidden] here forces it back off until checkout-gating.js's
   saveAddress() actually un-hides it. */
.odic-checkout__address-summary[hidden]{display:none}
.odic-checkout__address-summary-text{font-size:15px;color:var(--odic-text-soft);line-height:1.6}
.odic-checkout__edit-link{
	flex:none;background:none;border:none;padding:0;cursor:pointer;
	color:var(--odic-accent);font-weight:500;font-family:inherit;font-size:14px;
}

/* Payment gateway list (classic .wc_payment_methods / .payment_box —
   same flat #DDDDDD/8px card language as the fields above). WC hides
   the radio input automatically when only one gateway is available
   (checkout.js) — this site only has Card, no PayPal, so the list
   renders as a single unlabelled section in practice. */
.odic-checkout__section-body ul.wc_payment_methods{list-style:none;margin:0;padding:0}
.odic-checkout__section-body li.wc_payment_method{margin-bottom:12px}
.odic-checkout__section-body li.wc_payment_method label{
	display:inline;font-size:16px;font-weight:500;color:var(--odic-text);margin:0 0 12px;
}
.odic-checkout__section-body .payment_box{
	border:1px solid #dddddd;border-radius:8px;background:#f7f8fa;
	padding:20px 24px;margin-top:12px;font-size:14px;color:var(--odic-muted);
}
.odic-checkout__section-body .payment_box::before{display:none}

/* Order Summary sidebar. Bug fixed: wasn't actually following the
   screen on desktop no matter how far down Shipping Address/Payment
   Method scrolled ("Order Summary这里电脑端应该跟着屏幕在右上角的...
   他都应该在右侧显示才对") — position:sticky needs room to move WITHIN
   its own containing block, but .odic-checkout__grid's align-items:start
   (needed so the sidebar doesn't stretch to some arbitrary stretched
   height by default) also meant .sidebar itself never grew taller than
   its own content, i.e. same height as .summary — with zero extra
   height in its containing block, sticky had nowhere to move to and
   just sat at its static position the entire time. align-self:stretch
   overrides that grid default for this one item only, so .sidebar
   spans the full (tall, left-column-driven) row height while the left
   column's own items keep their normal shrink-to-content sizing —
   .summary then has real room inside it to stick and follow the scroll. */
.odic-checkout__sidebar{width:100%;box-sizing:border-box;align-self:stretch}
.odic-checkout__summary{
	position:sticky;top:112px;background:#fff;border-radius:var(--odic-radius-md);
	padding:32px;box-shadow:var(--odic-shadow-soft);
}
/* Coupon entry — a small collapsed toggle line, not the full-width
   banner WooCommerce's own default markup rendered above the whole
   checkout form. Matched to a real reference (DJI's checkout) on
   request: a small "+" circle + label, sitting quietly inside the
   Order Summary sidebar instead of announcing itself at the top of the
   page. Structural classes (.showcoupon, #woocommerce-checkout-
   form-coupon, .checkout_coupon) are WooCommerce core's own — untouched
   so its existing checkout.js toggle/AJAX-submit behavior keeps working
   with no JS of ours needed; only presentation changed here. */
.odic-checkout-coupon-toggle{margin:0 0 20px}
.odic-checkout-coupon-toggle .showcoupon{
	display:inline-flex;align-items:center;gap:8px;
	font-size:14px;font-weight:500;color:var(--odic-text);text-decoration:none;
}
.odic-checkout-coupon-toggle__icon{
	display:inline-flex;align-items:center;justify-content:center;
	width:20px;height:20px;border-radius:50%;flex:none;
	background:var(--odic-accent);color:#fff;font-size:14px;line-height:1;
}
.odic-checkout-coupon-form.checkout_coupon{
	display:flex;gap:8px;margin:12px 0 20px;padding:0;border:none;background:none;
}
.odic-checkout-coupon-form .form-row{margin:0;padding:0;flex:1}
.odic-checkout-coupon-form .form-row-last{flex:none}
.odic-checkout-coupon-form .input-text{
	width:100%;height:40px;padding:0 14px;border:1px solid var(--odic-border);
	border-radius:10px;font-size:14px;font-family:inherit;box-sizing:border-box;
}
.odic-checkout-coupon-form .input-text:focus{outline:none;border-color:var(--odic-accent)}
.odic-checkout-coupon-form button[name="apply_coupon"]{
	height:40px;padding:0 18px;border:none;border-radius:10px;
	background:var(--odic-text);color:#fff;font-size:14px;font-weight:500;font-family:inherit;cursor:pointer;
}
.odic-checkout__summary .woocommerce-checkout-review-order-table{width:100%;border-collapse:collapse;margin:0 0 8px}
.odic-checkout__summary .woocommerce-checkout-review-order-table thead{display:none}
.odic-checkout__summary .woocommerce-checkout-review-order-table td,
.odic-checkout__summary .woocommerce-checkout-review-order-table th{
	padding:14px 0;border-bottom:1px solid var(--odic-border);font-family:inherit;
	font-size:15px;color:var(--odic-text-soft);text-align:left;
}
.odic-checkout__summary .woocommerce-checkout-review-order-table .product-total{text-align:right;white-space:nowrap}
.odic-checkout__summary .woocommerce-checkout-review-order-table tfoot th{color:var(--odic-text);font-weight:500}
.odic-checkout__summary .woocommerce-checkout-review-order-table tfoot .order-total th,
.odic-checkout__summary .woocommerce-checkout-review-order-table tfoot .order-total td{
	font-size:18px;font-weight:700;color:var(--odic-text);border-bottom:none;
}
/* Qty stepper + Remove, on request — cart management moved directly
   into this Order Summary table instead of a separate /cart/ page (see
   woocommerce/checkout/review-order.php). Reuses the same
   .odic-pdp-qty__stepper/__btn component the Order page's own quantity
   control already uses, just sized down (28px buttons/32px input vs.
   36px/40px there) to fit a compact sidebar row instead of a full-width
   purchase panel. */
.odic-checkout-item__row{display:flex;align-items:center;gap:16px;margin-top:8px}
.odic-checkout-item__qty.odic-pdp-qty__stepper{flex:none}
.odic-checkout-item__qty .odic-pdp-qty__btn{width:28px;height:28px;font-size:15px}
.odic-checkout-item__qty-input{width:32px;text-align:center;border:none;background:transparent;font-size:14px;font-weight:600;font-family:inherit;-moz-appearance:textfield}
.odic-checkout-item__qty-input::-webkit-outer-spin-button,.odic-checkout-item__qty-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.odic-checkout-item__remove{font-size:13px;color:var(--odic-muted);text-decoration:underline}
.odic-checkout-item__remove:hover{color:var(--odic-text)}
.odic-checkout__summary .woocommerce-Price-amount{font-weight:inherit;color:inherit}

.odic-checkout__place-order{margin-top:20px;padding-top:20px;border-top:1px solid var(--odic-border)}
.odic-checkout__place-order .woocommerce-terms-and-conditions-wrapper{margin-bottom:16px;font-size:13px;color:var(--odic-muted)}
.odic-checkout__place-order .woocommerce-form__label-for-checkbox{display:flex;align-items:flex-start;gap:8px}
.odic-checkout__place-order noscript{display:block;font-size:13px;color:var(--odic-muted);margin-bottom:12px}

/* Place order — same pill/height/font language as every other primary
   button on the site (.odic-btn/.odic-pay-btn, 48px/16px/500), matched
   to the homepage hero "Learn more" button (.odic-btn--hero-dark: #333
   background, 24px radius) on prior request. Grey/disabled-look by
   default now, same as .odic-checkout__save-address-btn, and for the
   same reason — only turns into that black on .is-ready, once Shipping
   Address is actually saved (checkout-gating.js toggles this alongside
   unlocking Payment Method, same gate). Still a real, clickable submit
   button either way — WooCommerce's own validation on submit is the
   real gate; this is just the button not looking done before it is. */
.odic-checkout__place-order-btn{width:100%;background:#ddd;color:#bbb;border-radius:24px}
.odic-checkout__place-order-btn.is-ready{background:#333;color:#fff}

@media (max-width:900px){
	.odic-checkout-page{padding:40px 20px 80px}
	.odic-checkout-page h1{font-size:30px;margin-bottom:28px}
	.odic-checkout__grid{grid-template-columns:1fr;gap:32px}
	/* order:-1 (Order Summary first) removed on request — mobile should
	   read in the same order as the form itself: Shipping Address,
	   Payment Method, THEN Order Summary last ("手机端，应该先Shipping
	   Address然后Payment Method最后才是Order Summary才对"), i.e. just
	   the plain source order, no reordering. */
	.odic-checkout__sidebar{align-self:auto}
	.odic-checkout__summary{position:static}
	.odic-checkout__section{padding:24px}
	.odic-checkout__section-body .form-row-first,
	.odic-checkout__section-body .form-row-last{width:100%;float:none}
}

/* ========================================================================
   Cart — reuses the checkout's own .odic-checkout__grid/__main/__section/
   __sidebar/__summary classes (woocommerce/cart/cart.php + cart-totals.php,
   overridden) instead of a parallel set, on request ("VIEW Cart的界面依
   然是没有排版的，能不能按照Shipping Address的风格做出来") — the item
   rows and coupon field below are the only genuinely cart-specific
   pieces; everything else (card, sidebar, totals table, primary button)
   is already styled by the checkout rules above.
   ======================================================================== */
.odic-cart-items{display:flex;flex-direction:column}
.odic-cart-item{
	display:grid;grid-template-columns:64px 1fr auto 96px 96px;gap:16px;align-items:center;
	padding:20px 0;border-bottom:1px solid var(--odic-border);
}
.odic-cart-item:first-child{padding-top:0}
.odic-cart-item:last-child{border-bottom:none}
.odic-cart-item__thumb{width:64px;height:64px;border-radius:10px;overflow:hidden;flex:none;background:#f7f8fa}
.odic-cart-item__thumb img{width:100%;height:100%;object-fit:cover;display:block}
.odic-cart-item__info{min-width:0}
.odic-cart-item__name{margin:0;font-size:15px;font-weight:500;color:var(--odic-text)}
.odic-cart-item__name a{color:inherit}
.odic-cart-item__meta{margin:4px 0 0;font-size:13px;color:var(--odic-muted);line-height:1.5}
.odic-cart-item__meta p{margin:0}
.odic-cart-item__price-mobile{display:none}
.odic-cart-item__remove{
	display:inline-block;margin-top:6px;font-size:13px;color:var(--odic-muted);
	border-bottom:1px solid transparent;
}
.odic-cart-item__remove:hover{color:#e5484d;border-bottom-color:currentColor}
.odic-cart-item__price,.odic-cart-item__subtotal{font-size:15px;color:var(--odic-text-soft);text-align:right;white-space:nowrap}
.odic-cart-item__subtotal{font-weight:600;color:var(--odic-text)}
.odic-cart-item__qty .quantity{display:block}
.odic-cart-item__qty input.qty{
	width:100%;height:40px;box-sizing:border-box;
	border:1px solid #dddddd;border-radius:8px;background:#f7f8fa;
	padding:0 10px;font-size:15px;font-weight:500;font-family:inherit;color:var(--odic-text);text-align:center;
	-moz-appearance:textfield;
}
.odic-cart-item__qty input.qty::-webkit-outer-spin-button,
.odic-cart-item__qty input.qty::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.odic-cart-item__qty input.qty:focus{outline:none;border-color:var(--odic-accent)}

.odic-cart-actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:16px;margin-top:24px}
.odic-cart-coupon{display:flex;gap:10px;flex:1;min-width:240px}
.odic-cart-coupon__input{
	flex:1;height:44px;box-sizing:border-box;
	border:1px solid #dddddd;border-radius:8px;background:#f7f8fa;
	padding:0 16px;font-size:15px;font-weight:500;font-family:inherit;color:var(--odic-text);
}
.odic-cart-coupon__input:focus{outline:none;border-color:var(--odic-accent)}
.odic-cart-coupon__btn{height:44px;padding:0 22px;font-size:14px;flex:none}
.odic-cart-update-link{
	background:none;border:none;padding:0;cursor:pointer;
	font-size:14px;font-weight:500;color:var(--odic-muted);font-family:inherit;
	border-bottom:1px solid transparent;
}
.odic-cart-update-link:hover{color:var(--odic-text);border-bottom-color:currentColor}

/* Proceed to checkout — the primary CTA in the sidebar, same treatment
   as the checkout page's own Place Order button. */
.odic-cart-checkout-cta{margin-top:20px;padding-top:20px;border-top:1px solid var(--odic-border)}
/* !important: WooCommerce's own .woocommerce a.button.alt rule (extra
   .woocommerce ancestor class = higher specificity than a plain 2-class
   selector here) was winning the tie and painting this purple instead
   of the site's own button colour — same class of WC-core-CSS fight
   already flagged elsewhere in this file (.price{color:inherit!important}). */
.odic-cart-checkout-cta .checkout-button{
	display:flex!important;align-items:center;justify-content:center;width:100%;
	height:48px!important;border-radius:var(--odic-radius-pill)!important;
	background:#333!important;color:#fff!important;font-size:16px!important;font-weight:500!important;
	font-family:inherit!important;border:none!important;box-shadow:none!important;
	transition:opacity .15s ease;
}
.odic-cart-checkout-cta .checkout-button:hover{opacity:.88}

/* Empty cart — no custom template for this state (cart.php, and this
   CSS, only load when the cart has items), just enough centred styling
   inside the shared .odic-checkout-page wrapper so it isn't bare
   WooCommerce-default text either. */
.odic-checkout-page .woocommerce-notices-wrapper{max-width:640px;margin:0 auto 24px;text-align:center}
.odic-checkout-page .return-to-shop{text-align:center;margin:24px 0 0}
.odic-checkout-page .return-to-shop .button{
	display:inline-flex;align-items:center;justify-content:center;
	height:48px;padding:0 32px;border-radius:var(--odic-radius-pill);
	background:#333;color:#fff;font-size:16px;font-weight:500;
}
.odic-checkout-page .return-to-shop .button:hover{opacity:.88}

@media (max-width:640px){
	.odic-cart-item{grid-template-columns:56px 1fr;grid-template-areas:"thumb info" "thumb qty";row-gap:8px}
	.odic-cart-item__thumb{grid-area:thumb;width:56px;height:56px}
	.odic-cart-item__info{grid-area:info}
	.odic-cart-item__qty{grid-area:qty;width:88px}
	.odic-cart-item__price,.odic-cart-item__subtotal{display:none}
	.odic-cart-item__price-mobile{display:block;margin-top:4px;font-size:14px;color:var(--odic-text-soft)}
}

/* Card payment gateway fields (Odic_Card_Gateway::payment_fields() in
   functions.php) — same flat #F7F8FA/#DDDDDD/8px field language as the
   rest of the checkout above, matching the Figma reference (node
   2978:8533) exactly: 32x32 brand-icon tiles with a hairline #F8F8F8
   border, 48-56px fields, 16px/500 text. */
.odic-card-fields{padding-top:4px}
.odic-card-fields__brands{display:flex;gap:8px;margin-bottom:16px}
.odic-card-fields__brand{
	display:flex;align-items:center;justify-content:center;
	width:44px;height:44px;border-radius:8px;background:#fcfcfc;border:1px solid #f8f8f8;
}
/* max-width/height (not a fixed box) so each network's own real aspect
   ratio is kept — Visa/UnionPay are wide wordmarks, Mastercard/JCB are
   closer to square, forcing one fixed w×h would stretch whichever
   doesn't match it. */
.odic-card-fields__brand img{max-width:30px;max-height:20px;width:auto;height:auto;display:block}
.odic-card-fields__field{margin-bottom:12px}
.odic-card-fields__row{display:flex;gap:12px}
.odic-card-fields__row .odic-card-fields__field{flex:1;margin-bottom:12px}
.odic-card-fields__input{
	width:100%;height:48px;box-sizing:border-box;
	border:1px solid #dddddd;border-radius:8px;background:#f7f8fa;
	padding:0 16px;font-size:16px;font-weight:500;font-family:inherit;color:var(--odic-text);
}
.odic-card-fields__input::placeholder{color:#aaaaaa;font-weight:500}
.odic-card-fields__input:focus{outline:none;border-color:var(--odic-accent)}
.odic-card-fields__note{margin:4px 0 0;font-size:13px;color:var(--odic-muted)}
