/* Skulpt.
 *
 * THIS IS LIFTED FROM THE APPROVED V4 SET, NOT REINTERPRETED.
 * The source of truth is design/approved.css and the 18 screens beside it,
 * extracted from screens-final.html which produced the artwork Shane signed off.
 *
 * Every token below - hex values, font weights, shadow stacks, type sizes - is
 * verbatim from that file. If something here needs to change, change it in the
 * artwork first, or it is no longer the approved design.
 *
 * The only adaptation: the approved file draws a fixed 393x852 phone shell for
 * a presentation board. Here the same styles run against a real viewport.
 */

@font-face{font-family:'Glacial';src:url('fonts/GlacialIndifference-Regular.woff2') format('woff2');font-weight:400;font-display:block}
@font-face{font-family:'Glacial';src:url('fonts/GlacialIndifference-Bold.woff2') format('woff2');font-weight:700;font-display:block}
@font-face{font-family:'Tusker';src:url('fonts/TuskerGrotesk-3600Semibold.woff2') format('woff2');font-weight:600;font-display:block}
@font-face{font-family:'Tusker';src:url('fonts/TuskerGrotesk-3700Bold.woff2') format('woff2');font-weight:700;font-display:block}
@font-face{font-family:'Tusker';src:url('fonts/TuskerGrotesk-3700Bold.woff2') format('woff2');font-weight:800;font-display:block}
@font-face{font-family:'TuskerWide';src:url('fonts/TuskerGrotesk-2800Super.woff2') format('woff2');font-weight:800;font-display:block}

:root{
  --red:#e11c2b; --grey:#818181; --ink:#0d0d0e; --card:#1a1c1e; --card2:#212426;
  --line:#2a2d30; --z1:#3f3f3f; --z2:#818181; --z3:#b14f56; --z4:#c93540; --z5:#e11c2b;
  --disp:'Tusker',sans-serif;
  --dispw:'TuskerWide','Tusker',sans-serif;
  --body:'Glacial',system-ui,sans-serif;
}

*{margin:0;padding:0;box-sizing:border-box;-webkit-font-smoothing:antialiased;
  -webkit-tap-highlight-color:transparent}
html,body{background:var(--ink);color:#fff;font-family:var(--body);min-height:100dvh}
/* Buttons do not inherit the page font in any browser, so every button
   without its own face was drawn in the system font (Times in some WebKit
   builds). Element-level, so any class that sets a face still wins. */
button{font-family:var(--body)}
body{overflow-x:hidden}

/* ---------------------------------------------------------------------------
   MAKING IT FEEL LIKE AN APP AND NOT A WEB PAGE

   Four things give a web app away on a phone, and none of them are how it
   looks. They are all things that MOVE when they should not.

   1. THE RUBBER BAND. Scroll past the end of a list and the whole page bounces
      and shows the background behind it. Nothing native does that, and it is
      the single biggest tell.

   2. PULL TO REFRESH. Drag down at the top of a screen and the browser reloads
      the page. In an app that is not a refresh, it is losing what you were
      doing. Same property fixes both.

   3. DOUBLE TAP ZOOM. Tap a button twice quickly and the browser zooms in
      instead of pressing it twice. It also makes every single tap wait 300ms
      to find out whether a second one is coming, which is why a web app feels
      half a beat slow on everything.

   4. LONG PRESS SELECTING THE FURNITURE. Hold a button and the label turns
      blue and offers Copy. Text you would want to copy is exempted below;
      nobody has ever wanted to copy the word "SAVE".

   PINCH ZOOM IS LEFT ALONE, deliberately. iOS has ignored `user-scalable=no`
   since iOS 10 because it is an accessibility feature, so disabling it does not
   work on iPhones anyway and would only take it away from Android. The zooming
   that actually annoys people is the accidental double-tap kind, and that is
   fixed above.
   --------------------------------------------------------------------------- */

html{
  /* Stops the bounce AND pull-to-refresh. Needs to be on html, not body. */
  overscroll-behavior:none;
  /* iOS momentum scrolling on the page rather than the browser chrome. */
  -webkit-text-size-adjust:100%;
}

body{
  overscroll-behavior:none;
  /* No double-tap zoom, and no 300ms wait on every tap. */
  touch-action:manipulation;
  /* The furniture is not text to be selected. */
  -webkit-user-select:none;
  user-select:none;
}


/* EXCEPT the things somebody would genuinely want to copy: a message, a
   session's instructions, what their coach wrote back, anything they typed.
   Taking selection away from those would be worse than the bounce. */
input, textarea, .msg, .reply, .theSession, .stepNote, .athleteNote,
.note, .coachNote, [data-selectable]{
  -webkit-user-select:text;
  user-select:text;
}

/* Any scrolling area keeps its own bounce contained rather than passing it up
   to the page. Without this, scrolling to the bottom of a thread starts
   dragging the whole screen instead. */
.thread, .list, .rail, .week, .scroll{
  overscroll-behavior:contain;
}

/* The screen. On the board this was a 393x852 shell; here it is the viewport,
   capped so it still reads as a phone on a laptop. */
.ph{position:relative;width:100%;max-width:430px;margin:0 auto;min-height:100dvh;
  display:flex;flex-direction:column;overflow:hidden;background:var(--ink)}

.grain{position:fixed;inset:0;pointer-events:none;z-index:9;opacity:.05;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E")}

/* The red bloom. A positioned element, not a filter over the top: it reads as
   light IN the scene rather than a wash across it. */
.bloom{position:absolute;border-radius:50%;pointer-events:none;z-index:1;
  background:radial-gradient(circle,rgba(225,28,43,.42) 0%,rgba(225,28,43,.13) 40%,transparent 68%)}

/* Body scroll area, held clear of the floating tab bar. */
.bd{flex:1;padding:16px 22px 0;position:relative;z-index:8}
/* Clear of the floating tab bar. The bar is 62px tall sitting 16px off the
   bottom, so anything under about 110px puts a button behind it. A page ending
   in a full-width button needs the extra. */
.padtab{padding-bottom:132px}

/* THE TUSKER RULE. Its ink overflows the line box in both directions, so tight
   leading is held apart by explicit padding, never by the line box. */
.sec{font-family:var(--disp);font-size:20px;line-height:1.05;padding-top:.08em;font-weight:700;
  letter-spacing:.06em;text-transform:uppercase;color:#fff;margin:20px 0 11px}
.sec:first-child{margin-top:0}
.htitle{font-family:var(--disp);font-size:52px;line-height:1.02;font-weight:800;color:#fff;
  text-transform:uppercase;margin-top:12px;padding-top:.02em}
.num{font-family:var(--disp);font-weight:800;color:#fff;font-variant-numeric:tabular-nums;line-height:1.02}
/* WAS 4.09:1 at 9px. This is the label under EVERY number on the live screen
   - Elapsed, Distance, Heart, Laps - read outdoors, mid-session. AA wants
   4.5:1 for normal text; .62 measures 7.70:1 on the app ground. */
.lab9{font-size:9px;letter-spacing:.2em;text-transform:uppercase;color:rgba(255,255,255,.62);font-weight:700}

/* Glass. Three stacked shadows, no border. A flat fill with a 1px edge is not
   the same thing and reads cheap next to it. */
.glass,.card{position:relative;border-radius:20px;
  background:linear-gradient(180deg,rgba(255,255,255,.075),rgba(255,255,255,.022));
  box-shadow:0 18px 40px -22px rgba(0,0,0,.95),
             inset 0 1px 0 rgba(255,255,255,.10),
             inset 0 0 0 1px rgba(255,255,255,.05);
  backdrop-filter:blur(22px) saturate(120%);border:none}
.card{padding:15px 16px}
.glass.hot{background:linear-gradient(180deg,rgba(225,28,43,.30),rgba(225,28,43,.09));
  box-shadow:0 18px 44px -20px rgba(225,28,43,.55),
             inset 0 1px 0 rgba(255,255,255,.22),
             inset 0 0 0 1px rgba(225,28,43,.42)}

.two{display:grid;grid-template-columns:1fr 1fr;gap:10px}

.kv{display:flex;justify-content:space-between;align-items:baseline;padding:10px 0;
  border-bottom:1px solid var(--line)}
.kv:last-child{border-bottom:none}
.kv span{font-size:12.5px;color:var(--grey);font-weight:700;letter-spacing:.04em;text-transform:uppercase}
.kv b{font-family:var(--disp);font-size:24px;line-height:1.02;font-weight:700;color:#fff;
  font-variant-numeric:tabular-nums}
.kv b.red{color:var(--red)}

.stat span{display:block;font-size:10px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--grey);font-weight:700}
.stat b{display:block;font-family:var(--disp);font-size:34px;line-height:1.02;color:#fff;
  font-weight:800;margin-top:5px;font-variant-numeric:tabular-nums}
.stat b.red{color:var(--red)}

/* Chips */
.chips{display:flex;gap:7px;margin-top:13px;flex-wrap:wrap}
/* Chips are body-face, so they need the trailing-space fix but not the lift. */
.chip{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  padding:7px 12px 7px calc(12px + .1em);
  border-radius:100px;background:rgba(255,255,255,.1);color:#fff;backdrop-filter:blur(6px)}
.chip.red{background:var(--red);box-shadow:0 6px 20px -6px rgba(225,28,43,.9)}
.chip.gh{background:transparent;border:1px solid rgba(255,255,255,.28)}

/* The zone band. 26px, with the zone label sitting INSIDE it. This is the data
   signature of the whole product and it is drawn from real minutes. */
.zb{display:flex;height:26px;border-radius:6px;overflow:hidden}
.zb i{display:block;height:100%;position:relative}
.zb i em{position:absolute;left:5px;top:50%;transform:translateY(-50%);font-style:normal;
  font-family:var(--disp);font-size:14px;font-weight:700;color:rgba(255,255,255,.94)}
.zb.sm{height:13px;border-radius:3px}

/* ------------------------------------------------------------------ */
/* BUTTONS, AND THE TWO REASONS TUSKER NEVER SITS RIGHT IN ONE          */
/* ------------------------------------------------------------------ */
/*
 * 1. THE TEXT SAT LOW IN EVERY BUTTON IN THE APP.
 *
 *    Measured on the real font: Tusker at 23px reports a font box of 31 above
 *    the baseline and 3 below, while the actual INK is 22 above and 1 below.
 *    So there are 9 empty pixels above the caps and 2 below them. CSS centres
 *    the BOX, which means the letters a person can see end up 3.5px low.
 *
 *    It scales with the size, at roughly .145em, so it is corrected in em and
 *    works at every size the button is used at. Total height is unchanged
 *    because what comes off the top goes back on the bottom.
 *
 *    Glacial measures 0 offset, which is why this only ever looked wrong on
 *    the display face.
 *
 * 2. CENTRED TEXT WITH LETTER-SPACING IS NOT CENTRED.
 *
 *    letter-spacing adds its space after the LAST letter as well as between
 *    them, so a centred line carries a trailing gap and sits half a space to
 *    the left. text-indent of the same amount pushes it back.
 */
:root{ --tusker-lift:.145em; }

.btn{display:block;width:100%;background:var(--red);color:#fff;text-align:center;
  padding:calc(17px - var(--tusker-lift)) 0 calc(17px + var(--tusker-lift));
  border-radius:100px;box-shadow:0 14px 34px -12px rgba(225,28,43,.85);border:none;cursor:pointer;
  font-family:var(--disp);font-size:23px;font-weight:800;letter-spacing:.05em;
  text-indent:.05em;
  text-transform:uppercase;text-decoration:none}
.btn.gh{background:linear-gradient(180deg,rgba(255,255,255,.075),rgba(255,255,255,.022));
  color:#fff;box-shadow:inset 0 1px 0 rgba(255,255,255,.10), inset 0 0 0 1px rgba(255,255,255,.05)}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn.sm{font-size:15px;
  padding:calc(12px - var(--tusker-lift)) 0 calc(12px + var(--tusker-lift))}

/* A small inline pill, for rails and toolbars.
   This exists because sixteen buttons across three screens carried their own
   `style="width:auto;padding:9px 15px;font-size:11px"`, which overrode the
   shared metrics and put the Tusker correction back to nothing on every one of
   them. A button's proportions belong in one rule, not retyped per element. */
.btn.pill{width:auto;font-size:11px;
  padding:calc(9px - var(--tusker-lift)) 15px calc(9px + var(--tusker-lift));
  text-indent:.05em}
.btn.pill.mid{font-size:13px;
  padding:calc(10px - var(--tusker-lift)) 18px calc(10px + var(--tusker-lift))}
/* A full-size button that only wants to be as wide as its words. */
.btn.wide{width:auto;font-size:15px;
  padding:calc(13px - var(--tusker-lift)) 24px calc(13px + var(--tusker-lift));
  text-indent:.05em}
.btn.tiny{width:auto;font-size:10px;
  padding:calc(6px - var(--tusker-lift)) 12px calc(6px + var(--tusker-lift));
  text-indent:.05em}

/* A COUNT ON A BUTTON.
   Somebody waiting on a reply has to read as waiting. A number in brackets in
   the same grey as every other button is a label, not an alert, and it gets
   scanned past. The count sits in its own solid disc so the eye lands on it
   before it reads the word.

   Two tones on purpose. Red is somebody who has actually messaged and is
   sitting there; amber is somebody who has gone quiet and is worth a look
   today. Making both red makes neither of them mean anything by Thursday. */
.btn.hasCount{display:inline-flex;align-items:center;gap:8px;
  padding-right:calc(15px - 4px)}
.btn.hasCount.urgent{background:var(--red);color:#fff;
  box-shadow:0 12px 30px -12px rgba(225,28,43,.9)}
.btn.hasCount.warn{background:#8a6a12;color:#fff;
  box-shadow:0 12px 30px -14px rgba(201,162,39,.75)}
.btn .count{font-family:var(--body);font-style:normal;font-weight:800;
  font-size:11px;line-height:1;letter-spacing:0;text-indent:0;
  min-width:20px;padding:4px 6px;border-radius:100px;text-align:center;
  background:rgba(0,0,0,.34);color:#fff}

/* And it has to survive being looked at, not just noticed once. A slow pulse
   on the unread one only, and only for people who have not asked for less
   movement. */
@media (prefers-reduced-motion: no-preference){
  .btn.hasCount.urgent .count{animation:countPulse 2.4s ease-in-out infinite}
}
@keyframes countPulse{
  0%,100%{transform:scale(1);background:rgba(0,0,0,.34)}
  50%{transform:scale(1.09);background:rgba(0,0,0,.52)}
}

/* THE TAB BAR. Five items, and the middle one is a red circle. Four icons with
   no play button is a different bar and was wrong. */
.tab{position:fixed;left:50%;transform:translateX(-50%);bottom:calc(16px + var(--app-bottom,env(safe-area-inset-bottom,0px)));
  width:min(390px,calc(100% - 40px));height:62px;border-radius:100px;z-index:14;
  background:rgba(20,20,22,.72);backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,.09);
  display:flex;align-items:center;justify-content:space-around;box-shadow:0 16px 40px -18px #000}
/* The hit area was the 21x21 SVG itself, under the WCAG 2.5.8 minimum of 24
   and well under Apple's 44. This is a phone app; thumbs are not styluses. */
.tab a{display:grid;place-items:center;opacity:.42;text-decoration:none;
  width:46px;height:46px;border-radius:50%}
.tab a.on{opacity:1}
.tab a.mid{width:46px;height:46px;border-radius:50%;background:var(--red);opacity:1;
  box-shadow:0 8px 24px -6px rgba(225,28,43,.8)}

/* Forms */
label{display:block;font-size:11px;letter-spacing:.15em;text-transform:uppercase;
  color:var(--grey);margin-bottom:7px;font-weight:700}
input,select,textarea{width:100%;padding:13px 15px;border-radius:12px;
  border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.05);color:#fff;
  font-family:var(--body);font-size:15px}
input:focus,select:focus,textarea:focus{outline:0;border-color:var(--red)}
/* iOS draws a date input natively, at its own minimum width, and ignores
   width:100% - so it ran past the edge of the card (Alex's screenshot, People,
   "In force from"). Taking the native appearance off makes it size like every
   other input; min-height keeps an EMPTY one from collapsing, which is what
   iOS does once the appearance is gone. */
input[type="date"]{-webkit-appearance:none;appearance:none;display:block;min-width:0;
  max-width:100%;min-height:46px;text-align:left}
input[type="date"]::-webkit-date-and-time-value{text-align:left}
select option{background:var(--card)}
/* A SELECT IN SAFARI KEEPS ITS OWN LOOK unless that is switched off, and its
   own look ignores a dark background: Sport and Level on the session editor
   came out as white boxes with pale grey writing (WebKit, 1 Oct). Native look
   off, and our own red chevron so it still reads as a dropdown. */
select{-webkit-appearance:none;appearance:none;min-height:46px;padding-right:38px}
/* Pages set their own select background with the shorthand, which wipes an
   image, so the chevron is put back at a specificity those rules do not beat. */
body select:not([multiple]){
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23e11c2b' stroke-width='2'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 14px center;background-size:12px 8px}

.muted{color:var(--grey);font-size:12px}
/* Shane's carb day key (carbGuide() in app.js). The day it is, if there is
   one, is lit; the other two stay grey. */
.carbGuide{display:grid;grid-template-columns:max-content 1fr;gap:4px 12px;
  font-size:12px;line-height:1.45;color:var(--grey);margin-top:10px}
.carbGuide:first-child{margin-top:0}
.carbGuide>div{display:contents}
.carbGuide b{font-weight:700;letter-spacing:.06em;text-transform:uppercase;font-size:10px;
  color:rgba(255,255,255,.62);padding-top:.15em}
.carbGuide .on b{color:var(--red)}
.carbGuide .on span{color:#fff}
/* WAS 4.07:1. Every error message in the app used this, which made the
   hardest text in the app the text you most need to read. A lighter red keeps
   the meaning and clears AA. */
.err{color:#ff6b76;font-size:13px}
.ok{color:#6ac47a;font-size:13px}
.state{padding:70px 22px;text-align:center;color:var(--grey);font-size:14px;line-height:1.6}

.fontWarn{position:fixed;top:0;left:0;right:0;z-index:99;background:var(--red);color:#fff;
  padding:9px 14px;font-size:12px;text-align:center;display:none}
.toast{position:fixed;left:50%;transform:translateX(-50%);bottom:96px;z-index:80;
  padding:13px 22px;border-radius:100px;background:var(--card);border:1px solid rgba(255,255,255,.12);
  font-size:13px;display:none;box-shadow:0 10px 40px rgba(0,0,0,.6)}
.toast.show{display:block}
.toast.bad{border-color:var(--red)}

/* THE HERO PANEL. Verbatim from design/approved.css, used by session complete
   and the race day brief. The -50px pulls the photo up under the status bar, so
   the image starts at the very top of the screen and the rounded corners are
   only at the bottom. On the board there was a drawn status bar to sit under;
   here the device draws its own, so the pull is to 0 and the top inset is
   handled by the safe area instead. */
.hero{position:relative;flex:0 0 auto;margin-top:0}
.hero img{width:100%;display:block;border-radius:0 0 30px 30px}
.hero .scrim{position:absolute;inset:0;border-radius:0 0 30px 30px;
  background:linear-gradient(180deg,rgba(13,13,14,.72) 0%,rgba(13,13,14,.12) 30%,rgba(13,13,14,.55) 66%,rgba(13,13,14,.96) 100%)}
.hero .on{position:absolute;left:22px;right:22px;bottom:20px;z-index:3}
.badge{width:44px;height:44px;border-radius:13px;background:var(--red);display:grid;place-items:center;
  flex:0 0 auto;box-shadow:0 10px 26px -8px rgba(225,28,43,.85)}
.brow{display:flex;align-items:center;gap:12px}
.kick{font-family:var(--disp);font-size:17px;font-weight:700;letter-spacing:.1em;
  text-transform:uppercase;color:#fff}

/* The red-edged aside. It appears on five approved screens (connect the watch,
   test result, session detail, prescribed vs actual, race brief) and was being
   redeclared inside each page's <style>. */
.aside{border-left:3px solid var(--red);background:#171214;padding:13px 14px;
  border-radius:0 10px 10px 0;font-size:13px;line-height:1.5;color:#ddd}

/* A NATIVE <dialog> CENTRES ITSELF WITH margin:auto, AND THE RESET KILLS IT.
   `*{margin:0}` at the top of this file wins over the browser's own dialog
   rule, so every modal in the app was pinned hard to the top left corner:
   measured 0px from the left edge and 660px from the right on a 1280 viewport.
   Nothing errors, it just looks broken. Put the margin back. */
dialog:modal{margin:auto}

/* THE BUTTONS MUST ALWAYS BE ON SCREEN. Each page capped the middle at 60-64vh,
   and vh on an iPhone is the height with the Safari bars hidden, so head + body
   + foot came out taller than the phone and Cancel / Add and invite sat half
   off the bottom. The dialog now fits the visible screen and only the middle
   scrolls. The specificity beats each page's own .dlgBody rule on purpose. */
dialog[open]:has(> .dlgBody){display:flex;flex-direction:column;overflow:hidden;
  max-height:calc(100dvh - 24px)}
dialog[open] > .dlgHead, dialog[open] > .dlgFoot{flex:none}
dialog[open] > .dlgBody{flex:1 1 auto;min-height:0;max-height:none}

/* A coach looking at one of their athletes' screens. Fixed to the top so it is
   there on every one of them, and out of the way of the content. */
.viewing{position:fixed;top:0;left:0;right:0;z-index:40;display:flex;align-items:center;
  justify-content:space-between;gap:12px;padding:9px 15px;
  padding-top:calc(9px + var(--app-top,env(safe-area-inset-top,0px)));
  background:rgba(13,13,14,.86);backdrop-filter:blur(14px);
  box-shadow:inset 0 -1px 0 rgba(255,255,255,.08)}
.viewing .vBack{color:#fff;text-decoration:none;font-size:12.5px;font-weight:700;
  letter-spacing:.02em}
.viewing .vBack span{color:var(--red);font-weight:800;font-size:15px;margin-right:2px}
.viewing .vWho{font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--grey);font-weight:700}
/* THE WAY BACK on the coach's own screens that are not the hub: follow ups,
   notifications, support, their profile. Shane, 5 Oct: "Do we also have back
   buttons?" They had none, so the only way back was a swipe. Same words and
   chevron as the bar above. */
.hubBack{display:inline-flex;align-items:center;gap:5px;color:#fff;text-decoration:none;
  font-size:12.5px;font-weight:700;letter-spacing:.02em;margin-bottom:16px;font-family:var(--body)}
.hubBack span{color:var(--red);font-weight:800;font-size:15px;margin-top:-2px}
/* Push the page down so the bar never sits on top of the first thing on it. */
body:has(.viewing) .ph,
body:has(.viewing) .stage,
body:has(.viewing) .detail{padding-top:38px}

/* ROUND TWELVE: the bar is on every coach screen but the hub's front page,
   with Hub on the right. A screen with none of the three wrappers above is
   pushed down by the body instead, never by both. */
.viewing .vBack{padding:3px 6px 3px 0;cursor:pointer}
.viewing .vWho{flex:1;text-align:center}
.viewing .vHub{color:#bdbdbd;text-decoration:none;font-size:11px;font-weight:800;letter-spacing:.12em;
  text-transform:uppercase;padding:4px 11px;border-radius:100px;background:rgba(255,255,255,.07)}
.viewing .vHub:hover{color:#fff}
.navSpace{height:calc(38px + var(--app-top,env(safe-area-inset-top,0px)));grid-column:1 / -1}
body:has(.ph, .stage, .detail) .navSpace{display:none}
.hubBack{display:none}

/* An athlete's way back: a glass pill over the top left of the screen. */
.backPill{position:fixed;z-index:45;top:calc(var(--app-top,env(safe-area-inset-top,0px)) + 10px);left:12px;
  display:inline-flex;align-items:center;gap:4px;padding:8px 13px 8px 10px;border-radius:100px;
  color:#fff;text-decoration:none;font-family:var(--body);font-size:12.5px;font-weight:700;letter-spacing:.02em;
  background:rgba(13,13,14,.62);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.12),0 10px 24px -12px rgba(0,0,0,.9)}
.backPill span{color:var(--red);font-weight:800;font-size:16px;margin-top:-2px}
/* Room for it above the first line, the same way the coach's bar gets room. */
body:has(#backPill) .ph{padding-top:calc(48px + var(--app-top,env(safe-area-inset-top,0px)))}

/* A CLIENT'S SECTIONS, the same four tabs on each of their pages. */
.clientTabs{display:flex;gap:4px;padding:4px;border-radius:100px;background:rgba(255,255,255,.06);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.06);margin:14px 0 4px;overflow-x:auto;scrollbar-width:none;
  max-width:100%}
.clientTabs::-webkit-scrollbar{display:none}
.clientTabs a{flex:1 1 0;min-width:0;text-align:center;white-space:nowrap;text-decoration:none;color:#bdbdbd;
  font-family:var(--body);font-size:11px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;
  padding:10px 6px;border-radius:100px}
@media (max-width:380px){.clientTabs a{font-size:10px;letter-spacing:.02em;padding:9px 4px}}
.clientTabs a.on{background:#fff;color:#0d0d0e}
.clientTabs a:not(.on):hover{color:#fff}

/* TILES: the hub's front page and a client's home. One tap each to one thing. */
.tiles{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-top:18px}
@media (max-width:900px){.tiles{grid-template-columns:repeat(2,minmax(0,1fr))}}
.tile{position:relative;display:flex;flex-direction:column;justify-content:space-between;gap:12px;
  min-height:112px;padding:15px 15px 14px;border-radius:18px;text-decoration:none;color:#fff;cursor:pointer;
  border:0;text-align:left;font-family:var(--body);
  background:linear-gradient(180deg,rgba(255,255,255,.075),rgba(255,255,255,.022));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08),inset 0 0 0 1px rgba(255,255,255,.05),0 18px 40px -26px rgba(0,0,0,.95)}
.tile:hover{box-shadow:inset 0 1px 0 rgba(255,255,255,.08),inset 0 0 0 1px rgba(225,28,43,.55)}
.tile .tName{font-size:11px;letter-spacing:.16em;text-transform:uppercase;font-weight:800;color:#e6e6e6}
.tile .tWhat{font-size:12px;line-height:1.45;color:#9f9f9f}
.tile .tNum{font-family:var(--disp);font-size:40px;line-height:1.02;padding-top:.06em;padding-bottom:.04em;font-weight:800}
.tile .tNum.red{color:var(--red)}
.tile .tNum.amber{color:#f0b35a}
.tile.big{min-height:130px}
.tile.primary{background:linear-gradient(160deg,rgba(225,28,43,.32),rgba(225,28,43,.08));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.12),inset 0 0 0 1px rgba(225,28,43,.55),0 18px 40px -22px rgba(225,28,43,.55)}
.tile .tGo{position:absolute;right:14px;top:13px;color:var(--red);font-weight:800;font-size:18px}

/* THE BATCH PASTE (app.js openBatch), on the session library and the meal bank.
   Its own box styles because the pages it opens on have no dialog of their own. */
dialog.batchDlg{border:none;border-radius:20px;background:var(--card);color:#fff;padding:0;
  width:min(640px,calc(100vw - 24px))}
dialog.batchDlg::backdrop{background:rgba(0,0,0,.76)}
.batchDlg .dlgHead{padding:19px 21px;border-bottom:1px solid var(--line)}
.batchDlg .dlgBody{padding:16px 21px;overflow-y:auto}
.batchDlg .dlgFoot{padding:14px 21px;border-top:1px solid var(--line);display:flex;gap:10px;
  justify-content:flex-end;flex-wrap:wrap}
.batchDlg .dlgFoot .btn{width:auto;padding:12px 20px;font-size:15px}
.batchDlg .bSteps{margin:10px 0 0 18px;font-size:12.5px;line-height:1.55;color:#c9c9c9}
.batchDlg .bSteps li{margin-top:3px}
.batchDlg .bCopied{font-size:12px;color:#8fdca6;margin-top:8px;line-height:1.5}
.batchDlg .bCopied pre{white-space:pre-wrap;font-size:11px;color:#ddd;background:rgba(255,255,255,.05);
  padding:10px;border-radius:10px;margin-top:6px;max-height:180px;overflow:auto}
.batchDlg textarea{min-height:190px;margin-top:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:12px;line-height:1.55}
.batchDlg .bHead{font-weight:700;margin:14px 0 6px;font-size:13px}
.batchDlg .bNote{margin-top:12px;font-size:12.5px;color:#d9c27a}
.batchDlg .bi{padding:9px 0;border-bottom:1px solid rgba(255,255,255,.07);font-size:12.5px;line-height:1.45}
.batchDlg .bi b{display:block;font-size:13px}
.batchDlg .bi span{color:#c9c9c9}
.batchDlg .bi.bad span{color:#ff8a93}
.batchDlg .bi.skip span{color:#a5a5a5}
.batchDlg .bi.added span{color:#8fdca6}

/* PHOTOS FROM A SESSION OR A RACE. Shane, 5 Oct: "could we give clients the
   ability to upload photos after a session? Or after a race". One strip, the
   same wherever they show: photoStrip() in app.js. */
.photoStrip{display:grid;grid-template-columns:repeat(auto-fill,minmax(86px,1fr));gap:8px}
.photoTile{aspect-ratio:1;border:0;border-radius:14px;overflow:hidden;padding:0;cursor:pointer;
  background:rgba(255,255,255,.06);box-shadow:inset 0 0 0 1px rgba(255,255,255,.08);display:block}
.photoTile img{width:100%;height:100%;object-fit:cover;display:block}
.photoTile.photoPick{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;
  color:#d4d4d4;font-family:var(--body);font-size:10px;font-weight:800;letter-spacing:.08em;
  text-transform:uppercase;background:none;box-shadow:inset 0 0 0 1px rgba(255,255,255,.18)}
.photoTile.photoPick b{font-family:var(--body);font-size:38px;font-weight:400;color:var(--red);line-height:.8}
.photoTile.photoPick:disabled{opacity:.5}
.photoMsg{margin-top:8px;font-size:12.5px;line-height:1.5;color:#c9c9c9}
.photoMsg.bad{color:#ff8a93}
dialog.photoView{border:none;border-radius:18px;background:#000;color:#fff;padding:0;overflow:hidden;
  width:min(760px,calc(100vw - 24px));box-shadow:0 30px 80px -20px #000}
dialog.photoView::backdrop{background:rgba(0,0,0,.86)}
.photoView img{display:block;width:100%;max-height:76dvh;object-fit:contain;background:#000}
.photoView .pvFoot{display:flex;gap:10px;justify-content:space-between;padding:12px}
.photoView .pvFoot .btn{width:auto;padding:11px 20px;font-size:15px}

/* Last in the file on purpose: it has to come after the Forms rule above
   (15px) to win at the same specificity. */
/* 5. TAP-TO-TYPE ZOOM (Shane's screenshot, Messages, 6 Oct). iOS zooms the
   whole page in when you tap a box whose writing is under 16px, and leaves it
   zoomed when the keyboard goes: the screen came back 16/15 too big with Send
   and Hub off the right edge. Every box was 12 to 15px. On an iPhone or iPad
   (the only browsers that know -webkit-touch-callout) they are 16px. Only the
   SMALL ones are lifted: the first version did every box with !important and
   shrank the 64px weight on the daily screen to 16px. The base rule loses to
   any screen's own bigger size; the named ones are the screens that set a
   smaller size of their own. Desktop keeps the designed sizes. */
@supports (-webkit-touch-callout:none){
  input, select, textarea{font-size:16px}
  .field input, .box textarea, .fld input, .fld select, .qs textarea, .tpl .body input,
  .newWrap textarea, textarea#theSession, .batchDlg textarea{font-size:16px !important}
}

/* THE STORE APPS. The phone keeps the page clear of the clock and the home bar
   itself (iOS: the web view's content inset; Android: the web view sits inside
   the system bars, see app.js), so the page must not add the same space again.
   Every top and bottom safe-area rule reads these first; the website never sets
   them, so on the web the env() behind each one is used exactly as before. */
html.inApp{--app-top:0px;--app-bottom:0px}
/* A notification that lands while the Android app is open (app.js). */
.appBanner{position:fixed;left:12px;right:12px;top:12px;z-index:200;padding:13px 16px;border-radius:16px;
  background:rgba(23,25,27,.94);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);color:#fff;
  font-size:14px;line-height:1.4;font-weight:600;box-shadow:inset 0 0 0 1px rgba(255,255,255,.1),0 14px 30px -12px rgba(0,0,0,.9);
  border-left:3px solid var(--red);cursor:pointer}

/* "Turn notifications on", on Today and the hub (app.js pushNudge, 22 Oct). */
.pushNudge{margin-top:14px;padding:14px 15px;border-radius:14px;background:rgba(255,255,255,.07);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.12);color:#fff;text-align:left}
.pushNudge b{display:block;font-size:13.5px;font-weight:700}
.pushNudge i{display:block;font-style:normal;font-size:12px;color:rgba(255,255,255,.72);margin-top:4px;line-height:1.45}
.pushNudge .pnActs{display:flex;gap:8px;margin-top:11px}
.pushNudge .pnActs .btn{width:auto;flex:1;padding:10px 12px;font-size:14px;text-align:center;text-decoration:none}
.pushNudge .pnMsg{font-size:12px;color:#ff8a93;margin-top:8px;line-height:1.45}
.pushNudge .pnMsg:empty{display:none}
