/* gb-tokens.css — Good Boy's one canonical design system.
   Source of truth: DESIGN.md (Cameron-approved 2026-07-28). Every page should link this
   file and stop declaring its own :root — chat.html/inbox.html still carry an older cool-blue
   palette from before DESIGN.md existed; retrofitting them onto this file is the point.
   Nothing in here is page-specific. If a rule only makes sense on one page, it belongs in
   that page's own <style>, not here. */

:root{
  /* ---- palette (verbatim from DESIGN.md) ---- */
  --bg:#0d0c0a;          /* near-black, hero/gate only */
  --bg-lift:#14120f;     /* one step up */
  --card:#1a1713;        /* card surfaces */
  --card-hi:#211d17;     /* hover/active card state */
  --ink:#ece7dd;         /* primary text */
  --dim:#9a927f;         /* secondary text */
  --faint:#5f594c;       /* tertiary / caption text */
  --warm:#e8a34c;        /* gold — cool end of the escalation gradient */
  --heat:#ff5a7a;        /* red/pink — hot end of the escalation gradient */
  --accent:#2178ff;      /* blue — links/utility only, never for escalation */
  --line:#29251e;        /* hairlines / borders */
  /* status — not in DESIGN.md's palette table (that doc is about escalation, not status),
     but every goal/streak surface needs a done/missed color that reads instantly and isn't
     pulled from the warm->heat escalation ramp (that ramp means something else: intensity,
     not pass/fail). Chosen to sit quietly next to warm/heat without competing. */
  --ok:#58b16b;
  --bad:#e05a5a;

  /* ---- layout constants shared by the tab bar + any page docked above it ---- */
  --gb-tabbar-h:64px;
  --gb-tabbar-safe-h:calc(var(--gb-tabbar-h) + env(safe-area-inset-bottom,0px));
}

/* ============================================================ gradient text + wash
   Move 1: headlines run a gradient across warm->heat instead of a flat color. */
.gradient-text{
  background:linear-gradient(90deg,var(--warm),var(--heat));
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.gradient-wash{
  background:radial-gradient(120% 80% at 50% -10%, color-mix(in srgb, var(--heat) 22%, var(--bg)) 0%, var(--bg) 60%);
}

/* ============================================================ tilt + straighten-on-hover
   Move 2+3: nothing is a perfectly axis-aligned rectangle, and interaction resolves the
   tilt. Set --tilt to vary the angle per instance: style="--tilt:-2deg". Default is a mild
   -1.2deg so an element gets the treatment even with no inline override. */
.tilt{
  --tilt:-1.2deg;
  transform:rotate(var(--tilt));
  transition:transform .2s ease;
}
.tilt:hover,.tilt:focus-within{ transform:rotate(0deg) translateY(-4px); }
/* Cards specifically also want a bigger lift + not just the rotate — matches the
   .polaroid:hover recipe named directly in DESIGN.md. */
.tilt-card{ --tilt:-1.2deg; transform:rotate(var(--tilt)); transition:transform .2s ease; }
.tilt-card:hover,.tilt-card:focus-within{ transform:rotate(0deg) translateY(-6px); }

/* ============================================================ sticker badges
   Move 4: a rotated pill overlapping a corner, never a label sitting politely beside
   content. Position this ABSOLUTE inside a position:relative parent. */
.sticker-badge{
  position:absolute; top:-10px; left:-14px;
  background:linear-gradient(90deg,var(--warm),var(--heat));
  color:#1a1408; font-weight:800; font-size:.72rem; letter-spacing:.02em;
  padding:.3rem .7rem; border-radius:999px; transform:rotate(-8deg);
  box-shadow:0 4px 10px rgba(0,0,0,.35); white-space:nowrap; z-index:2;
}

/* ============================================================ polaroid / film-strip framing
   Move 5: real photography gets a frame treatment, never a bare <img>. */
.polaroid{
  position:relative; display:inline-block; background:#f4f1ea; padding:10px 10px 34px;
  border-radius:6px; box-shadow:0 10px 26px rgba(0,0,0,.45);
}
.polaroid img{ display:block; width:100%; border-radius:2px; }
.polaroid .cap{
  position:absolute; left:0; right:0; bottom:8px; text-align:center;
  font-size:.78rem; color:#3a3630; font-weight:700;
}
.filmstrip{ display:flex; gap:8px; overflow-x:auto; padding:4px 2px; }
.filmstrip .frame{
  flex:none; width:84px; height:84px; border-radius:10px; object-fit:cover;
  border:2px solid var(--tier-color,var(--warm)); background:var(--card);
}

/* ============================================================ tier / escalation gradient
   Move 7: a real progression gets ITS OWN point on the gold->red ramp, not one flat color
   regardless of stage. --tier is 0 (coolest/earliest) to 1 (hottest/furthest along) —
   set inline per element: style="--tier:0.4". Consumers: streak pills, spice-level badges,
   level rings, the goals-tab dot-grid's "how strong a day" shading. */
.tier{
  --tier:0;
  --tier-color:color-mix(in srgb, var(--heat) calc(var(--tier) * 100%), var(--warm));
}
.tier-fill{ background:var(--tier-color); }
.tier-text{ color:var(--tier-color); }
.tier-border{ border-color:var(--tier-color); }

/* ============================================================ safe-area helpers
   Nothing existing handles the home-indicator inset from the start; every new page should. */
.gb-safe-top{ padding-top:env(safe-area-inset-top,0px); }
.gb-safe-bottom{ padding-bottom:env(safe-area-inset-bottom,0px); }
/* For scrollable page content that sits ABOVE the fixed tab bar (see gb-tabbar.js) — reserves
   space for the bar itself plus the home-indicator inset beneath it, so the last real item in
   a list is never partially hidden behind the bar. */
/* !important is load-bearing here, not decoration: this class exists to GUARANTEE clearance
   above the fixed tab bar, and several pages' own local `.wrap{padding:...}` shorthand rule
   sits at equal specificity (both single-class selectors) and later in the cascade (the
   page's own <style> loads after this stylesheet) — without !important the page's own
   padding-bottom silently wins and the tab bar overlaps real content. Found live 2026-07-30
   via an actual rendered screenshot (Playwright QA of the locked-direction retrofit): the
   Pro/redeem section on profile.html and the last thread on inbox.html were both partially
   hidden behind the bar. goals.html happened to hardcode a large-enough bottom padding
   (100px) to accidentally clear it, which is exactly why a syntax/structure check alone never
   caught this — the bug only exists as pixels, not as a token or a tag. */
.gb-tabbar-clearance{ padding-bottom:calc(var(--gb-tabbar-h) + env(safe-area-inset-bottom,0px) + 12px) !important; }

/* ============================================================ small shared primitives */
.gb-btn{
  font:inherit; cursor:pointer; border:none; border-radius:12px;
  background:linear-gradient(90deg,var(--warm),var(--heat)); color:#1a1408;
  font-weight:800; padding:.9rem 1.1rem; transition:opacity .15s,transform .15s;
}
.gb-btn:active{ transform:scale(.98); }
.gb-btn[disabled]{ opacity:.5; pointer-events:none; }
.gb-btn-ghost{
  font:inherit; cursor:pointer; background:var(--card); color:var(--ink);
  border:1px solid var(--line); border-radius:12px; font-weight:700; padding:.85rem 1.05rem;
}
.gb-card{
  background:var(--card); border:1px solid var(--line); border-radius:16px; padding:1.1rem 1.2rem;
}

/* ============================================================ LOCKED DIRECTION (2026-07-30)
   Deconstructed Fashion base + 3 contextual embellishments. Picked from a 24-style survey by
   Cameron, locked in artifact c315a190-4429-40f0-a2d0-82a2fa000013. ADDITIVE ONLY — nothing
   here renames or removes a token above; existing pages that never opt in keep working
   exactly as before. The rule for every embellishment below: if you can't name the trigger
   that earned it, it doesn't belong on screen. */

:root{
  --zen-bg:#eef2ea; --zen-ink:#2a4a1a; --zen-accent:#7a9a6a;
  --pop-yellow:#ffde59;
}

/* ---- base: torn-edge card shapes ----
   A small deliberately-varied SET (six), not one reused shape and not infinite per-instance
   uniqueness (impractical for a dynamically-rendered list). Consumers cycle a..f across list
   items by index/hash so neighboring cards never read identical. Cards using these should
   drop border-radius/border — the clip-path IS the edge treatment. */
.gb-torn-a{ clip-path:polygon(2% 4%,97% 0,100% 90%,4% 100%); transform:rotate(-1.4deg); }
.gb-torn-b{ clip-path:polygon(0 0,95% 3%,100% 96%,3% 100%); transform:rotate(1.1deg); }
.gb-torn-c{ clip-path:polygon(3% 0,100% 5%,96% 100%,0 94%); transform:rotate(-0.6deg); }
.gb-torn-d{ clip-path:polygon(0 6%,94% 0,100% 94%,6% 100%); transform:rotate(1.8deg); }
.gb-torn-e{ clip-path:polygon(4% 0,100% 3%,97% 97%,0 100%); transform:rotate(-2.1deg); }
.gb-torn-f{ clip-path:polygon(0 3%,96% 0,100% 97%,3% 100%); transform:rotate(0.9deg); }
/* small, badge-sized tears (avatars, thumbnails) — subtler points, no rotation of their own
   since these usually sit inline next to text and a tilted avatar reads as a bug, not style. */
.gb-torn-sm-a{ clip-path:polygon(0 8%,88% 0,100% 84%,10% 100%); }
.gb-torn-sm-b{ clip-path:polygon(2% 0,100% 8%,96% 96%,0 100%); }
.gb-torn-sm-c{ clip-path:polygon(4% 6%,100% 0,98% 94%,0 100%); }

/* ---- "chaos reflects reality" ----
   A container class scales how jagged the tear reads, driven by real data (see each page's
   own computation of e.g. open-required-today count) — never decoration, always computed.
   Calm = the tear nearly vanishes; high = the sharpest points in the whole set. Default (no
   wrapper class) sits at the mid values defined above. */
.gb-chaos-calm .gb-torn-a,.gb-chaos-calm .gb-torn-b,.gb-chaos-calm .gb-torn-c,
.gb-chaos-calm .gb-torn-d,.gb-chaos-calm .gb-torn-e,.gb-chaos-calm .gb-torn-f{
  clip-path:polygon(0.5% 1%,99% 0,100% 99%,1% 100%); transform:rotate(-0.4deg);
}
.gb-chaos-high .gb-torn-a{ clip-path:polygon(4% 8%,94% 0,100% 84%,8% 100%); transform:rotate(-3deg); }
.gb-chaos-high .gb-torn-b{ clip-path:polygon(0 0,90% 6%,100% 92%,6% 100%); transform:rotate(2.6deg); }
.gb-chaos-high .gb-torn-c{ clip-path:polygon(6% 0,100% 9%,92% 100%,0 88%); transform:rotate(-2.2deg); }
.gb-chaos-high .gb-torn-d{ clip-path:polygon(0 10%,88% 0,100% 90%,10% 100%); transform:rotate(3.4deg); }
.gb-chaos-high .gb-torn-e{ clip-path:polygon(8% 0,100% 6%,94% 94%,0 100%); transform:rotate(-3.8deg); }
.gb-chaos-high .gb-torn-f{ clip-path:polygon(0 5%,92% 0,100% 94%,5% 100%); transform:rotate(2.1deg); }

/* ---- embellish 1: gradient priority (style #17) ----
   ONE per screen — whatever is today's single most important thing. Never the whole
   background, never more than one instance at a time. */
.gb-priority{
  background:linear-gradient(160deg,var(--heat),var(--warm)) !important; color:#1a0a08 !important;
}
.gb-priority *{ color:#1a0a08 !important; }
.gb-priority .checkin-btn{ background:#1a0a08 !important; color:var(--pop-yellow) !important; }

/* ---- embellish 2: zen contextual (style #14) ----
   Activates ONLY for mindfulness-shaped goals/moments. Deliberately breaks the base system on
   purpose: rounded, sage, no tear, no rotation, slower motion — the contrast IS the signal,
   so this never gets torn edges or the hot-pink accent layered back in. */
.gb-zen{
  clip-path:none !important; transform:none !important; border-radius:16px !important;
  background:var(--zen-bg) !important; border:none !important; box-shadow:0 2px 10px rgba(0,0,0,.3);
}
.gb-zen,.gb-zen *{ color:var(--zen-ink) !important; }
.gb-zen .checkin-btn,.gb-zen .streak-chip{ background:var(--zen-accent) !important; color:#fff !important; }
@keyframes gb-breathe{0%,100%{transform:scale(1)}50%{transform:scale(1.1)}}
.gb-zen-pulse{ animation:gb-breathe 2.4s ease-in-out 1; }

/* ---- embellish 3: pop-art contextual (style #13) ----
   A full genre-shift takeover for reward/celebration moments ONLY — check-in confirmed,
   streak milestone, level-up, a photo unlock. 1.5–2.5s, then gone; never a resting state.
   Mounted/dismissed by each page's own showPopBurst() helper. One deliberate exception: a
   mindfulness check-in should use .gb-zen-pulse above instead of this — loud would undercut
   the thing it's rewarding. */
@keyframes gb-bam-in{0%{transform:scale(.3) rotate(-14deg);opacity:0}55%{transform:scale(1.18) rotate(4deg);opacity:1}100%{transform:scale(1) rotate(-4deg)}}
@keyframes gb-halftone-in{from{opacity:0}to{opacity:1}}
/* ---- F4, the frame the storyboard calls "fade to card" (pop-burst-and-addgoal-v5.html,
   Variation A). F1-F3 (impact/overshoot/settle) were already gb-bam-in; without this, dismissal
   was a hard DOM removal -- a cut, not a fourth frame. .gb-pop-out plays BEFORE removal (JS
   adds the class, waits for animationend, then removes), so the burst settles back into the
   card underneath instead of vanishing. */
@keyframes gb-pop-out{0%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(.92)}}
.gb-pop-overlay{
  position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;
  background:radial-gradient(circle,var(--pop-yellow) 0%,var(--pop-yellow) 40%,var(--heat) 41%);
  background-size:13px 13px;animation:gb-halftone-in .12s ease;cursor:pointer;
}
.gb-pop-overlay.gb-pop-out{ animation:gb-pop-out .22s ease forwards; }
.gb-pop-card{
  background:#fff;color:#0a0a0a;border:4px solid #0a0a0a;padding:.9rem 1.3rem;font-weight:900;
  text-align:center;line-height:1.25;transform:rotate(-3deg);box-shadow:6px 6px 0 #0a0a0a;
  animation:gb-bam-in .5s cubic-bezier(.2,1.4,.4,1);font-size:1.05rem;
}
.gb-pop-card small{ display:block;font-size:.62rem;font-weight:700;margin-top:2px;letter-spacing:.04em; }
@media (prefers-reduced-motion:reduce){
  .gb-pop-overlay,.gb-pop-card,.gb-pop-overlay.gb-pop-out,.gb-zen-pulse{ animation:none !important; }
}
