/* gpdo — device mockup system.
   Screens are built at true iPhone logical size (390x844) with the app's LITERAL metrics
   (56px header, ~88px tab bar, 70px centre FAB, 14px body, 12px radius) and then scaled
   as a whole via --sc. Do not re-derive sizes at the scaled magnitude — scale the frame. */
.dev{--sc:.84;--m:1;width:calc(412px * var(--sc) * var(--m));height:calc(866px * var(--sc) * var(--m));flex:0 0 auto}
.dev-in{width:412px;height:866px;transform:translate3d(0,var(--py,0px),0) scale(calc(var(--sc) * var(--m)));transform-origin:0 0}
/* Frames are sized by an inline --sc per placement, which a media query cannot override —
   so narrow phones scale through a second factor instead (346px frame vs a 312px content box
   on a 360px screen). */
@media(max-width:400px){.dev{--m:.88}}
.bez{width:412px;height:866px;background:#000;border:1px solid var(--raised);border-radius:56px;padding:11px}
.scr{position:relative;width:390px;height:844px;background:var(--canvas);border-radius:45px;overflow:hidden;font-size:14px;line-height:1.25}
/* Status bar — top safe area 47px */
.sb{position:absolute;inset:0 0 auto;height:47px;z-index:6;display:flex;align-items:center;justify-content:space-between;padding:0 30px;font-size:15px;font-weight:700;color:var(--pin)}
.island{position:absolute;top:9px;left:50%;transform:translateX(-50%);width:118px;height:33px;border-radius:var(--r-pill);background:#000;z-index:7}
.sb-r{display:flex;align-items:center;gap:5px}
.sb-r i{display:block;background:var(--pin)}
.sb-b{width:23px;height:11px;border:1px solid var(--pin);border-radius:3px;padding:1.5px;background:none!important}
.sb-b span{display:block;height:100%;width:78%;background:var(--pin);border-radius:1px}
/* Screen header — 56px, 24/700 title left */
.hd{height:56px;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-size:24px;font-weight:700}
/* ---------- MAP SURFACE ----------
   Traced from the founder's real TestFlight capture rather than invented: Vienna, inner west.
   Land #161616, arterials #2f2f2f at 2.4 over a fine #242424 grid of minor streets — organic
   curves, because the app's map has no straight grid and a repeating-gradient lattice was the
   single biggest tell that this was a fake. The fine mesh is generated with a sine wobble so no
   two streets run truly parallel.
   District names are the app's own label tier: uppercase, 1.6px tracked, ~#8f8f8f. Street names
   are 9.5px and follow their road. Google attribution sits bottom-left exactly as it ships.
   PLACEHOLDER STILL — the real hero must be that native capture. See DESIGN-NOTES.md. */
.map{position:absolute;inset:0;background:#161616;overflow:hidden}
.map-svg{position:absolute;inset:0;width:100%;height:100%}
.mp-a{fill:none;stroke:#2f2f2f;stroke-width:2.4;stroke-linecap:round}
.mp-b{fill:none;stroke:#242424;stroke-width:1.1;stroke-linecap:round}
.mp-rail{fill:none;stroke:#232323;stroke-width:1.1;stroke-dasharray:5 4}
.map-hood{position:absolute;transform:translate(-50%,-50%);font-size:12px;font-weight:400;letter-spacing:1.6px;color:#8f8f8f;white-space:nowrap;text-align:center}
.map-hood.sm{font-size:10px;letter-spacing:1.2px;color:#7a7a7a}
.map-nm{position:absolute;font-size:9.5px;font-weight:400;color:#767676;white-space:nowrap}
.gattr{position:absolute;left:13px;bottom:96px;z-index:3;font-size:15px;font-weight:700;letter-spacing:-.2px;color:rgba(255,255,255,.72)}
/* Small map strips behind bottom sheets (sections 4/5) — the full SVG trace is overkill at
   30% height behind a sheet; skewed non-orthogonal gradients avoid the ruled-grid tell. */
.map-roads{position:absolute;inset:-40%;background:repeating-linear-gradient(99deg,transparent 0 37px,#242424 37px 38.2px),repeating-linear-gradient(12deg,transparent 0 53px,#242424 53px 54.2px),repeating-linear-gradient(64deg,transparent 0 131px,#2f2f2f 131px 133px)}
.map-water{position:absolute;left:-16%;top:34%;width:132%;height:150px;background:#101010;transform:rotate(-19deg)}
/* Pins — the REAL app markers (src/gipedo-native/components/map-marker-icons/), shipped as three
   SVG assets: MapFootball, MapBasketball, MapBoth. Solid white teardrop with the sport knocked
   out, viewBox '240 63 210 313', MAP_PITCH_MARKER_SIZE 38 → 25.5×38 at the source aspect.
   Tip-anchored at 87.4% (where the teardrop point actually falls in that viewBox) rather than
   centre-anchored as the app does — the deliberate fix the brief invites. */
.pin{position:absolute;width:25.5px;height:38px;transform:translate(-50%,-87.4%);filter:drop-shadow(0 1px 2px rgba(0,0,0,.55))}
/* Profile screen — app/(tabs)/profile.tsx. 128px centred avatar with a 36px camera badge,
   Posts | Following | Followers in that order with 1px neutral900 dividers and 12px uppercase
   neutral500 labels, then the posts/reels icon strip over a full-bleed grid.
   Source uses font-black (900) for the stat figures; approximated at 700 because the site ships
   only two weights — the same deliberate deviation as the 10px tab labels. */
.camb{position:absolute;bottom:0;right:0;width:36px;height:36px;border-radius:var(--r-pill);background:var(--inverted);border:2px solid #000;display:grid;place-items:center;color:var(--on-light)}
.pstat{display:flex;justify-content:center;align-items:stretch;margin-top:24px}
.pstat>span{display:grid;justify-items:center}
.pstat i{width:1px;background:var(--card);margin:0 20px}
.pstat b{display:block;font-size:20px;font-weight:700;text-align:center}
.pstat>span>span{font-size:12px;text-transform:uppercase;color:var(--text-3);text-align:center}
.ptabs{display:flex;margin-top:24px;border-top:1px solid var(--card)}
.ptabs span{flex:1;display:grid;place-items:center;padding:12px 0;border-bottom:2px solid transparent;color:var(--text-3)}
.ptabs span.on{border-bottom-color:var(--inverted);color:var(--inverted)}
.pgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:2px}
.pgrid div{aspect-ratio:1;overflow:hidden;background:var(--raised)}
.pgrid img{width:100%;height:100%;object-fit:cover}
/* Cluster badge — the capture shows a 34px ring, not a filled pill: near-transparent centre so
   the map reads through it, one 1.5px grey stroke, white count. Real counts at hero zoom are 2s
   and 3s; the old 12 / 8 / 23 pills were the "20 pitches on one spot" problem. */
.clus{position:absolute;transform:translate(-50%,-50%);width:34px;height:34px;border-radius:var(--r-pill);background:rgba(10,10,10,.5);border:1.5px solid #6f6f6f;display:grid;place-items:center;font-size:15px;font-weight:700;color:var(--pin)}
/* Map chrome — ExploreMapSearchOverlay.tsx: absolute at topInset+8, left/right 16, gap 8.
   SearchInput.tsx: h-12 (48px), px-4, rounded-xl, gap 16, text-base neutral50, placeholder
   neutral500, and the FontAwesome search glyph sits AFTER the field at 24px neutral500.
   The overlay overrides the input's bg to neutral800.
   Filter button INACTIVE = neutral800 fill + 1px neutral700 border + neutral50 glyph at 22.
   (Active would be neutral50 fill with a black glyph — deliberately NOT shown, because the
   field reads empty and a white button would claim filters are applied.) */
.mchrome{position:absolute;top:55px;left:16px;right:16px;z-index:4;display:flex;gap:8px}
.msearch{flex:1;height:48px;background:var(--raised);border-radius:var(--r);display:flex;align-items:center;justify-content:space-between;gap:16px;padding:0 16px;font-size:16px;box-shadow:0 2px 4px rgba(0,0,0,.3)}
.msearch span{color:var(--text-3)}
.msearch svg{color:var(--text-3);flex:0 0 auto}
.mfilter{width:48px;height:48px;border-radius:var(--r);background:var(--raised);border:1px solid var(--hairline);color:var(--text-1);display:grid;place-items:center;box-shadow:0 2px 4px rgba(0,0,0,.3)}
/* FAB rail — MAP_FLOATING_BUTTON: 56px, radius 28, shadow 0 2px 4px rgba(0,0,0,.25), right 20.
   Bottom-up order is add-pitch (bottomOffset), locate (+68), matches (+136) → 12px gaps. */
.mrail{position:absolute;right:20px;bottom:104px;z-index:4;display:grid;gap:12px}
.mfab{width:56px;height:56px;border-radius:var(--r-pill);background:var(--raised);display:grid;place-items:center;color:var(--text-1);box-shadow:0 2px 4px rgba(0,0,0,.25)}
.mfab.wht{background:var(--inverted);color:var(--on-light)}
/* ---------- TAB BAR ---------- 8px pad + 46px block + 34px safe area = 88px
   Values verbatim from src/gipedo-native/components/BottomTabBar.tsx:
   BAR_BG #0C0C0F, hairline rgba(255,255,255,.08), paddingTop 8, ACTIVE #FFFFFF,
   INACTIVE rgba(255,255,255,.45), glyph box 30x30, gap 4, glow 40x40 r20 rgba(255,255,255,.12). */
.tb{position:absolute;left:0;right:0;bottom:0;height:88px;background:#0C0C0F;border-top:1px solid rgba(255,255,255,.08);padding-top:8px;z-index:5;display:grid;grid-template-columns:repeat(5,1fr)}
.tbi{display:grid;justify-items:center;align-content:start;gap:4px;height:46px;position:relative}
.tbi i{position:relative;z-index:1;color:rgba(255,255,255,.45);display:grid;place-items:center;width:30px;height:30px}
.tbi span{font-size:10px;font-weight:700;letter-spacing:.2px;color:rgba(255,255,255,.45)}
.tbi.on i{color:var(--pin)}
.tbi.on span{color:var(--pin)}
/* The active tab FILLS — source passes filled={focused}, so closed shapes take fill=color.
   A CSS declaration beats the fill="none" presentation attribute, and fill inherits through <use>. */
.tbi.on i svg{fill:currentColor}
.tbi.on::before{content:"";position:absolute;top:-5px;width:40px;height:40px;border-radius:var(--r-pill);background:rgba(255,255,255,.12)}
/* Centre FAB — 70px, 4px border in the bar colour, 38px glyph at the mark's 41:62 aspect.
   Resting #262626 with a #8A8A8A glyph; focused #2A2A2E, white glyph, 62px inner glow.
   FAB_PROTRUDE is 38 in the source, but it is a marginTop on a row that already sits 8px below
   the bar's top edge, so the FAB clears the top HAIRLINE by 30px. Both numbers in the brief are
   right — they are measured from different references. 88 - 48 - 70 = -30. */
.tfab{position:absolute;left:50%;bottom:48px;transform:translateX(-50%);width:70px;height:70px;border-radius:var(--r-pill);background:var(--raised);border:4px solid #0C0C0F;display:grid;place-items:center;z-index:6;box-shadow:0 4px 10px rgba(0,0,0,.45);color:#8A8A8A}
.tfab.on{background:#2A2A2E;color:var(--pin)}
.tfab svg{position:relative;z-index:1}
.tfab-glow{position:absolute;width:62px;height:62px;border-radius:var(--r-pill);background:rgba(255,255,255,.1)}
/* ---------- BOTTOM SHEET ---------- pure #000, 16px radius, grab handle, NO scrim */
.sh{position:absolute;left:0;right:0;bottom:0;background:var(--canvas);border-radius:var(--r-lg) var(--r-lg) 0 0;padding:0 16px 16px;z-index:5;overflow:hidden}
.sh.s75{top:25%}.sh.s95{top:5%}
.grab{width:36px;height:4px;border-radius:2px;background:var(--text-2);margin:8px auto 16px}
/* Pitch photo carousel — PitchPhotoCarousel.tsx: rounded-2xl, 358×200 (screen − 32), neutral900.
   Counter pill is top-2 LEFT (not right), rgba(0,0,0,.5), px-2 py-1, 12px/600 neutral50.
   Dots sit in their own bottom-2 centred pill, gap 4: active 8px neutral50, inactive 6px neutral500.
   Manage button is top-2 right, 36px, rgba(0,0,0,.55), camera 18px. */
.photo{position:relative;height:200px;border-radius:var(--r-lg);background:var(--card);display:grid;place-items:center;overflow:hidden}
.photo img{width:100%;height:100%;object-fit:cover}
.pill{position:absolute;top:8px;left:8px;background:rgba(0,0,0,.5);border-radius:var(--r-pill);padding:4px 8px;font-size:12px;font-weight:600;color:var(--pin)}
.pcam{position:absolute;top:8px;right:8px;width:36px;height:36px;border-radius:var(--r-pill);background:rgba(0,0,0,.55);display:grid;place-items:center}
.dots{position:absolute;bottom:8px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:4px;background:rgba(0,0,0,.5);border-radius:var(--r-pill);padding:4px 8px}
.dots i{width:6px;height:6px;border-radius:var(--r-pill);background:var(--text-3)}
.dots i.on{width:8px;height:8px;background:var(--pin)}
/* Directions / Open in Maps — bg-neutral900, px-3 py-2, rounded-lg, 16px icon + 14px/700 label. */
.ab2{background:var(--card);border-radius:var(--r-sm);padding:8px 12px;display:flex;align-items:center;justify-content:center;gap:6px;font-size:14px;font-weight:700;color:var(--text-1)}
/* In-screen buttons — app metrics: 14px/700 label, 12px radius.
   Primary is colors.neutral50 (#fafafa) on black, per the app's own primary buttons
   (ExploreMapAddPitchFab, ReportSheet, HomeBaseMapPicker all use neutral50). The app has NO
   accent colour — `colors` is just Tailwind's default palette — so amber never appears
   inside a device frame. It is the WEBSITE's accent only. */
.ab{border-radius:var(--r);padding:13px;text-align:center;font-size:14px;font-weight:700}
.ab-pri{background:var(--inverted);color:var(--on-light)}
.ab-sec{background:var(--raised);color:var(--text-1)}
.ab-del{background:var(--destructive);color:var(--pin)}
/* Date strip. On the PITCH SHEET this is inline in PitchDetails.tsx — 7 cells, px-3 py-2,
   rounded-xl, minWidth 56, and NO calendar button (that belongs to MatchesDateSelector, which
   the standalone matches list uses and which also prepends a 44×52 neutral800 picker).
   Weekday ABOVE day; selected = neutral50 fill, neutral600 weekday, black day. */
.strip{display:flex;align-items:center;gap:8px}
.dpick{width:44px;height:52px;border-radius:var(--r);background:var(--raised);display:grid;place-items:center;color:var(--text-1);flex:0 0 auto}
.dcell{flex:1;min-width:56px;background:var(--card);border-radius:var(--r);padding:8px 12px;display:grid;place-content:center;justify-items:center;gap:1px}
.dcell span{font-size:12px;font-weight:600;text-transform:uppercase;color:var(--text-3)}
.dcell b{font-size:18px;font-weight:700;color:var(--text-1)}
.dcell.on{background:var(--inverted)}
.dcell.on span{color:#525252}
.dcell.on b{color:var(--on-light)}
/* Match card — MatchCard.tsx: neutral900, rounded-xl, p-4, mb-3. Leading sport glyph in a
   neutral800 rounded-lg p-2 tile at 20px neutral300; stacked icon rows at 14px neutral400;
   people count on the RIGHT. The app has no green "spots" badge on this card. */
.mc{background:var(--card);border-radius:var(--r);padding:16px;margin-bottom:12px;display:flex;gap:12px;align-items:flex-start}
.mc-tile{background:var(--raised);border-radius:var(--r-sm);padding:8px;display:grid;place-items:center;color:var(--text-dim);flex:0 0 auto}
.mc-body{flex:1;min-width:0}
.mc-body>b{font-size:16px;font-weight:700;color:var(--text-1);display:block}
.mc-row{display:flex;align-items:center;gap:4px;font-size:14px;color:var(--text-2);margin-top:4px}
.mc-row:first-of-type{margin-top:8px}
.mc-row svg{color:var(--text-2);flex:0 0 auto}
.mc-count{display:flex;align-items:center;gap:4px;font-size:14px;font-weight:700;color:var(--text-dim);flex:0 0 auto}
.mc-count svg{color:var(--text-2)}
/* Avatars — Avatar.tsx: round, initials at 0.4× size, fill from generateAvatarColor(name), and
   a cover-fitted photo on top whenever the account has a pictureUrl. The initials and the
   generated colour stay in the markup underneath as the real fallback state — that is what a
   private account or a removed profile picture renders as.
   Stack per MatchPlayerAvatars: 28px, -8px overlap, 2px neutral900 ring, +N in 12px neutral400. */
.av{position:relative;border-radius:var(--r-pill);display:grid;place-items:center;font-weight:700;color:var(--text-1);flex:0 0 auto;overflow:hidden}
.av img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.avs{display:flex;align-items:center;margin-top:8px}
.avs .av{box-shadow:0 0 0 2px var(--card)}
.avs .av+.av{margin-left:-8px}
.avs u{text-decoration:none;font-size:12px;color:var(--text-2);margin-left:8px}
.chip{background:var(--raised);border-radius:5px;padding:2px 7px;font-size:12px;font-weight:700;color:var(--text-2)}
/* Mosaic feed — 2px hairline gutters, flush to the edges, zero corner radius */
.mos{display:grid;grid-template-columns:repeat(3,1fr);gap:2px}
.mos>*{aspect-ratio:1;overflow:hidden;position:relative}
.mos .tall{grid-row:span 2;aspect-ratio:auto}
/* Text-only tiles — PostThumbnail.tsx: TEXT_BG tints, padding 10, text VERTICALLY CENTRED,
   neutral300 at 12px/600 with a 16px line-height. */
.tt{padding:10px;font-size:12px;font-weight:600;line-height:16px;color:var(--text-dim);display:flex;align-items:center}
/* Photo tiles — licensed stock stand-ins for real user posts (Pexels, no attribution required).
   copy-outline marks multi-image posts; video posts get the 40px rgba(0,0,0,.45) play badge. */
.mph{background:var(--raised);position:relative}
.mph img{width:100%;height:100%;object-fit:cover}
.cbadge{position:absolute;top:6px;right:6px;color:var(--text-1)}
.vbadge{position:absolute;inset:0;display:grid;place-items:center}
.vbadge svg{width:40px;height:40px;padding:9px;border-radius:var(--r-pill);background:rgba(0,0,0,.45);color:var(--text-1);box-sizing:border-box}
.ph{display:grid;place-items:center;background:var(--raised);font-size:9px;font-weight:700;letter-spacing:1.5px;color:var(--text-3);text-align:center;padding:6px}
/* Reels — ReelItem.tsx. infoContainer: absolute bottom 16 (here 16 above the 88px tab bar),
   left/right 20, flex-row, justify-between, align-items flex-end. The icon column is
   ICONS_COLUMN_WIDTH 48 with gap 20 — it is bottom-aligned beside the user block, NOT centred
   vertically. Order: heart+count, chat+count, send, options dots, mute pill.
   Heart and chat are neutral600 when not liked; send / dots / mute glyph are neutral50. */
.reel{position:absolute;inset:0;background:#000;display:grid;place-items:center;overflow:hidden}
.reel video{width:100%;height:100%;object-fit:cover}
.rinfo{position:absolute;bottom:104px;left:20px;right:20px;z-index:3;display:flex;justify-content:space-between;align-items:flex-end;gap:10px}
.ruser{display:flex;align-items:center;gap:10px;min-width:0}
.ruser-t{min-width:0}
.ruser-t b{display:block;font-size:16px;font-weight:700;color:var(--text-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ruser-t span{display:block;font-size:14px;color:var(--text-1);margin-top:2px}
.ruser-t i{display:block;font-style:normal;font-size:12px;color:var(--text-2);margin-top:2px}
.rrail{width:48px;flex:0 0 auto;display:grid;justify-items:center;gap:20px}
.rrail>span{display:grid;justify-items:center}
.rrail u{text-decoration:none;font-size:12px;color:var(--text-1);margin-top:2px}
.rrail .dim{color:#525252}
.rmute{width:36px;height:36px;border-radius:var(--r-pill);background:rgba(0,0,0,.45);display:grid;place-items:center;color:var(--text-1)}
/* The one motion signature — the double-tap like burst.
   100px opaque white heart, dead centre at 40% height: 200ms in, 400ms hold, 200ms out. */
.burst{position:absolute;left:50%;top:40%;width:100px;height:100px;z-index:4;opacity:0;color:var(--pin);transform:translate(-50%,-50%)}
@keyframes burst{0%{opacity:0;transform:translate(-50%,-50%) scale(.35)}5%{opacity:1;transform:translate(-50%,-50%) scale(1)}15%{opacity:1;transform:translate(-50%,-50%) scale(1)}20%,100%{opacity:0;transform:translate(-50%,-50%) scale(1)}}
@media (prefers-reduced-motion:no-preference){.burst{animation:burst 4s var(--ease) infinite}}
