/**
 * Base — design tokens, reset, typography, animations, shared components,
 * accessibility, icon-in-button helpers. Loaded on every page.
 */

/* ── TOKENS ── */
:root {
  --bg: #FAFAF6;
  --white: #FFFFFF;
  --ink: #1C1A18;
  --stone: #8C8278;
  --dust: #C4B9AE;
  --border: #D8D4CE;
  --cream: #FAF7F2;
  --ivory: #F3EDE3;
  --bg-alt: #f8f8f8;         /* Page background for the checkout family. */

  /* 2.1 redline accents (canonical names — prefer these in new code). */
  --fri-yay: #C25F4E;        /* Standard accent. Was --terra #dc7a62. */
  --fri-yay-light: #F5E8E2;
  --mascot: #B48970;         /* Standard hover. Was --terra-hover / hardcoded #bc886d. */
  --free-choice: #D7D852;    /* Light — always pair with Ink text, never white. */
  --half-day: #062832;

  /* Legacy aliases — 2.0 names kept so ~790 existing usages retarget without a rename.
     Do not give these independent values; retire them per page as pages are touched. */
  --charcoal: var(--ink);
  --terra: var(--fri-yay);
  --terra-hover: var(--mascot);
  --terra-light: var(--fri-yay-light);

  --gold: #C9A96E;
  --gold-light: #F6EFE1;
  --sage: #7A9B8A;
  --sage-light: #E4F0EA;
  --sage-dark: #5F7A6C;      /* Approve/connected states. Dashboard restyle. */

  /* Vendor-dashboard restyle tokens (dashboard-pages mockup). */
  --mascot-light: #E6D7CE;   /* Mascot tint — row hover, segmented-toggle track. */
  --fri-yay-pale: #F7F3F0;   /* Palest terracotta wash — tooltips, callout cards. */
  --hallway: #F0EFEE;        /* Neutral surface between --bg and --border. */
  --pop-quiz: #b8433a;       /* Destructive / negative delta. */
  --brown: #957B66;
  --dana-point: #8bbfba;
  --dana-point-light: #e4f0ef;
  /* 2.0 category-page palette (Apparel / Self-Care / Essentials / Meetups). */
  --spinach: #7faf79;
  --field-day: var(--spinach); /* Same color, two names. Redline says write --spinach. */
  --berry: #c4728a;
  --pickleball: #e3db1f;
  --pickleball-hover: #ccc31a;
  --cortado: #7A6458;
  --cortado-light: #f0ece8;
  --high-tea: #D4ABAB;
  --sunny-honey: #DDB84E;
  --sandy-beach: #E0BC6A;
  --olive-ranch: #808000;
  --olive-oil: #C2B709;
  --olive-ranch-light: #f0efcc;
  --terra-pale: #e8c4b0;
  --ff-display: 'Playfair Display', Georgia, serif;
  --ff-body: 'Inter', sans-serif;
  --ff-accent: 'Sacramento', cursive;
  --ff-mono: 'IBM Plex Mono', 'Courier New', monospace;
  --radius-pill: 100px;    /* Search bars only. Not buttons — see --radius-btn. */
  --radius-btn: 4px;       /* Standard button radius, no exceptions.
                              Email/newsletter opt-in submits stay 0 (square). */
  --radius-card: 12px;
  --shadow-sm: 0 1px 3px rgba(28,26,24,0.04);
  --shadow-md: 0 4px 16px rgba(28,26,24,0.07);
  --shadow-lg: 0 12px 40px rgba(28,26,24,0.1);
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}

/* ── RESET / BASE ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
  /* background: var(--bg); */
  color: var(--charcoal);
  font-family: var(--ff-body);
  font-size: 15px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
h1, h2, h3 { color: #000000; }
button { cursor: pointer; border: none; background: none; font-family: var(--ff-body); }

/* ── ANIMATIONS ── */
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(24px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes ts-icon-spin {
  to { transform: rotate(360deg); }
}
/* Apply to any <i class="ri-..."> to make it spin in place. Used by the
 * AJAX add-to-cart loading state (icon swap handled in main.js). */
.ts-icon-spin {
  animation: ts-icon-spin 0.8s linear infinite;
  display: inline-flex;
}

/* ── EMAIL SIGN-UP: INLINE SUCCESS (TS-OPTIN-SUCCESS v1) ────────────────
 * Every newsletter box (footer, store email list, product page, post
 * opt-ins, blog popup) confirms in place: assets/js/main.js swaps the email
 * row for this box once the subscribe call succeeds, at the same size, so
 * nothing on the page moves. A bad email keeps the box and adds the red line
 * (.ts-optin-err) under it. Ported 1:1 from the reference block. */
.ts-optin-ok {
  display: flex;
  align-items: center;
  gap: 14px;
  box-sizing: border-box;
  padding: 12px 16px;
  background: #FFFFFF;
  border: 1px solid var(--ink);
  border-radius: 4px;
  color: var(--ink);
  font-family: 'Inter', sans-serif;
  font-size: 14px;
  line-height: 1.4;
  text-align: left;
  animation: tsOptinIn .35s cubic-bezier(0.22, 1, 0.36, 1);
}
.ts-optin-ok-icon {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ts-optin-ok strong { display: block; font-weight: 700; font-size: 14.5px; }
.ts-optin-ok span span { display: block; font-size: 12.5px; }
.ts-optin-err {
  margin: 6px 0 0;
  font-family: 'Inter', sans-serif;
  font-size: 12.5px;
  font-weight: 600;
  color: #C25F4E;
  text-align: left;
}
.ts-optin-bad { outline: 2px solid #C25F4E !important; outline-offset: -2px; }
@keyframes tsOptinIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .ts-optin-ok { animation: none; } }

/* ── SHARED BUTTONS ── */
/* Used across header, single-product page and cart drawer, so they live here. */
.pill-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--terra);
  color: #fff;
  border-radius: var(--radius-btn);
  padding: 13px 30px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  transition: all 0.2s var(--ease-out);
  border: none;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(196,115,90,0.25);
}
.pill-btn:hover { background: var(--terra-hover); transform: translateY(-1px); box-shadow: 0 4px 14px rgba(196,115,90,0.3); }
.pill-btn.outline {
  background: transparent;
  color: var(--charcoal);
  border: 1.5px solid var(--charcoal);
  box-shadow: none;
}
.pill-btn.outline:hover { background: rgba(28,26,24,0.05); box-shadow: none; transform: translateY(-1px); }

.btn-add-cart {
  background: var(--charcoal);
  color: #fff;
  border-radius: var(--radius-btn);
  padding: 14px 28px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: all 0.2s var(--ease-out);
  box-shadow: 0 2px 8px rgba(28,26,24,0.15);
  width: 100%;
  font-family: var(--ff-body);
  border: none;
  cursor: pointer;
  text-decoration: none;
}
.btn-add-cart:hover { background: var(--mascot); transform: translateY(-2px); box-shadow: 0 8px 24px rgba(28,26,24,0.18); color: #fff; }
.btn-add-cart[disabled] { opacity: 0.5; cursor: not-allowed; transform: none; }

.btn-continue {
  background: transparent;
  color: var(--ink);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-btn);
  padding: 14px 22px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.02em;
  transition: all 0.2s var(--ease-out);
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  text-decoration: none;
}
/* LOCKED EXCEPTION (2.1 redline): border darkens to Ink + lift + soft glow.
 * Never fills with Mascot like the standard hover — do not "fix" this. */
.btn-continue:hover {
  border-color: var(--ink);
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(28,26,24,0.1);
}

/* ── REMIX ICON helpers ── Generic <i class="ri-..."> normalisation. */
.btn-add-cart i, .btn-continue i { font-style: normal; display: inline-flex; align-items: center; line-height: 1; }
.btn-add-cart i { font-size: 16px; }
.btn-continue i { font-size: 14px; }

/* ── ACCESSIBILITY ── */
.screen-reader-text {
  border: 0;
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute !important;
  width: 1px;
  word-wrap: normal !important;
}
.skip-link {
  background: var(--charcoal);
  color: #fff;
  padding: 10px 16px;
  position: absolute;
  left: -9999px;
  top: 8px;
  z-index: 1000;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.skip-link:focus { left: 8px; }

/* .merch-* rules moved to main-style.css — the merch carousel now renders
 * site-wide (homepage, shop archive, blog archive…) via ts_render_merch_section(),
 * so its styles live with the other shared component rules. */

/* ── GRID LOADER ── Shared dot-pulse loader used wherever an AJAX-filtered
 * grid lives (shop archive, blog archive, future archives).
 *
 * The loader fills its closest positioned ancestor (each consumer marks the
 * relevant content column with `position: relative` — `.blog-content` for
 * the blog archive, `.shop-product-section` for the shop archive). It floats
 * over the result area, vertically + horizontally centered, while the grid
 * itself dims via aria-busy. No layout jumping, no appended-below loader. */
.ts-grid-loader[hidden],
.shop-grid-loader[hidden],
.blog-grid-loader[hidden] { display: none !important; }
.ts-grid-loader,
.shop-grid-loader,
.blog-grid-loader {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  pointer-events: none;
  background: rgba(255,255,255,0.55);
  backdrop-filter: blur(1.5px);
}
.ts-grid-loader .loader-dots,
.shop-grid-loader .loader-dots,
.blog-grid-loader .loader-dots {
  display: flex;
  gap: 6px;
}
.ts-grid-loader .loader-dots span,
.shop-grid-loader .loader-dots span,
.blog-grid-loader .loader-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--dust);
  animation: tsDotPulse 1.2s ease infinite;
}
.ts-grid-loader .loader-dots span:nth-child(2),
.shop-grid-loader .loader-dots span:nth-child(2),
.blog-grid-loader .loader-dots span:nth-child(2) { animation-delay: 0.2s; }
.ts-grid-loader .loader-dots span:nth-child(3),
.shop-grid-loader .loader-dots span:nth-child(3),
.blog-grid-loader .loader-dots span:nth-child(3) { animation-delay: 0.4s; }
.ts-grid-loader .loader-text,
.shop-grid-loader .loader-text,
.blog-grid-loader .loader-text {
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--stone);
  text-transform: uppercase;
}
@keyframes tsDotPulse {
  0%, 100% { opacity: 0.3; transform: scale(0.8); }
  50%      { opacity: 1;   transform: scale(1.1); }
}

/* Shared "Nothing found" empty state — used by every AJAX-filtered grid on
 * the site (shop, mercantile, travel, dokan storefront). The wrapping class
 * has grid-column:1/-1 so it spans the full width when dropped inside a
 * CSS grid container. */
.ts-empty-state {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 14px;
  padding: 56px 24px;
  color: var(--ink);
}
.ts-empty-state-img {
  width: 160px;
  height: auto;
  max-width: 60%;
  margin-bottom: 8px;
  opacity: 0.95;
}
.ts-empty-state-heading {
  font-family: var(--ff-display);
  font-size: 24px;
  font-weight: 500;
  color: var(--charcoal);
  margin: 0;
}
.ts-empty-state-message {
  font-size: 14px;
  color: var(--stone);
  line-height: 1.65;
  max-width: 460px;
  margin: 0;
}

/* ── AJAX pagination — shared across shop, store, mercantile, travel,
   apothecary. Lives in base so any future paginated grid can reuse it. ── */
.shop-pagination-wrap { margin-top: 32px; }
.shop-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.shop-pagination-btn {
  min-width: 38px;
  height: 38px;
  padding: 0 12px;
  border: 1px solid var(--charcoal, #2c2c2c);
  background: #fff;
  color: var(--charcoal, #2c2c2c);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  border-radius: 999px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.shop-pagination-btn:hover:not([disabled]) {
  background: var(--charcoal, #2c2c2c);
  color: #fff;
}
.shop-pagination-btn.is-current {
  background: var(--charcoal, #2c2c2c);
  color: #fff;
  cursor: default;
}
.shop-pagination-btn[disabled] {
  opacity: .4;
  cursor: not-allowed;
}
.shop-pagination-gap {
  padding: 0 4px;
  color: var(--charcoal, #2c2c2c);
  opacity: .6;
}

/* ── Shared "Sponsored" editorial sidebar (inc/sponsored-sidebar.php) ──
 * Used on the Resource Archive + Welcome. Ported from .sidebar-promos /
 * .sidebar-item in the 2.0 reference. */
.sidebar-promos {
  display: flex;
  flex-direction: column;
  gap: 40px;
  padding: 8px 0 24px;
  position: sticky;
  top: calc(var(--nav-h, 64px) + 40px);
}
.sidebar-item { display: block; }
.sidebar-item-img {
  display: block;
  width: 100%;
  aspect-ratio: 4/5;
  background: linear-gradient(145deg, #E8E0D8, #D4C8BC);
  border-radius: 14px;
  overflow: hidden;
  margin-bottom: 14px;
  transition: transform 0.3s var(--ease-out, ease);
}
.sidebar-item-img:hover { transform: translateY(-3px); }
.sidebar-item-eyebrow {
  font-size: 10px;
  letter-spacing: 0;
  text-transform: none;
  font-weight: 400;
  color: var(--stone, #8C8278);
  margin-bottom: 6px;
}
.sidebar-item-title {
  display: block;
  /* Inter, matching the reference (.sidebar-item-title: Inter 18/700, #000,
   * line-height 1.35). The reference's --ff-display is Inter; ours is
   * Playfair, so name the font here rather than via the token. */
  font-family: 'Inter', sans-serif;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.03em;
  color: #000;
  line-height: 1.35;
  margin-bottom: 8px;
  text-decoration: none;
}
/* No hover colour on the title — deliberate, not an omission. None of the three
 * references that draw this sidebar (welcome-logged-in, welcome-not-logged-in,
 * resource-archive) give it one; the card's affordance is the image lift on
 * .sidebar-item-img plus the .sidebar-item-link below it. The `transition` went
 * with the hover rule, since nothing was left for it to animate. */
.sidebar-item-title em { font-style: italic; }
.sidebar-item-link {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--charcoal, #1C1A18);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color 0.15s;
}
.sidebar-item-link:hover { color: var(--fri-yay, #C25F4E); }

/* ── BLOG PREVIEW CARDS · 2-line title + 2-line text (client feedback) ──
 * Every blog preview card caps its title and its excerpt at two lines, and
 * reserves both lines even when the text is shorter, so titles and body text
 * line up across cards sitting side by side. Lives here (not in each page's
 * stylesheet) because the same card markup appears on the homepage "Read the
 * TeacherStore Blog" row, single posts, storefronts, the blog archive and the
 * world landings. The em value is the fallback for browsers without the `lh`
 * unit; it matches each element's line-height. */
.more-blog-title,
.merc-blog-card-title,
.ts-world .blog-card-title,
.ts-meetups .blog-card-title,
.blog-card-title span {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Grid cards (side by side): reserve the full two lines. The world / meetups /
 * mercantile cards stack vertically with centred bodies, so they only clamp. */
.more-blog-title,
.blog-card-title span {
  min-height: 2.7em;
  min-height: 2lh;
}
.more-blog-excerpt {
  min-height: 3.1em;
  min-height: 2lh;
}
.blog-card-excerpt {
  min-height: 3.2em;
  min-height: 2lh;
}

/* Sidebar blog posts: title capped at two lines (no reserved height — these
 * stack in a single column). */
.sidebar-item--post .sidebar-item-title,
.sidebar-post-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── Seller coupon button ──
   One design in three places: under a product's price, on a storefront, and
   in the seller's dashboard. Shared, so it lives here rather than in any one
   page's stylesheet. Lifted from the client prototype. */
.ts-coupon {
	display: inline-flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	gap: 2px;
	max-width: 100%;
	padding: 7px 12px;
	border: 1px dashed #B9AFA4;
	border-radius: 6px;
	background: #FAFAF6;
	font-family: inherit;
	color: var(--ink);
	line-height: 1.25;
}

.ts-coupon-label {
	max-width: 100%;
	font-size: 12.5px;
	font-weight: 600;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.ts-coupon-tag {
	font-family: 'IBM Plex Mono', monospace;
	font-size: 10px;
	font-weight: 400;
	letter-spacing: 0.02em;
	color: var(--ink);
	white-space: nowrap;
}

.ts-coupon--sm { padding: 5px 10px; }
.ts-coupon--sm .ts-coupon-label { font-size: 12px; }

/* ── SELLER BIO: 4 lines + "… Read more" (#61) ──────────────────────────
 * inc/blog.php ts_render_bio_clamp(): store page, blog / TV / podcast seller
 * card, product page seller box. Clamped to 4 lines; when it runs longer,
 * "… Read more" sits at the end of line 4 over a fade in the background
 * colour behind it (main.js sets --ts-bio-fade and shows the button). Open,
 * the whole bio shows and the button drops below as "Read less". The type
 * size comes from each page's own bio class. */
.ts-bio-wrap { position: relative; --ts-bio-fade: #fff; }
.ts-bio-wrap .ts-bio {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
}
/* One <p> per paragraph, with a gap between them (#334). */
.ts-bio-wrap .ts-bio p { margin: 0; }
.ts-bio-wrap .ts-bio p + p { margin-top: 14px; }
.ts-bio-wrap .ts-bio.is-open {
  display: block;
  -webkit-line-clamp: unset;
  line-clamp: unset;
  overflow: visible;
}
.ts-bio-more {
  position: absolute;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: 0 0 0 36px;
  border: 0;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0), var(--ts-bio-fade) 30px);
  font-family: inherit;
  font-weight: 600;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.ts-bio-more[hidden] { display: none; }
.ts-bio-more:hover { color: #bc886d; }
.ts-bio-dots { font-weight: 400; color: var(--ink); }
.ts-bio-more-txt { text-decoration: underline; text-underline-offset: 3px; }
.ts-bio.is-open + .ts-bio-more {
  position: static;
  display: block;
  margin-top: 6px;
  padding: 0;
  background: none;
}
.ts-bio.is-open + .ts-bio-more .ts-bio-dots { display: none; }

/* A seller's email box (#60): the signed-in customer's account email shows
   read-only; signed out, the field reads "Sign in to join" and opens sign-in. */
form.email-form input[readonly],
form.post-optin-form input[readonly] { cursor: pointer; }
form.email-form input[readonly]:not(:placeholder-shown),
form.post-optin-form input[readonly]:not(:placeholder-shown) { cursor: default; }
