:root{
  /* Surfaces. A dark theme separates by a visible EDGE, not a big fill jump: contrast
     ratios compress at low luminance, so lifting a card far enough to read as "1.5
     against the page" just makes it washed-out grey. Measured targets instead: the card
     sits 1.13 on the page, a control 1.20 on the card, and --line does the real work at
     3.6 against a card and 3.0 against every surface, which is the WCAG 1.4.11 floor for
     a control boundary. The old palette had that border at 1.26, which is invisible, and
     that, not the text colour, is why the app looked flat.
     Every pair below is asserted in tests/test_contrast.py so this cannot silently rot. */
  --bg:#0a0d13; --surface:#161b26; --raised:#222a3a; --well:#0f131c;
  --line:#6a7384; --line-soft:#2e3747;
  --ink:#f4f6fa; --muted:#a8b3c4; --dim:#7d8798;
  --accent:#4cc4a0; --on-accent:#05120c; --red:#ff7d7d; --amber:#ffc46b;
  --on-red:#2a0b0b; --on-amber:#231a05; --beam:#7dffd4;
  /* Channel triples, so a tint is mixed from the token instead of the colour being
     hard-coded a second time and drifting when the token changes. */
  --accent-rgb:76 196 160; --red-rgb:255 125 125; --amber-rgb:255 196 107;
  /* The calorie split's three arcs: one lighter than the accent, the accent, one deeper.
     Steps of OPACITY were tried first and do not work on a dark ground, because fading a
     colour toward a near-black page only ever moves it toward the page: at .34 the fat arc
     measured 2.04 against the card, under the 3:1 a graphic carrying meaning has to clear.
     Stepping lightness up as well as down keeps all three above 3:1 on the card AND on a
     raised surface, and keeps them one family so green still reads as green. */
  --split-p:#a6ead2; --split-c:var(--accent); --split-f:#2a8f78;
  --muted-rgb:168 179 196;
  /* The camera button's glass. One light source, upper left, so the lit stop and the deep stop are
     the SAME green at two different distances from that light, not two different greens. They are
     tokens because light mode will need its own pair, and a colour written into a rule is a colour
     that cannot follow a theme. --on-accent-rgb is the same near-black as --on-accent, as a triple,
     so the shadows inside the glass are mixed from the palette instead of guessed. */
  --lens-lit:#a5f0d5; --lens-deep:#2f9c7e; --on-accent-rgb:5 18 12; --lit-rgb:255 255 255;
  /* The focus reticle, defined here and brightened locally when pressed. */
  --bracket:rgb(var(--accent-rgb)/.5);
  /* One spacing and radius scale, so padding stops being decided per rule. */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px;
  --r1:10px; --r2:14px; --r3:18px; --r4:26px; --pill:999px;
  --pad:16px;
  /* Elevation: an edge plus a soft shadow. Shadow alone cannot be seen on a dark ground. */
  --lift-1:0 1px 2px rgb(0 0 0 / .35);
  --lift-2:0 4px 14px rgb(0 0 0 / .35);
  --lift-3:0 6px 22px rgb(0 0 0 / .45);
}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;background:var(--bg);color:var(--ink);
  font:16px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  overflow-x:hidden}
body{padding:env(safe-area-inset-top) var(--pad) calc(env(safe-area-inset-bottom) + 24px)}
.screen{max-width:560px;margin:0 auto}
header{display:flex;align-items:center;justify-content:space-between;padding:18px 0 8px}
.brand{font-size:21px;font-weight:650;margin:0;letter-spacing:-.01em}
.muted{color:var(--muted)}
.hint{color:var(--muted);text-align:center;font-size:15px;margin:14px 0 0}
.error{color:var(--red)}

button{font:inherit;border:0;border-radius:14px;padding:16px 20px;min-height:52px}
.primary{background:var(--accent);color:var(--on-accent);font-weight:650;width:100%;
  box-shadow:var(--lift-1)}
.ghost{background:transparent;color:var(--muted);border:1px solid var(--line);
  padding:10px 16px;min-height:44px;border-radius:11px}

/* A visible focus ring. :focus-visible only, so it appears for a keyboard or a switch and never
   as a box left behind after a tap. Offset so it reads as a ring rather than thickening the border. */
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r1)}

#lock{padding-top:22vh;text-align:center}
#passcode{width:100%;margin:18px 0 12px;padding:18px;font-size:26px;text-align:center;
  letter-spacing:.3em;background:var(--surface);border:1px solid var(--line);
  border-radius:14px;color:var(--ink)}

/* THE CAMERA BUTTON IS THE APP.
   Lance, 25 Sep 2026: "that is the most used button in the entire app and it's the foundation of
   the entire thing... it must exude trust and professionalism as if this technology is extremely
   reliable accurate and trustworthy."

   Trust is drawn with PRECISION, not with decoration. Everything here is a real optical cue rather
   than an ornament: a glass element with one light source, a barrel edge, an aperture, and a focus
   reticle. Nothing bounces, nothing rainbows, nothing celebrates. An instrument you would believe a
   measurement from is still, exact, and answers the instant you touch it.

   PRESSING IT OPENS THE SYSTEM CAMERA, and that is the constraint the press animation is built
   around: the input is inside the label, so the picker opens on the browser's own gesture handling
   with nothing deferred. Every effect below is CSS on :active, so it plays WHILE the camera sheet
   rises and can never hold it up. Anything on a timer here would delay the camera, and a shutter
   that hesitates is the opposite of the thing this button is trying to say. */
.shoot{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:20px;
  margin-top:8vh;padding:42px 20px 38px;border:1px solid var(--line);
  /* The card is lit from the same direction as the glass on it. A control reads as one solid object
     when every surface agrees about where the light is. */
  background:linear-gradient(180deg,var(--raised) 0%,var(--surface) 62%);
  border-radius:var(--r4);cursor:pointer;box-shadow:var(--lift-2);
  transition:transform .14s cubic-bezier(.2,.8,.3,1),border-color .14s,box-shadow .14s}
.shoot:active{transform:scale(.985);border-color:rgb(var(--accent-rgb)/.55)}
/* A label's focus lands on the input inside it, which is 1px and invisible, so the ring has to be
   drawn on the thing a person can actually see. */
.shoot:focus-within{outline:3px solid rgb(var(--accent-rgb)/.75);outline-offset:3px}
.shoot input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}

.lensWrap{position:relative;display:grid;place-items:center;width:150px;height:150px}

/* The focus reticle: four corner brackets, drawn as eight backgrounds on one element rather than
   eight spans. They close in when pressed, which is what a camera does when it takes the shot. */
.lensWrap::before{content:"";position:absolute;inset:0;pointer-events:none;
  background:
    linear-gradient(var(--bracket),var(--bracket)) left top/14px 2px no-repeat,
    linear-gradient(var(--bracket),var(--bracket)) left top/2px 14px no-repeat,
    linear-gradient(var(--bracket),var(--bracket)) right top/14px 2px no-repeat,
    linear-gradient(var(--bracket),var(--bracket)) right top/2px 14px no-repeat,
    linear-gradient(var(--bracket),var(--bracket)) left bottom/14px 2px no-repeat,
    linear-gradient(var(--bracket),var(--bracket)) left bottom/2px 14px no-repeat,
    linear-gradient(var(--bracket),var(--bracket)) right bottom/14px 2px no-repeat,
    linear-gradient(var(--bracket),var(--bracket)) right bottom/2px 14px no-repeat;
  transition:inset .16s cubic-bezier(.2,.8,.3,1)}
.shoot:active .lensWrap::before{inset:9px;--bracket:rgb(var(--accent-rgb)/.9)}

/* Ready, not nagging: a slow bloom behind the glass, about the rate of a resting breath. */
.lensWrap::after{content:"";position:absolute;width:128px;height:128px;border-radius:50%;
  background:radial-gradient(circle,rgb(var(--accent-rgb)/.28),transparent 70%);
  animation:lensReady 3.4s ease-in-out infinite}
@keyframes lensReady{0%,100%{opacity:.5;transform:scale(1)}50%{opacity:1;transform:scale(1.07)}}

/* The glass. One light source, upper left, held consistently by every highlight and shadow here:
   two light sources is the thing that makes a rendered object look fake. */
.lens{width:112px;height:112px;border-radius:50%;display:block;position:relative;z-index:1;
  /* A shallow gradient, not a bulge. A big white blowout across the top is how a button ends up
     looking moulded and cheerful; an instrument is nearly flat with its light held at the edge. */
  background:radial-gradient(circle at 42% 34%,var(--lens-lit) 0%,var(--accent) 52%,var(--lens-deep) 100%);
  box-shadow:
    0 0 0 1px rgb(var(--lit-rgb)/.10),                 /* a machined outer edge */
    0 0 0 5px rgb(var(--accent-rgb)/.13),              /* the coating, sitting off the barrel */
    0 14px 30px rgb(var(--on-accent-rgb)/.5),          /* weight, so it rests ON the card */
    inset 0 -8px 16px rgb(var(--on-accent-rgb)/.42),   /* the underside turning away */
    inset 0 2px 5px rgb(var(--lit-rgb)/.16);           /* the top edge catching the light */
  transition:transform .14s cubic-bezier(.2,.8,.3,1),box-shadow .14s}
/* Pressed: it sits DOWN into the card and the coating tightens around it. Travel plus a change of
   light, because a press that only changes colour reads as a state, and a press that moves reads as
   a mechanism. */
.shoot:active .lens{transform:scale(.945);
  box-shadow:
    0 0 0 1px rgb(var(--lit-rgb)/.16),
    0 0 0 9px rgb(var(--accent-rgb)/.2),
    0 3px 10px rgb(var(--on-accent-rgb)/.55),
    inset 0 -4px 12px rgb(var(--on-accent-rgb)/.5),
    inset 0 2px 4px rgb(var(--lit-rgb)/.1)}

/* The aperture. It closes on the press: the single clearest way to say "the shot is being taken"
   without drawing a shutter that is not really there. */
.lens::before{content:"";position:absolute;inset:27px;border-radius:50%;
  border:6px solid var(--on-accent);
  /* An opening is a HOLE: dark inside, with the lit rim of the cut showing at its edge. Drawing it
     as a flat dark ring is what made the old one read as a printed circle rather than an aperture. */
  box-shadow:0 0 0 1px rgb(var(--lit-rgb)/.14),inset 0 0 0 1px rgb(var(--on-accent-rgb)/.9),
    inset 0 1px 6px rgb(var(--on-accent-rgb)/.65);
  transition:inset .14s cubic-bezier(.2,.8,.3,1),border-width .14s}
.shoot:active .lens::before{inset:33px;border-width:8px}

/* The specular: the one highlight that makes it read as glass rather than paint. */
.lens::after{content:"";position:absolute;inset:0;border-radius:50%;pointer-events:none;
  background:linear-gradient(161deg,rgb(var(--lit-rgb)/.15) 0 11%,rgb(var(--lit-rgb)/0) 38%)}

.shootLabel{font-size:21px;font-weight:650;letter-spacing:-.015em}
.shoot:active .shootLabel{color:var(--accent)}

/* Reduced motion keeps every STATE and drops every MOVEMENT: pressed still looks pressed, so the
   button still answers, it just does not travel to say so. */
@media (prefers-reduced-motion:reduce){
  .shoot,.lens,.lens::before,.lensWrap::before{transition:none}
  .shoot:active{transform:none}
  .shoot:active .lens{transform:none}
  .lensWrap::after{animation:none;opacity:.75}
}

.pick{display:block;margin-top:10px;padding:14px 20px;text-align:center;
  background:transparent;border:1px solid var(--line-soft);border-radius:14px;
  color:var(--muted);font-size:15px;min-height:50px;cursor:pointer}
.pick input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}

/* Radar sweep. Only transform and opacity animate, so it composites on the GPU
   and costs no layout or paint per frame. The glow is a static box-shadow on a
   moving element, not an animated shadow, which would be a repaint every frame. */
.scanWrap{position:relative;margin-top:10px;border-radius:20px;overflow:hidden;min-height:160px}
#shot{width:100%;display:block}
.scan{position:absolute;inset:0;pointer-events:none;transition:opacity .32s ease}
.scan.done{opacity:0}
/* a faint grid for the instrument feel, static */
.scan::after{content:"";position:absolute;inset:0;
  background-image:linear-gradient(rgb(var(--accent-rgb) / .07) 1px,transparent 1px),
    linear-gradient(90deg,rgb(var(--accent-rgb) / .07) 1px,transparent 1px);
  background-size:14.2857% 14.2857%}
/* The beam is the glowing line itself, moved in PIXELS by the Web Animations API
   once the photo is on screen (see runSweep in app.js). A translateY(%) sweep sat
   frozen at the top of the photo on Lance's iPhone; pixel travel fixed it there,
   confirmed on his phone 13 Sep 2026. No percentages, no animated ancestor. */
.beam{position:absolute;top:0;left:-4%;right:-4%;height:2px;border-radius:2px;
  background:var(--beam);
  box-shadow:0 0 6px 2px rgb(125 255 212 / .85), 0 0 22px 8px rgb(var(--accent-rgb) / .55),
             0 0 60px 20px rgb(var(--accent-rgb) / .28);
  opacity:0;will-change:transform}
.beam.live{opacity:1}
/* the trail, above the line, so it follows the beam down */
.beam::before{content:"";position:absolute;left:4%;right:4%;bottom:2px;height:76px;
  background:linear-gradient(to top,rgb(var(--accent-rgb) / .34),transparent)}

/* Progress bar. Fills toward 90% over 13s, which is where the median answer
   lands, then holds. It never sits at 100% waiting, because a full bar with
   nothing happening reads as broken. The last tenth closes when the answer does. */
.progress{height:4px;border-radius:3px;background:var(--well);overflow:hidden;margin-top:12px}
.progressFill{height:100%;width:100%;border-radius:3px;background:var(--accent);
  transform:scaleX(0);transform-origin:left;will-change:transform}
.progress.full .progressFill{animation:none;transform:scaleX(1);
  transition:transform .3s ease}
@keyframes fill{from{transform:scaleX(0)}to{transform:scaleX(.9)}}

@media (prefers-reduced-motion:reduce){
  .beam{animation:none;top:50%;opacity:.5}
  .jitter{animation:none}
  .progressFill{animation:none;transform:scaleX(.5)}
}

/* the photo stays at the top of the summary, with numbered labels under it */
.summaryShot{width:100%;display:block;border-radius:18px;margin-bottom:14px}

.swapHint{margin:14px 0 6px;color:var(--muted);font-size:13px}
/* Alternatives must never be truncated: this is the correction path the whole
   accuracy design leans on, and an unreadable option cannot be chosen. */
.alts button{flex:1 1 100%;text-align:left;white-space:normal;line-height:1.35;
  padding:12px 14px;overflow-wrap:anywhere}
.conf{margin-top:8px;font-size:13px;color:var(--amber)}
.conf.low{color:var(--red)}
.working{display:flex;align-items:center;gap:12px;justify-content:center;padding:20px 0;color:var(--muted)}
.spinner{width:20px;height:20px;border-radius:50%;border:3px solid var(--line);
  border-top-color:var(--accent);animation:spin .8s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* the answer */
.answer{background:var(--surface);border:1px solid var(--line);border-radius:22px;
  padding:22px;margin-top:14px}
.kcal{font-size:clamp(42px,14vw,54px);font-weight:700;line-height:1;letter-spacing:-.03em}
.kcal span{font-size:21px;font-weight:500;color:var(--muted);margin-left:8px}
.kj{color:var(--muted);font-size:15px;margin-top:6px}
/* A clear second to the macros: a headline figure, not a caption, but never
   competing with the tiles above it. Calories carry the weight, kJ sits beside
   them small, the same relationship the unit suffix has on a macro tile. */
.energy{display:flex;align-items:baseline;justify-content:center;gap:10px;margin-top:18px}
.energy b{font-size:clamp(22px,7.5vw,31px);font-weight:700;letter-spacing:-.02em;color:var(--ink)}
.energy b i{font-style:normal;font-size:.45em;font-weight:600;color:var(--muted);margin-left:3px}
.energy span{font-size:13px;color:var(--muted)}
.macros{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:4px}
/* min-width:0 stops a long value (100.5 g) from forcing the grid wider than
   the screen. Grid and flex children default to min-width:auto. */
/* Tiles are display, not controls, so they take the quiet border: the lifted fill and the shadow
   do the separating, and a 3:1 edge on every tile would read as a grid of boxes. */
.macro{background:var(--raised);border:1px solid var(--line-soft);border-radius:15px;
  padding:16px 6px;text-align:center;min-width:0;box-shadow:var(--lift-1)}
/* The hero of the page. He should read protein, carbs and fat without looking
   for them, so they are the largest thing on screen and energy steps back. */
.macro b{display:block;font-size:clamp(26px,9vw,40px);font-weight:700;
  letter-spacing:-.03em;white-space:nowrap;line-height:1.05}
.macro b i{font-style:normal;font-size:.44em;font-weight:600;color:var(--muted);margin-left:2px}
.macro small{margin-top:5px}
.macro small{color:var(--muted);font-size:13px;text-transform:uppercase;letter-spacing:.06em}


.ask{margin-top:16px;background:var(--raised);border:1px solid var(--line);
  border-radius:18px;padding:18px}
.ask p{margin:0 0 14px;font-weight:600}
.opts{display:flex;flex-wrap:wrap;gap:10px}
.opts button{background:var(--bg);color:var(--ink);border:1px solid var(--line);
  flex:1 1 44%;min-width:0;padding:14px 10px;font-size:15px}

.items{margin-top:18px;display:flex;flex-direction:column;gap:10px}
.item{background:var(--raised);border:1px solid var(--line-soft);border-radius:16px;
  padding:14px 16px;box-shadow:var(--lift-1)}
.item .row{display:flex;justify-content:space-between;gap:12px;align-items:baseline}
.item b{font-weight:600;font-size:17px;min-width:0;overflow-wrap:anywhere}
.item .kc{color:var(--muted);font-size:15px;white-space:nowrap}
.itemMacros{display:flex;gap:14px;margin-top:8px;flex-wrap:wrap}
.itemMacros span{color:var(--muted);font-size:13px;white-space:nowrap}
.itemMacros b{color:var(--ink);font-weight:650;font-size:15px}
.item .src{color:var(--muted);font-size:13px;margin-top:8px}
.taps{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.taps button{background:var(--bg);color:var(--muted);border:1px solid var(--line);
  padding:9px 13px;min-height:44px;font-size:13px;border-radius:11px;
  max-width:100%;overflow-wrap:anywhere}
.taps button.on{border-color:var(--accent);color:var(--accent)}

.actions{display:flex;gap:10px;margin-top:18px}
.actions button{flex:1}

/* today */
/* Meal rows are buttons: they open that meal's summary again. */
.dayline{display:flex;width:100%;justify-content:space-between;align-items:center;gap:12px;
  padding:14px 12px;margin:0 -12px;border:0;border-bottom:1px solid var(--line-soft);
  background:transparent;color:var(--ink);font:inherit;text-align:left;
  min-height:52px;border-radius:12px}
.dayline:active{background:var(--raised)}
.dayText{min-width:0;overflow-wrap:anywhere}
.chev{color:var(--muted);margin-left:8px;font-size:17px}
.dayline .kc{white-space:nowrap;color:var(--muted);font-size:15px;flex:0 0 auto}
/* The whole bars block is now the switch, instead of a pill above it (Lance, 23 Sep 2026:
   "get rid of the % of target switch button thing ... just make it if you press on the
   collection of the bars"). It sits outside .targetsTap rather than inside it: that wrapper
   is already role="button", and a button inside a button is invalid and reads as one
   confused control to a screen reader.

   The hint stays, much quieter than the pill was. Removing the pill removes the only thing
   on the screen that said switching exists at all, and a gesture nobody knows about is the
   same as a feature that is not there. It is a caption, not a control: 11.5px, muted, no
   border, and the tap target is the whole block behind it. */
.bars{margin-top:16px;display:flex;flex-direction:column;gap:12px;
  padding:12px;margin-inline:-12px;border-radius:14px;cursor:pointer;
  -webkit-tap-highlight-color:transparent}
.bars:active{background:var(--well)}
.bars:focus-visible{outline:2px solid rgb(var(--accent-rgb) / .8);outline-offset:2px}
.barsHint{margin:2px 0 0;text-align:right;font-size:11.5px;color:var(--dim);letter-spacing:.02em}
.barsHint span{margin-left:3px}
.bar small{display:flex;justify-content:space-between;color:var(--muted);font-size:13px}
/* The track holds the whole amount, not just the target, so the two fills sit side by side
   and the mark shows where the target was. overflow:hidden keeps the rounded ends. */
.track{position:relative;display:flex;height:9px;background:var(--well);border-radius:5px;
  margin-top:6px;overflow:hidden}
.fill{height:100%;background:var(--accent)}
.fill.over{background:var(--amber)}
.trackMark{position:absolute;top:0;bottom:0;width:0;border-left:2px dashed var(--ink);opacity:.8}
/* The calorie bar is the primary one on the day, so it is taller than the three macro
   bars under it and sits tight to the figure it belongs to. Weight is doing the work
   here, not colour: it stays the same green as the macros. */
.calBar{margin-top:12px}
.calBar .track{height:14px;border-radius:7px}
.attr{color:var(--muted);font-size:11.5px;line-height:1.5;margin-top:26px}

/* Update pill. Fixed, so it never pushes the layout around, and it is a hint
   rather than an interruption: nothing reloads until it is tapped. */
.updatePill{position:fixed;left:50%;transform:translateX(-50%);
  bottom:calc(env(safe-area-inset-bottom) + 14px);z-index:20;
  background:var(--accent);color:var(--on-accent);font-size:13px;font-weight:600;
  border-radius:999px;padding:11px 18px;min-height:0;
  box-shadow:var(--lift-3);animation:pillIn .3s ease}
@keyframes pillIn{from{opacity:0;transform:translate(-50%,10px)}to{opacity:1;transform:translate(-50%,0)}}
.version{color:var(--muted);opacity:.55;font-size:11.5px;text-align:center;margin-top:18px}

/* day and month navigation */
/* THE DAY IS THE SUBJECT OF THIS ROW. EVERYTHING ELSE IS A CONTROL.
   
   Until 23 Sep all five carried the same raised fill and border, and the calendar chip was ACCENT
   green, so the loudest thing in the row was a utility while "Today", the thing the row is actually
   about, sat in plain 17px text. The eye landed on the wrong element every time.
   
   Now: the day is the largest thing here, the arrows are chevrons rather than boxes, and the two
   utilities keep a quiet outline. All four stay 44px, which is the tap target floor: quieter must
   never mean harder to hit. */
.dayNav{display:flex;align-items:center;gap:4px;padding:6px 0 14px}
.dayLabel{flex:1;text-align:center;font-size:21px;font-weight:650;min-width:0;letter-spacing:-.01em}
.navBtn{background:transparent;border:0;color:var(--muted);
  width:44px;height:44px;min-height:44px;padding:0;border-radius:12px;font-size:21px;line-height:1}
.navBtn:hover:not(:disabled),.navBtn:focus-visible{color:var(--ink)}
.calBtn{background:transparent;border:1px solid var(--line-soft);color:var(--muted);
  width:44px;height:44px;min-height:44px;padding:0;border-radius:12px;font-size:15px;line-height:1}
.navBtn:disabled{opacity:.3}
.todayBtn{width:100%;background:transparent;border:1px solid var(--line);
  color:var(--muted);border-radius:12px;padding:12px;min-height:46px;
  font-size:15px;margin-bottom:12px}
.empty{color:var(--muted);text-align:center;padding:26px 0;margin:0}

/* month grid. At 390px: 358 body, less 2x12 card padding = 334, less six 4px
   gaps = 310, so each cell is 44px. That is the tap target floor exactly, which
   is why the cells carry a number and a total and nothing else. */
.grid{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;
  background:var(--surface);border:1px solid var(--line);border-radius:18px;padding:12px}
.wd{text-align:center;color:var(--muted);font-size:11.5px;padding-bottom:4px}
.cell{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;
  min-height:50px;min-width:0;padding:2px;border:1px solid transparent;border-radius:10px;
  background:transparent;color:var(--ink);font:inherit}
.cell.blank{visibility:hidden}
.cell.isToday{border-color:var(--accent)}
.cell.future{opacity:.35}
.cellDay{font-size:13px;font-weight:600;line-height:1}
.cellCal{font-size:11.5px;color:var(--muted);line-height:1}
/* an unlogged day gets a quiet dot, never a zero */
.cellCal.dot{width:3px;height:3px;border-radius:50%;background:var(--line-soft);margin-top:3px}

/* Press feedback. iOS Safari has no Vibration API, so this is visual only.
   Transform and opacity keep it off the layout and paint path. */
button:active,.shoot:active,.pick:active,.dayline:active,.cell:active{
  transform:scale(.965);opacity:.88}
button,.shoot,.pick,.dayline,.cell,details summary{
  transition:transform .09s ease,opacity .09s ease}
.updatePill:active{transform:translateX(-50%) scale(.95);opacity:.9}
@media (prefers-reduced-motion:reduce){
  button:active,.shoot:active,.pick:active,.dayline:active,.cell:active{transform:none}
  .updatePill:active{transform:translateX(-50%)}
}

/* Collapsed sections (23 Sep 2026, Lance: "i dont think every food needs the 'not this? change it'
   so big ... it's too in your face and the screen is too crowded").

   The bordered full width box that used to live here is gone, and both things that used it moved
   onto .quiet below. A meal with four foods was showing four of them, each 44px tall with a border,
   for a path most people never take.

   The note that was here said "the label has to say what it is for, not be a bare chevron", and that
   still holds, which is why this is an icon AND the word "Change" rather than the bare chevron that
   was asked for. A glyph alone on a correction control is a guess about what it corrects. */
/* Both, not either. ::-webkit-details-marker alone leaves the triangle in current Chrome and
   Safari, which want list-style; and this is set on every summary rather than per class, because
   the per-class version is how a fat picker ended up wearing a raw browser triangle the moment a
   rule it silently depended on was deleted. */
details summary{list-style:none}
details summary::-webkit-details-marker{display:none}
details.swap .taps{margin-top:10px}

/* current portion, so he can see what the steps are doing */

/* titled meal rows */
.dayMain{display:flex;flex-direction:column;gap:5px;min-width:0;align-items:flex-start}
/* The food is the heading, not "Meal 2".
   It was the other way round until 23 Sep: "Meal 2" was bold and full brightness while "chicken and
   rice" sat underneath in grey. Nobody scans a day looking for which number a meal was; they scan
   for what they ate, and the app was making them read the small grey line to find it. */
.dayTitle{font-size:17px;font-weight:650;color:var(--ink)}
.dayTitle em{font-style:normal;font-weight:500;color:var(--muted);margin-left:8px;font-size:13px}
.daySub{color:var(--muted);font-size:13px;overflow-wrap:anywhere}
.dayMacros{display:flex;gap:12px;flex-wrap:wrap}
.dayMacros span{color:var(--muted);font-size:13px;white-space:nowrap}
.dayMacros b{color:var(--ink);font-weight:650;font-size:13px}
.dayline{align-items:flex-start;padding:14px 12px}

.danger{background:var(--red);color:var(--on-red);font-weight:650;flex:1}

/* the info panel: one tap from the day header, collapsed by default */
.calBtn.info{font-size:17px;font-weight:700;font-style:italic;font-family:Georgia,serif}
.infoPanel{background:var(--surface);border:1px solid var(--line);border-radius:16px;
  padding:16px;margin-bottom:12px}
.infoPanel p{margin:0 0 9px;color:var(--muted);font-size:13px;line-height:1.5}
.infoPanel p:last-child{margin-bottom:0}
.infoHead{color:var(--ink)!important;font-weight:650;font-size:15px!important}
.infoPanel .attr{font-size:11.5px;line-height:1.5}
.infoPanel .version{opacity:.55;font-size:11.5px}

/* ---------- targets screen ---------- */
.targetsTap{display:block;cursor:pointer;border-radius:14px}
.targetsTap:active{transform:scale(.985);opacity:.9}
.tnote{display:block;margin-top:12px;color:var(--muted);font-size:13px;text-align:right}
.tnote.unset{color:var(--amber);text-align:left}
/* THREE WEIGHTS, AND THEY MUST LOOK DIFFERENT.
   
   .primary  filled accent   the one thing this screen is for. Never two on a screen.
   .ghost    outlined        a real alternative, chosen about as often as not
   .linkBtn  no border       occasional, or a way out. A link, which is what it is called.
   
   linkBtn used to carry "border:1px solid var(--line)", so a link rendered as a box identical to a
   ghost button. That is why every screen read as a stack of equal options: the app had one control
   style wearing three class names, and an eye had to READ each box to rank it. Counted 23 Sep: four
   near-identical boxes on the home screen, four more in the account export block.
   
   Still 46px tall. Quieter is not the same as harder to hit. */
.linkBtn{background:transparent;border:0;color:var(--accent);
  width:100%;border-radius:12px;min-height:46px;font-size:15px;font-weight:600;margin-top:4px}
.linkBtn:hover,.linkBtn:focus-visible{text-decoration:underline}
.navSpacer{width:44px;flex:0 0 44px}
.tcard{margin-top:0;margin-bottom:12px}
.tHead{margin:0 0 12px;font-weight:650;font-size:15px}
.tSub{margin:12px 0 0;color:var(--muted);font-size:13px;line-height:1.5}
.seg{display:flex;gap:8px}
.seg button{flex:1;background:var(--raised);border:1px solid var(--line);color:var(--ink);
  border-radius:12px;min-height:46px;padding:8px 6px;font-size:15px}
.seg button.on,.acts button.on{border-color:var(--accent);color:var(--accent)}
.seg small{font-size:13px;color:var(--muted);margin-left:3px}
.seg button.on small{color:var(--accent)}
.tgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:12px}
.tfield{display:flex;flex-direction:column;gap:6px;min-width:0}
.tfield>span{color:var(--muted);font-size:13px}
.tin{display:flex;align-items:center;gap:6px;background:var(--raised);border:1px solid var(--line);
  border-radius:12px;padding:0 10px;min-height:46px;min-width:0}
.tin input{flex:1;min-width:0;width:100%;background:transparent;border:0;color:var(--ink);
  font:inherit;font-size:17px;font-weight:600;padding:10px 0;outline:none}
.tin em{font-style:normal;color:var(--muted);font-size:13px}
.acts{display:flex;flex-direction:column;gap:8px;margin-top:10px}
.acts button{background:var(--raised);border:1px solid var(--line);color:var(--ink);text-align:left;
  border-radius:12px;min-height:46px;padding:11px 14px;font-size:15px;line-height:1.35}
.tBmr{margin-top:14px}
.tnums{display:flex;gap:18px;flex-wrap:wrap}
.tnums span{color:var(--muted);font-size:13px}
.tnums b{color:var(--ink);font-size:21px;font-weight:700;margin-right:2px}
.trow{margin-top:14px}
.tline{display:flex;align-items:center;justify-content:space-between;gap:12px}
.tline>span:first-child{font-size:15px;font-weight:600}
.tline .tin{width:130px;flex:0 0 130px}
.tslider{width:100%;height:44px;margin:4px 0 0;accent-color:var(--accent)}
.terr{margin:14px 0 0;color:var(--red);font-size:13px}
.twarn{margin:14px 0 0;color:var(--amber);font-size:13px;line-height:1.5}

/* Back out of a meal: top of the page, one-handed, press feedback from button:active */
.mealNav{padding:6px 0 10px}
.backBtn{background:var(--raised);border:1px solid var(--line);color:var(--ink);min-height:46px;
  padding:0 18px 0 12px;border-radius:12px;font-size:15px;font-weight:600;max-width:100%;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* calendar button: today's month and day, drawn, not an emoji. Theme tokens only. */
.calIcon{display:inline-flex;flex-direction:column;align-items:stretch;justify-content:flex-start;
  overflow:hidden;padding:0;line-height:1}
.calIcon .calMon{background:var(--line);color:var(--ink);font-size:11.5px;font-weight:800;
  letter-spacing:.06em;padding:3px 0 2px;text-align:center}
.calIcon .calDay{flex:1;display:flex;align-items:center;justify-content:center;
  color:var(--ink);font-size:17px;font-weight:700;font-variant-numeric:tabular-nums}

/* ---------- photo first, then the scan ---------- */
/* Nothing about the scan shows until the photo is on screen: the bar is invisible
   and not animating, the grid is off, the beam is off. */
.progress{opacity:0;transition:opacity .2s ease}
.progress.running{opacity:1}
.progress.running .progressFill{animation:fill 13s cubic-bezier(.3,.42,.45,1) forwards}
.progress.running.full .progressFill{animation:none;transform:scaleX(1);transition:transform .3s ease}
.scan::after{opacity:0;transition:opacity .2s ease}
.scanWrap.scanning .scan::after{opacity:1}
#shot{transition:opacity .12s ease}

/* Loading the photo: a quiet pulse and short text inside the frame. Opacity only,
   so it reads as working, and it looks nothing like the scan that follows. */
.scanWrap.loading,.scanWrap.nophoto{background:var(--surface)}
.scanWrap.loading #shot{opacity:0}
.photoNote{position:absolute;inset:0;display:none;align-items:center;justify-content:center;
  padding:0 20px;text-align:center;color:var(--muted);font-size:15px;z-index:1;pointer-events:none}
.scanWrap.loading .photoNote,.scanWrap.nophoto .photoNote{display:flex}
.scanWrap.loading::before{content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,rgba(255,255,255,0) 20%,rgba(255,255,255,.07) 50%,rgba(255,255,255,0) 80%);
  animation:photoPulse 1.3s ease-in-out infinite}
@keyframes photoPulse{0%,100%{opacity:.25}50%{opacity:1}}
@media (prefers-reduced-motion:reduce){
  .scanWrap.loading::before{animation:none;opacity:.6}
  .progress.running .progressFill{animation:none;transform:scaleX(.5)}
}

/* targets: a labelled way in */
.targetsCta{margin-top:16px}
.targetsCta .setTargets{min-height:50px}
.targetsCta .tnote{margin-top:8px;text-align:center;font-size:13px}
.editTargets{width:100%;margin-top:14px;min-height:44px;font-size:15px}

/* stage messages crossfade with the scan, opacity only */
#workingText{transition:opacity .17s ease}
#workingText.fading{opacity:0}

/* FAQ in the info panel: each question collapsed */
.faq{margin:14px 0 6px;border-top:1px solid var(--line-soft)}
.faq details{border-bottom:1px solid var(--line-soft)}
.faq summary{list-style:none;cursor:pointer;display:flex;justify-content:space-between;align-items:center;
  gap:10px;min-height:44px;padding:10px 0;color:var(--ink);font-size:15px;font-weight:600}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{content:"\203A";transform:rotate(90deg);color:var(--muted);font-size:17px;flex:0 0 auto}
.faq details[open] summary::after{transform:rotate(-90deg)}
.faq details p{margin:0 0 12px;color:var(--muted);font-size:13px;line-height:1.55}

/* ---------- calendar: goal-aware stats and day colours ---------- */
.cell.tone-good{background:rgb(var(--accent-rgb) / .24)}
.cell.tone-bad{background:rgb(var(--red-rgb) / .26)}
.cell.tone-warn{background:rgb(var(--amber-rgb) / .26)}
.cellCal .mark,.legend .mark{font-weight:800;font-style:normal;font-size:11.5px;margin-right:1px}
.tone-good .mark,.lg .tone-good + .mark{color:var(--accent)}
.tone-bad .mark,.lg .tone-bad + .mark{color:var(--red)}
.tone-warn .mark,.lg .tone-warn + .mark{color:var(--amber)}
.mstats{background:var(--surface);border:1px solid var(--line);border-radius:18px;padding:14px 14px 12px;margin-bottom:10px}
.mgoal{margin:0 0 10px;font-weight:650;font-size:15px}
.mgoal span{font-weight:500;color:var(--muted);font-size:13px}
.figs{display:grid;grid-template-columns:repeat(var(--n,4),minmax(0,1fr));gap:6px}
.fig{background:var(--raised);border:1px solid var(--line-soft);border-radius:12px;
  padding:9px 6px;text-align:center;min-width:0}
.fig b{display:block;font-size:17px;font-weight:700;white-space:nowrap}
.fig small{display:block;margin-top:3px;font-size:11.5px;color:var(--muted);line-height:1.2;overflow-wrap:anywhere}
.mnote{margin:10px 0 0;color:var(--muted);font-size:13px;line-height:1.45}
.legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin:10px 2px 12px;color:var(--muted);font-size:13px}
.lg{display:inline-flex;align-items:center;gap:4px}
.sw{display:inline-block;width:11px;height:11px;border-radius:3px}
.sw.tone-good{background:rgb(var(--accent-rgb) / .55)}
.sw.tone-bad{background:rgb(var(--red-rgb) / .6)}
.sw.tone-warn{background:rgb(var(--amber-rgb) / .6)}

/* ---------- save or share the meal as an image ---------- */
.cardActs{display:flex;gap:8px;margin:12px 0 2px}
.cardBtn{flex:1;min-height:44px;padding:10px 12px;background:transparent;color:var(--muted);
  border:1px solid var(--line);border-radius:12px;font-size:15px;font-weight:550}
.cardBtn:disabled{opacity:.45}
.cardView{position:fixed;inset:0;z-index:50;background:var(--bg);display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:12px;padding:20px 16px}
.cardView img{max-width:100%;max-height:72vh;border-radius:12px;-webkit-touch-callout:default;
  -webkit-user-select:auto;user-select:auto}
.cardView p{margin:0;color:var(--ink);font-size:15px;text-align:center}
.cardView button{min-width:140px}

/* ---------- all vitamins and minerals, and the info sheet ---------- */
.ngroup{margin-top:10px}
.nhead{margin:12px 0 2px;color:var(--muted);font-size:13px;text-transform:uppercase;letter-spacing:.06em}
.nrow{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;min-height:44px;
  padding:8px 2px;background:transparent;border:0;border-bottom:1px solid var(--line-soft);border-radius:0;
  color:var(--ink);text-align:left;font-size:15px;font-weight:500}
.nname{display:inline-flex;align-items:center;gap:6px;min-width:0;white-space:nowrap}
.nright{display:flex;flex-direction:column;align-items:flex-end;gap:5px;flex:none}
.nbar{position:relative;display:block;width:84px;height:3px;border-radius:2px;
  background:rgb(var(--muted-rgb) / .16);overflow:hidden}
.nbar i{position:absolute;left:0;top:0;bottom:0;background:rgb(var(--muted-rgb) / .72);border-radius:2px}
.nbar b{position:absolute;top:0;bottom:0;width:12px;
  background:repeating-linear-gradient(90deg,rgb(var(--muted-rgb) / .6) 0 2px,transparent 2px 4px)}
.nval{color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:13px;text-align:right}
.nutriNote{margin:10px 0 2px;color:var(--muted);font-size:13px;line-height:1.45}
/* On the meal summary the macro labels and the calorie line open their sheets.
   They keep their exact look; an invisible extension makes each a 44px target. */
.labelTap{position:relative;display:inline;min-height:0;padding:0;margin:0;background:transparent;border:0;
  border-radius:0;font:inherit;color:inherit;letter-spacing:inherit;text-transform:inherit}
.labelTap::after{content:"";position:absolute;inset:-15px -10px}
.energyTap{position:relative;display:flex;align-items:baseline;justify-content:center;gap:10px;min-height:0;
  padding:0;margin:0;background:transparent;border:0;border-radius:0;font:inherit;color:inherit}
.energyTap::after{content:"";position:absolute;inset:-8px -6px}
.nsheetWrap{position:fixed;inset:0;z-index:60}
.nsheetBack{position:absolute;inset:0;background:rgba(0,0,0,.5)}
.nsheet{position:absolute;left:0;right:0;bottom:0;background:var(--surface);border-top:1px solid var(--line-soft);
  border-radius:18px 18px 0 0;padding:14px 16px calc(22px + env(safe-area-inset-bottom));max-height:70vh;overflow:auto}
.nsheetHead{display:flex;align-items:center;justify-content:space-between;gap:10px}
.nsheet h2{margin:0;font-size:21px;line-height:1.3}
.nsheet h2 small{font-size:13px;font-weight:500;color:var(--muted)}
.nsheetClose{flex:none;width:44px;height:44px;min-height:44px;padding:0;background:transparent;
  border:1px solid var(--line);border-radius:12px;color:var(--ink);font-size:15px}
.nsheet p{margin:10px 0 0;line-height:1.45;font-size:15px}
.nsheet .nlabel{color:var(--muted)}
.nsheet .nref{color:var(--muted);font-size:13px}

/* ---------- polish sweep, 13 Sep 2026 ----------
   Refinement only: no new elements, wording or behaviour. Each rule is tagged so
   any one can be deleted on its own to undo it. The camera, the macro tiles, the
   calorie line and kJ are deliberately untouched. */

/* [G1] One focus ring for keyboard focus, in the app's accent, instead of the
   browser's square blue box. Touch taps do not show it. */
button:focus-visible,summary:focus-visible,.shoot:focus-within,.pick:focus-within{
  outline:2px solid rgb(var(--accent-rgb) / .8);outline-offset:2px}
.nrow:focus-visible,.labelTap:focus-visible,.energyTap:focus-visible{outline-offset:0;border-radius:8px}

/* [G2] One secondary button: the outline buttons were four near-identical styles
   (radius 11 or 12, 44 or 46px, 14, 15 or 16px text). Now one.
   
   .linkBtn was in this list until 23 Sep and should not have been. Unifying four BUTTONS was right;
   sweeping the link in with them meant the app had one control appearance for three different
   weights, which is the whole reason nothing guided the eye. This rule also sits later in the file
   than .linkBtn's own, so it silently won on colour. Removing it here is the fix; the link keeps
   the shared height, because quieter must not mean harder to hit. */
.ghost,.todayBtn,.cardBtn,.editTargets{border-radius:12px;min-height:46px;
  font-size:15px;font-weight:500;color:var(--muted)}
.linkBtn{border-radius:12px;min-height:46px}

/* [G4] One disabled look (was .3 on day arrows, .45 on the image button). */
.navBtn:disabled,.cardBtn:disabled{opacity:.4}

/* [G5] FAQ questions get the same press feedback as every other disclosure. */
.faq summary:active{transform:scale(.985);opacity:.88}
.faq summary{transition:transform .09s ease,opacity .09s ease}
@media (prefers-reduced-motion:reduce){.faq summary:active{transform:none}}

/* [G6] One heading weight, 650, where three were in use (600, 650, 700). */
.dayLabel,.nsheet h2,.ask p{font-weight:650}

/* [D1] "of 2088 cal" was inheriting the headline's tight letter spacing and read
   as "of2088cal". */
.kcal span{letter-spacing:0}

/* [D2] Meal rows on the day view reach the card's content edge on both sides (they
   stopped 24px short on the right) and their divider runs straight to the ends. */
.dayline{width:calc(100% + 24px);border-radius:0}

/* [M1] "Delete for good?" fits on one line beside Keep instead of wrapping, down
   to a 360px screen. */
.actions button{padding-left:12px;padding-right:12px;font-size:15px}
.actions .danger{padding-left:8px;padding-right:8px;font-size:15px;white-space:nowrap}

/* ---------- meal view order, 13 Sep 2026 ---------- */
/* The photo no longer has numbered pills under it, so it sits straight on the tiles. */
.summaryShot{margin-bottom:14px}

/* What stands out: one wrapping row of short chips. Colour follows the existing
   traffic-light level; each opens that nutrient's sheet. 36px to see, 44px to tap. */
.chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
.chip{position:relative;min-height:36px;padding:7px 12px;border-radius:999px;font-size:13px;font-weight:550;
  border:1px solid transparent;line-height:1.2}
.chip::after{content:"";position:absolute;inset:-4px 0}
.chip.red{background:rgb(var(--red-rgb) / .13);color:var(--red);border-color:rgb(var(--red-rgb) / .25)}
.chip.amber{background:rgb(var(--amber-rgb) / .13);color:var(--amber);border-color:rgb(var(--amber-rgb) / .25)}
.chip.good{background:rgb(var(--accent-rgb) / .12);color:var(--accent);border-color:rgb(var(--accent-rgb) / .25)}

/* Portion: one line, smaller, the grams, bigger, and "reset" only when changed. */
.stepper{display:flex;align-items:center;gap:8px;margin-top:12px;flex-wrap:nowrap}
.stepBtn{width:44px;height:44px;min-height:44px;padding:0;background:var(--bg);border:1px solid var(--line);
  border-radius:12px;font-size:17px;line-height:1;color:var(--ink)}
.stepG{min-width:64px;text-align:center;font-size:15px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--ink)}
.resetBtn{min-height:44px;padding:0 6px;background:transparent;border:0;color:var(--muted);font-size:13px;
  text-decoration:underline;text-underline-offset:3px}

/* The one collapsed-section style, used by the per-food vitamins, the meal and day
   nutrient lists, and the correction path. A quiet text control that expands inline. */
details.quiet{margin-top:6px}
details.quiet summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:6px;
  min-height:44px;padding:0 2px;color:var(--muted);font-size:13px}
details.quiet summary::-webkit-details-marker{display:none}
details.quiet summary::after{content:"\203A";transform:rotate(90deg);font-size:15px}
details[open].quiet summary::after{transform:rotate(-90deg)}
details.quiet summary:active{transform:scale(.97);opacity:.88}
details.quiet summary{transition:transform .09s ease,opacity .09s ease}
@media (prefers-reduced-motion:reduce){details.quiet summary:active{transform:none}}

/* Save / Share image: its own full-width row above Delete meal and Back. */
.cardRow .cardActs{margin:18px 0 0}

/* ---------- today view order, 13 Sep 2026 ---------- */
/* Calories left or over sit inside the headline's small suffix, neutral colour. The
   suffix never breaks apart: when it cannot fit beside the number it moves under it
   as one line, instead of leaving "left" on a line of its own. */
.kcal em{font-style:normal}
.kcal{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:8px;row-gap:4px}
.kcal span{margin-left:0;white-space:nowrap;line-height:1.2}
/* Meal rows now follow the bars directly. */
.targetsTap + .items{margin-top:16px}
.items + details.nutriAll{margin-top:16px}
/* A published limit reads on its own small line under the amount. */
.nlim{color:var(--muted);font-size:13px;white-space:nowrap;font-variant-numeric:tabular-nums}
/* Limit bars: grey toward the limit, amber once over. The only bars that change colour. */
.nbar.limit.over i{background:var(--amber)}
.nbar.limit.over b{background:repeating-linear-gradient(90deg,rgb(var(--amber-rgb) / .7) 0 2px,transparent 2px 4px)}

/* ---------- foods with no exact match, 13 Sep 2026 ---------- */
/* "At least" on a total that leaves out a food with no nutrition data yet. */
.atl{font-style:normal;color:var(--muted);font-weight:600}
.macro b .atl{margin:0 2px 0 0}
.energy b .atl{margin:0 3px 0 0}
.kcal .atl{font-size:.45em;align-self:center;margin-right:-4px}
.atlNote{margin:8px 0 0;text-align:center;color:var(--muted);font-size:13px;line-height:1.4}
.nodata{margin-top:8px;color:var(--amber);font-size:13px}

/* A dish estimated from a typical recipe: the breakdown behind a quiet control. */
details.recipe{margin-top:2px}
details.recipe .parts{margin:0 0 6px;color:var(--muted);font-size:13px;line-height:1.55}

/* Stage labels stay on one line: the wording is chosen to fit, and this is only the
   last resort for a very long food name. */
#workingText{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}

/* Account, consent and privacy (multi-user phase 4) */
/* The wording on these screens comes from privacy_text.json. */
.acct h2 { font-size:21px;margin:0 0 6px }
.acct h3 { font-size:15px;margin:24px 0 8px;color:var(--ink) }
.acct p { color:var(--muted);font-size:15px;line-height:1.5 }
.acct p b { color:var(--ink) }
.acct a.linkBtn,.infoPanel a.linkBtn { display:flex;align-items:center;justify-content:center;text-decoration:none;box-sizing:border-box }
.infoPanel a.linkBtn { margin-top:12px }
.acctRow { display:flex;align-items:center;gap:10px;margin-top:14px;flex-wrap:wrap }
.acctValue { flex:1;color:var(--ink);font-weight:600;min-width:0;overflow-wrap:anywhere }
.acctRow .linkBtn { width:auto;margin:0;padding:0 14px }
/* "Australia/Brisbane" was breaking mid-word into "Brisban / e". A slash is a break opportunity and
   the row was too tight to fit the zone, so it broke AFTER the slash and then ran out of room again.
   The value is a span, not a b: my first attempt targeted the wrong element and the bug survived a
   screenshot, which is a good argument for looking twice. */
/* Let the zone use the whole width and wrap at the slash if it must, rather than being squeezed
   into a third of a row. Truncating to "Australia/Brisb..." was no better than breaking mid-word:
   both hide the answer the row exists to give. */
.acctRow{flex-wrap:wrap;gap:4px 10px}
.acctRow .acctValue{flex:1 1 100%;order:3;font-weight:650}
.acctRow .linkBtn{order:2}

/* The export block: one thing most people want, two alternatives, and the policy on its own. */
.acctExport{display:flex;align-items:center;justify-content:center;text-decoration:none;
  width:100%;box-sizing:border-box;margin-top:8px}
.acctAlts{display:flex;gap:18px;justify-content:center;margin:10px 0 0}
.acctAlts a{color:var(--accent);font-size:13px;text-decoration:none}
.acctAlts a:hover,.acctAlts a:focus-visible{text-decoration:underline}
.acctPolicy{margin:18px 0 0;text-align:center}
.acctPolicy a{color:var(--muted);font-size:13px}
.zoneEdit { margin-top:10px;display:flex;gap:8px;flex-wrap:wrap }
.zoneEdit select { flex:1;min-width:0;min-height:46px;background:var(--raised);color:var(--ink);border:1px solid var(--line);
  border-radius:12px;padding:0 10px;font:inherit }
.zoneEdit .primary { width:auto;padding:0 18px }
.tick { display:flex;gap:12px;align-items:flex-start;margin:14px 0;color:var(--ink);font-size:15px;line-height:1.45 }
.tick input { width:22px;height:22px;flex:0 0 22px;margin:1px 0 0;accent-color:var(--accent) }
.tick em { color:var(--muted);font-style:normal;font-size:13px }
.tick a { color:var(--accent) }
.dangerOutline { width:100%;color:var(--red)!important;border-color:rgb(var(--red-rgb) / .5)!important }
.deleteConfirm .actions { display:flex;gap:8px;margin-top:12px }
.deleteConfirm .tfield { margin-top:12px }
.draftTag { display:inline-block;background:var(--amber);color:var(--on-amber)!important;font-weight:700;font-size:13px!important;
  border-radius:6px;padding:3px 8px;margin:0 0 12px }
.consentText ul { padding-left:20px;margin:6px 0 }
.consentText li,.consentText p { color:var(--muted);font-size:15px;line-height:1.5;margin:5px 0 }
.consentText b { color:var(--ink) }
.homeHint { background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:12px 14px;margin:16px 0 0;
  display:flex;gap:10px;align-items:center }
.homeHint p { margin:0;flex:1;color:var(--muted);font-size:15px;line-height:1.45 }
.homeHint .ghost { flex:0 0 auto }
/* A display rule would otherwise beat the hidden attribute. */
/* hidden means hidden, everywhere.
   The browser's own [hidden] rule is display:none, but ANY author rule that sets display beats it,
   because a stylesheet outranks the user agent one. So every element here with its own display, a
   flex row of buttons for instance, quietly ignored the hidden attribute and showed anyway. The
   codebase had been patching that one selector at a time: .homeHint, .zoneEdit, .deleteConfirm,
   .tTip. It surfaced again on 23 Sep when "Start over" and "Read this meal" appeared on the home
   screen with no photos taken.
   One rule instead of a growing list. !important is the right tool exactly here: nothing should
   ever be visible while carrying hidden. */
[hidden]{display:none !important}

/* Features wave 1: the macro view pill (A) and the cooking fat controls (B) */
.viewRow{display:flex;justify-content:flex-end;margin:10px 0 -4px}
.viewPill{background:var(--raised);color:var(--muted);border:1px solid var(--line);border-radius:999px;
  padding:5px 12px;font-size:13px;font-weight:600;line-height:1.2;cursor:pointer}
.viewPill:active{transform:scale(.97)}
.barFigure.over{color:var(--amber)}
.fatRemove{display:flex;gap:8px;margin-top:8px;flex-wrap:wrap}
.addFatRow{margin-top:10px}

/* Features wave 1: feedback (C) */
.fbKinds{display:flex;flex-direction:column;gap:6px;margin:10px 0}
.fbText{width:100%;box-sizing:border-box;background:var(--raised);color:var(--ink);border:1px solid var(--line);
  border-radius:12px;padding:12px;font:inherit;font-size:17px;line-height:1.4;resize:vertical}
.fbCount,.fbNote{color:var(--muted);font-size:13px;margin:6px 0}
.reportRow{margin-top:18px;text-align:center}
.whatsNew{color:var(--ink);font-weight:600}

/* Features wave 1: targets as % of calories (D) */
.tGrams{color:var(--muted);font-weight:400;font-size:13px;margin-left:4px}
.tFixed .tslider,.tFixed .tin input{opacity:.55}
.trow{position:relative}
.tTip{position:absolute;left:0;top:0;z-index:5;white-space:nowrap;pointer-events:none;
  background:var(--ink);color:var(--bg);font-size:13px;font-weight:600;line-height:1;
  padding:6px 9px;border-radius:9px;box-shadow:var(--lift-2)}
/* Features wave 2: the exact amount editor (H) */
button.stepG{background:transparent;border:1px dashed var(--line);border-radius:10px;min-height:44px;padding:0 10px;
  font:inherit;font-size:15px;font-weight:600;color:var(--ink);font-variant-numeric:tabular-nums;cursor:pointer}
button.stepG[aria-expanded="true"]{border-style:solid;border-color:var(--accent);color:var(--accent)}
.amountEdit{margin-top:10px}
.amtSlider{width:100%;height:44px;margin:0;accent-color:var(--accent);touch-action:manipulation}
.amtRow{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.amtIn{flex:1 1 110px;min-width:0}
.amtIn input{font-size:16px}
.amtBtn{flex:0 0 auto;width:auto;min-height:44px;padding:0 16px}
/* The scales tick. 44px of tap target, because it sits under a slider people are already
   fighting with, and a 20px checkbox next to a slider is a missed tap. */
.amtWeighed{display:flex;align-items:center;gap:10px;min-height:44px;margin-top:6px;cursor:pointer}
.amtWeighed input{width:22px;height:22px;margin:0;flex:0 0 auto;accent-color:var(--accent)}
.amtWeighed span{font-size:15px;color:var(--muted)}
.amtWeighed input:checked+span{color:var(--ink);font-weight:600}
.weighRow{margin:10px 0 2px;padding:10px 12px;border:1px solid var(--line-soft);border-radius:12px;background:var(--well)}
.weighRow .amtWeighed{margin-top:0}
.weighHint{margin:2px 0 0 32px;font-size:13px;color:var(--dim);line-height:1.35}
.amtErr{margin-top:8px}
/* Features wave 2: typed foods, "Not this? Change it" and "+ Add a food" (E, F) */
.typedBox{display:flex;gap:8px;margin-top:10px;align-items:stretch}
.typedBox input{flex:1 1 auto;min-width:0;font:inherit;font-size:16px;color:var(--ink);background:var(--raised);
  border:1px solid var(--line);border-radius:12px;padding:10px 12px;min-height:46px}
.typedBox input::placeholder{color:var(--muted)}
.typedBox .typedGo{flex:0 0 auto;width:auto;padding:0 16px}
.typedStatus{margin:8px 0 0;color:var(--muted);font-size:13px}
.typedStatus.bad{color:var(--red)}
.addFatRow{display:flex;flex-wrap:wrap;gap:8px}
.addFatRow>.ghost{flex:1 1 140px;white-space:nowrap;padding-inline:10px}
.addFood{flex:1 1 100%}
.stepper.checkAmount button.stepG{border-style:solid;border-color:var(--amber);color:var(--amber)}
.checkHint{margin:6px 0 0;color:var(--amber);font-size:13px}
/* Features wave 3: log a meal by typing it (J) */
.typeMeal{display:block;width:100%;margin-top:12px}
.homeTyped{margin-top:12px}
#typeMealSays{text-align:center}
/* Features wave 4: logging onto the day you are looking at (L) */
.dayLog{margin-top:18px}
.dayLogTitle{margin:0 0 10px;font-size:15px;font-weight:650;color:var(--ink)}
.dayLog .pick{margin-top:8px}
/* Features wave 4: how to photograph a meal (K) */
.photoGuide{background:var(--surface);border:1px solid var(--line);border-radius:18px;padding:14px;margin-bottom:14px}
.shots{display:flex;gap:10px}
.shots figure{flex:1 1 0;margin:0;min-width:0}
.shots img{width:100%;aspect-ratio:1;object-fit:cover;border-radius:12px;display:block}
.shots figcaption{margin-top:6px;font-size:13px;line-height:1.35;color:var(--muted)}
.shots .good{color:var(--accent)}
.shots .bad{color:var(--red)}
.photoGuide .tips,.faq .tips{margin:12px 0 0;padding-left:18px;color:var(--muted);font-size:13px;line-height:1.5}
.photoGuide .ghost{margin-top:12px}
.faq .shots{margin-top:10px}
/* Features wave 5: log this meal again (M) */
.againRow{margin-top:12px}
.againRow .ghost{width:100%}
/* Features wave 5: when the meal was eaten (N) */
.eatenRow{margin:12px 0 0}
.eatenEdit{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:12px}
.eatenEdit input{font-size:16px;color:var(--ink);background:var(--raised);border:1px solid var(--line);
  border-radius:12px;padding:10px 12px;min-height:46px}
.eatenEdit .ghost{width:auto;padding:0 16px}
/* Features wave 5: the week (O) */
.weekBox{margin-top:12px;background:var(--surface);border:1px solid var(--line);border-radius:16px;padding:12px 14px}
.weekTap{display:flex;width:100%;justify-content:space-between;align-items:center;gap:10px;background:transparent;
  border:0;padding:0;color:var(--muted);font:inherit;font-size:13px;text-align:left;min-height:32px}
/* The week chart (UI refresh, stage 2). The SVG is the picture and is aria-hidden; the
   buttons sitting over it are the controls, so tap, focus and screen readers are unchanged.
   The plot is sunken so the bars have something to stand in. */
.weekChart{position:relative;margin-top:12px;padding-bottom:20px}
.weekPlot{display:block;width:100%;height:64px;background:var(--well);border-radius:10px;
  border:1px solid var(--line-soft);overflow:hidden}
.wkIn{fill:var(--accent)}
.wkOver{fill:var(--amber)}
/* non-scaling-stroke keeps this 1px: the viewBox is stretched to the screen width, so a
   plain stroke would be drawn as a fat smear horizontally and a hairline vertically. */
.wkTarget{stroke:var(--ink);stroke-width:1;stroke-dasharray:5 4;opacity:.75;
  vector-effect:non-scaling-stroke}
.weekHits{position:absolute;inset:0;display:grid;grid-template-columns:repeat(7,1fr)}
.weekHits .weekDay{background:transparent;border:0;padding:0;min-height:0;display:flex;
  align-items:flex-end;justify-content:center;cursor:pointer}
.weekDay small{color:var(--muted);font-size:11.5px;line-height:1}
.weekDay.missed small{color:var(--dim)}
.weekDay.future{opacity:.45}
.weekDay:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;border-radius:8px}
.weekNote{margin:10px 0 0;color:var(--muted);font-size:13px;line-height:1.5}
/* Features wave 5: named target presets (P) */
.presetList button em{display:block;font-style:normal;font-size:13px;color:var(--muted);margin-top:2px}
.presetList.deleting button{border-color:rgb(var(--red-rgb) / .55);color:var(--red)}
.presetSave{display:flex;gap:8px;margin-top:10px;align-items:center}
.presetSave .tin{flex:1 1 auto;min-width:0}
.presetSave .tin input{font-size:16px}
.presetSave .ghost{flex:0 0 auto;width:auto;padding:0 16px}
/* Features wave 5: reading a nutrition label (Q) */
.labelPick{margin-top:12px}

/* Several angles of one meal. Deliberately set apart from the single shot at the top: it is an
   experiment, not a better mode, and the two must never be mistaken for each other. Bordered and
   inset rather than another button in the stack, so the eye reads it as its own thing. */
.angles{margin-top:22px;padding:16px 18px;border:1px solid var(--line-soft);
  border-radius:var(--r3);background:var(--well)}
.angles h2{margin:6px 0 4px;font-size:17px;font-weight:650}
/* Amber, the app's caution colour, used here to mean "unproven" rather than "wrong". It is the same
   colour the eating-disorder notice uses, and for the same reason: read this before relying on it. */
.angleTag{display:inline-block;margin:0;padding:3px 9px;border-radius:999px;
  background:rgb(var(--amber-rgb) / .14);color:var(--amber);
  font-size:11.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}
.angleWhy{margin:6px 0 12px;color:var(--muted);font-size:15px;line-height:1.5}
.angles .pick{margin-top:0}
.angles .actions{margin-top:12px}

/* Three overlapping plates, against the single lens on the main button. The distinction is the
   whole point of the mark: one photo of one plate up there, several of the same meal down here. */
.plates{display:inline-block;width:20px;height:14px;margin-right:9px;vertical-align:-2px;position:relative}
.plates::before,.plates::after{content:"";position:absolute;top:1px;width:12px;height:12px;
  border-radius:50%;border:1.5px solid var(--muted)}
.plates::before{left:0;opacity:.45}
.plates::after{left:6px;background:var(--well)}

.angleList{list-style:none;margin:0 0 12px;padding:0}
.angleItem{display:flex;align-items:center;gap:10px;padding:9px 0;
  border-bottom:1px solid var(--line-soft)}
.angleNum{font-weight:650;font-size:15px}
.angleHint{color:var(--muted);font-size:13px;flex:1}
/* 44px so it is a real tap target on a phone, not a decorative cross. */
.angleDrop{min-width:44px;min-height:44px;background:transparent;border:0;color:var(--muted);
  font-size:21px;line-height:1;cursor:pointer}
.angleDrop:hover,.angleDrop:focus-visible{color:var(--red)}
.panelTable{width:100%;border-collapse:collapse;margin-top:12px}
.panelTable td{padding:9px 0;border-bottom:1px solid var(--line-soft);font-size:15px}
.panelTable td:last-child{text-align:right;font-weight:650;font-variant-numeric:tabular-nums}
.panelTable td small{color:var(--muted);font-weight:400}

/* The figures, editable since 23 Sep 2026. Styled to read as a value that CAN be changed rather
   than a form to fill in: no box until it is touched, so the table still scans as a table. */
.panelFig{width:5.5em;background:transparent;border:0;border-bottom:1px dashed var(--line);
  color:var(--ink);font:inherit;font-weight:650;font-variant-numeric:tabular-nums;
  text-align:right;padding:2px 2px 3px;min-height:34px}
.panelFig:focus{outline:none;border-bottom:1px solid var(--accent);background:var(--raised)}

/* The voluntary rows. Closed by default so the amount box and Log it stay on the first screen: with
   everything open, eleven rows sat between the photo and the thing the screen exists to do. */
.moreFigs{margin-top:10px}
.moreFigs summary{list-style:none;cursor:pointer;color:var(--accent);font-size:13px;
  padding:10px 0;min-height:44px;display:flex;align-items:center}
.moreFigs summary::-webkit-details-marker{display:none}
.moreFigs summary::after{content:"";margin-left:8px;width:7px;height:7px;border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;transform:rotate(45deg) translateY(-2px)}
.moreFigs[open] summary::after{transform:rotate(-135deg) translateY(-2px)}
.moreFigs .panelTable{margin-top:0}
.panelTable td:last-child{white-space:nowrap}
.panelAmount{display:flex;gap:10px;align-items:center;margin-top:14px;flex-wrap:wrap}
.panelAmount .tin{flex:0 0 120px}
.panelAmount .tin input{font-size:16px}
.fromLabel{color:var(--muted);font-size:13px;margin-top:8px}

/* The label you just photographed, on the screen where you approve the figures read off it.
   Capped by height rather than width: a nutrition panel is tall and narrow, and letting it run to
   its natural height pushed the amount box and the Log it button off the bottom of a phone. */
.panelShot{display:block;width:100%;max-height:34vh;object-fit:contain;margin:12px 0 4px;
  border-radius:var(--r2);background:var(--well);border:1px solid var(--line-soft)}

/* What the amount currently typed will actually add to the day. The per 100 table above and the
   figures on Today were both right and there was nothing showing the arithmetic between them, which
   is how a correct answer came to be reported as a bug. Accent, because it is the answer. */
.panelTotals{margin:10px 0 0;font-size:15px;color:var(--accent);font-variant-numeric:tabular-nums;
  min-height:20px}

/* The "who Meal Bot is not for" warning. Amber rather than red: it is a caution to read, not an
   error to clear, and red is already the colour for a figure that has gone over target. */
.safetyNotice{margin-top:16px;background:var(--raised);border:1px solid var(--amber);
  border-radius:var(--r3);padding:16px 18px;box-shadow:var(--lift-1)}
.safetyNotice h2{margin:0 0 8px;font-size:17px;font-weight:650;color:var(--amber)}
.safetyNotice p{margin:0 0 12px;font-size:15px;line-height:1.55}
.safetyNotice .ghost{width:100%}
.safetyNotice .linkBtn{display:block;margin:0 0 12px;padding:0}
/* The same warning on the consent screen, where it sits above the ticks. */
.warn{border-left:3px solid var(--amber);padding-left:12px;margin:14px 0}
.helpline{margin-top:12px;color:var(--amber)}

/* The calorie split ring (23 Sep 2026). Three lightnesses of the accent rather than three
   hues: colour in this app already means progress, over and problem, and a third scheme
   would stop any of them meaning anything. The steps are far enough apart to survive a
   greyscale screenshot, and the percentage is written beside each dot, so nothing here is
   carried by colour alone. The three tones are tokens, measured in test_contrast.py. */
.splitBlock{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin:14px 0 2px}
.splitRing{width:var(--ringSize,92px);height:var(--ringSize,92px);flex:none}
.splitRing svg{width:100%;height:100%;display:block}
.ringSeg{transition:stroke-dasharray .35s ease}
.splitKey{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px;
  font-size:13px;color:var(--muted)}
.splitKey li{display:flex;align-items:center;gap:8px;white-space:nowrap}
.splitDot{width:10px;height:10px;border-radius:50%;flex:none}
/* Today's hero is left aligned and a meal's is centred, so the ring follows whichever
   it sits under rather than fixing one alignment and being wrong on one screen. */
#heroSplit .splitBlock{justify-content:center;margin:10px 0 4px}
@media (prefers-reduced-motion:reduce){.ringSeg{transition:none}}

/* The leading glyph on a quiet disclosure. Dim rather than muted so the WORD is what you
   read and the mark is only a handle for the eye, and aria-hidden so it is never spoken. */
.swapIcon,.nutriIcon{color:var(--dim);font-size:14px;line-height:1}
details.swap{margin-top:10px}
.nutriAll{margin-top:14px}

/* Delete says what it is in its colour (Lance: "delete meal can be in red"). Red TEXT on the
   quiet button, not a filled red block: filled red is what the "Delete for good?" confirm
   wears, and a screen with two of those would be shouting before anything is at stake.
   --red is 5.94 on a card, so this clears AA as text. */
.ghost.risk{color:var(--red);border-color:rgb(var(--red-rgb) / .45)}
.ghost.risk:active{background:rgb(var(--red-rgb) / .08)}

/* The meal screen's occasional actions (23 Sep 2026, part two of the tidy-up).

   Four bordered 46px boxes were competing with the one green button that ends the screen: add a
   food, add cooking fat, log this again, remove. All four are things you do sometimes, and the
   outline said each was as important as finishing. Weight now follows how often a thing is used
   and what it costs, which is the same rule the rest of the hierarchy pass follows.

   None of them gets quieter than 44px, because quieter must never mean harder to hit. The border
   is what goes, not the target. */

/* Secondary: a filled well instead of an outline. Still obviously a control, no longer a box
   shouting next to the primary. The two adds are a pair and share one weight. */
.ghost.soft{border-color:transparent;background:var(--well);color:var(--ink);font-weight:550}
.ghost.soft:active{background:var(--raised)}

/* Tertiary: text. "Log this again" is useful and rare, and rare is the half that should show. */
.ghost.plain{border-color:transparent;background:transparent;color:var(--muted);font-weight:550}
.ghost.plain:active{background:var(--well)}

/* Minor destructive: red text, no box. Deliberately lighter than "Delete meal", which keeps its
   red outline, because that ends the whole meal and this drops one ingredient. Same colour, less
   weight, so the difference between them is legible before you tap either. */
.ghost.risk.quiet{border-color:transparent;background:transparent;width:auto;padding-inline:0;
  font-weight:550}
.ghost.risk.quiet:active{background:rgb(var(--red-rgb) / .08)}

/* Google's mark on the sign-in button. Inline SVG, no request, same as every other asset here.
   The white tile is not decoration: four colours on a near-black button read as smudges, and a
   mark people do not recognise is worse than no mark on a button whose whole job is looking
   familiar enough to trust. */
.google{display:flex;align-items:center;justify-content:center;gap:10px}
.gmark{width:19px;height:19px;flex:0 0 19px;background:#fff;border-radius:3px;padding:2px}
.orLine{display:flex;align-items:center;gap:12px;color:var(--dim);font-size:13px;margin:14px 0 10px}
.orLine::before,.orLine::after{content:"";flex:1;height:1px;background:var(--line-soft)}
