/* GENERERAD av apps/app/build.sh ur frontend/index.html.
   Ändra INTE här — ändra i moneys index.html, eller flytta blocket till
   packages/shared-ui/skal.css (#741). */
*{margin:0;padding:0;box-sizing:border-box}
/* === SÄKERHETSZONERNA (#495) ===
   iOS reserverar en zon i topp och botten. Appen målar redan sin bakgrund
   under HELA zonen (uppmätt 2026-08-05: `.app-fixed-root` börjar på y=0 och
   docken når y=912) — zonen är alltså padding på en målande yta, inte en
   marginal. Det som var fel var att den var för STOR: 68 px tomt över headern
   och 34 px tomt under dockens ikoner.

   Råvärdena bor i egna variabler av ett skäl: env() går inte att simulera i en
   vanlig webbläsare, och just det här området har historiskt kostat två blinda
   CSS-fixar (PR #181, #182) för att det bara gick att mäta på Josuas telefon.
   Med variablerna räcker det att sätta `--safe-t`/`--safe-b` i konsolen för att
   se den riktiga geometrin på en desktop.

   Trimningarna (riktningen är Josuas beslut i #495; talen är förslag som
   verifieras på hans enhet — de är UPPSKATTADE ur öns/indikatorns kända
   geometri, inte uppmätta där):
   - Toppen: iOS reserverar 68 px (uppmätt, app_logs). Öns underkant ligger
     uppskattat ~55 px ned. Vi tar 7 och lämnar 61 — försiktigt första steg,
     samma policy som ISLAND_TRIM i helpers.js för den vågräta insetten.
   - Botten: iOS reserverar 34 px (uppmätt). Home-indikatorn är 5 px hög och
     slutar ~13 px över kanten. Vi tar 18 och lämnar 16 — 3 px över indikatorn,
     och mer än dubbelt Safari-lägets 7 px bottenmarginal.
   De LODRÄTA zonerna läses bara härifrån — ingen annan fil rör env() för dem.
   Den VÅGRÄTA är ett undantag och bor i `safeInsetFor` i helpers.js: sidan
   beror på rotationen och går inte att uttrycka som ett statiskt värde. */
:root{
  --safe-t:env(safe-area-inset-top,0px);
  --safe-b:env(safe-area-inset-bottom,0px);
  --shell-top:max(0px,calc(var(--safe-t) - 7px));
  --dock-bottom:max(0px,calc(var(--safe-b) - 18px));
  --dock-h:calc(50px + var(--dock-bottom));
}
html,body,#root{height:100%;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","SF Pro Text","Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
/* touch-action:manipulation blockerar dubbeltapp-zoom men tillåter pinch-zoom
   (WCAG 1.4.4). */
html{touch-action:manipulation;-webkit-text-size-adjust:100%;text-size-adjust:100%}
/* === TEXTMARKERING ÄR AV I UI:t (Josua 2026-08-08) ===
   Gränssnittets texter ska inte gå att markera — bara data, och den vägen går
   via långtryck → Kopiera (ContextMenu.js). Två regler behövs, inte en:
   `user-select` stoppar markeringen, `-webkit-touch-callout` stoppar iOS egen
   kopiera-meny, som annars äter upp långtrycket innan vår kod ser det.
   Undantagen bär klassen `.selectable`: wikin, felytorna, loggvyn och
   kodblock — allt man rimligen vill kopiera ur i sin helhet.
   OBS: fält ärver INTE user-select:none i moderna motorer, men vi skriver ut
   det ändå så att regeln inte hänger på den detaljen. */
body{overscroll-behavior-y:contain;touch-action:manipulation;
  -webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
.selectable,.selectable *,input,textarea,[contenteditable]{
  -webkit-user-select:text;user-select:text;-webkit-touch-callout:default}
input,button,select,textarea{font-family:inherit}
::-webkit-scrollbar{display:none}
@keyframes pulse{0%,100%{opacity:0.4}50%{opacity:0.8}}
@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
@keyframes fadeIn{from{opacity:0;transform:translateY(6px) scale(0.997)}to{opacity:1;transform:translateY(0) scale(1)}}
/* Toasten centreras med `transform:translateX(-50%)` i sin egen stil. En
   animation ERSÄTTER elementets transform helt så länge den spelar — därför
   måste centreringen skrivas ut i VARJE keyframe. Toasten använde `fadeIn`,
   som bara skriver translateY/scale: centreringen var avstängd under hela
   inglidningen, och toasten hoppade på plats i sista bildrutan. Mätt
   2026-08-20 i 1280 px fönster med en 466 px bred toast: vänsterkanten stod på
   640 px hela animationen igenom och landade på 406 px, alltså ett hopp på
   234 px = halva textens bredd. Längre text hoppade längre (#764).
   `toastOut` måste ha `forwards`: utan fill-mode återgår opacity till 1 i
   glappet innan elementet tas bort, och uttoningen slutar med en blink. */
@keyframes toastIn{from{opacity:0;transform:translateX(-50%) translateY(8px) scale(0.997)}to{opacity:1;transform:translateX(-50%) translateY(0) scale(1)}}
@keyframes toastOut{from{opacity:1;transform:translateX(-50%) translateY(0) scale(1)}to{opacity:0;transform:translateX(-50%) translateY(8px) scale(0.997)}}
/* Grupperingszonen PULSAR (Josua 2026-08-10: *"Blinkandet måste bli mer polerat
   och inte lika framträdande. Mer pulse än blink."*).

   Tre saker gjorde den föregående versionen till en blink, och alla tre är
   borta här:
     - ringen RÖRDE SIG (outline-offset −2 ↔ 2, alltså fyra pixlar ut och in),
     - den låg på FULL accent hela tiden, så rörelsen var det enda som gav den
       rytm — och en hård kant som hoppar läses som ett larm, inte som andning,
     - 620 ms med `motion.ease` är en UT-kurva: den snärtar till ytterläget och
       glider tillbaka, alltså asymmetriskt, vilket är precis blinkens form.

   Nu står ringen still och byter bara STYRKA, långsamt och symmetriskt: 1,8 s
   per varv med `ease-in-out`, mellan 22 % och 75 % accent. Alfan bärs av två
   variabler som sätts i `dropZoneStyle`, eftersom temafärgen inte finns här.

   HISTORIK, för nästa läsare som frestas att göra den till en riktig studs
   igen: den VAR `transform: translateY(-4px)` fram till 2026-08-10, och det
   kallades "utan layoutpåverkan". Det gällde layouten — inte geometrin.
   `getBoundingClientRect` räknar in transformer, och både dragets träffsökning
   och FLIP-mätningen läser just den. Släppmålet rörde sig alltså fyra pixlar
   under ett stillastående finger, tre gånger i sekunden: 7 % av ett 54 px block
   mot en zongräns med 10 % hysteres. Zonen slog om av sin egen animering.
   Rör aldrig släppmålets box under en gest. */
@keyframes dropPulse{0%,100%{outline-color:var(--drop-ring-lo)}50%{outline-color:var(--drop-ring-hi)}}
/* §8 rörelsevokabulär: fem namngivna mönster, motion.ease, enbart transform/opacity */
/* Segmentbyte INUTI en vy (BudgetView:s flikar) — kort förskjutning, inte en
   sidvändning. Skild från sidbytet nedan med flit: en flik inuti en vy byter
   inte sida. */
@keyframes viewInRight{from{opacity:0;transform:translate3d(22px,0,0)}to{opacity:1;transform:translate3d(0,0,0)}}
@keyframes viewInLeft{from{opacity:0;transform:translate3d(-22px,0,0)}to{opacity:1;transform:translate3d(0,0,0)}}
@keyframes viewPageIn{from{transform:translate3d(100%,0,0)}to{transform:translate3d(0,0,0)}}
@keyframes viewPageOut{from{transform:translate3d(0,0,0)}to{transform:translate3d(-100%,0,0)}}
@keyframes viewPageInBack{from{transform:translate3d(-100%,0,0)}to{transform:translate3d(0,0,0)}}
@keyframes viewPageOutBack{from{transform:translate3d(0,0,0)}to{transform:translate3d(100%,0,0)}}
@keyframes sheetIn{from{transform:translate3d(0,100%,0)}to{transform:translate3d(0,0,0)}}
@keyframes backdropIn{from{opacity:0}to{opacity:1}}
@keyframes detailIn{from{opacity:0;transform:translate3d(100%,0,0)}to{opacity:1;transform:translate3d(0,0,0)}}
@keyframes detailOut{from{opacity:1;transform:translate3d(0,0,0)}to{opacity:0;transform:translate3d(-24%,0,0)}}
@keyframes dialogIn{from{opacity:0;transform:scale(0.96)}to{opacity:1;transform:scale(1)}}
@keyframes rowEnter{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
@keyframes rowExit{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-4px)}}
@keyframes skelOut{from{opacity:1}to{opacity:0}}
@keyframes skelIn{from{opacity:0}to{opacity:1}}
/* §8 mönster 1: sidbyte (page 360ms).
   Två sidor som rör sig tillsammans, som att bläddra mellan bilder i iOS
   Bilder (Josua 2026-08-02). Ingen opacity — sidorna finns hela vägen, de
   flyttar sig bara. Den gamla ligger absolut på sin faktiska scrollposition
   (App.js sätter `top`), annars visar den sin topp när man står nedscrollad.
   overflow-x:clip, inte hidden: clip skapar ingen scrollcontainer och kan
   därför paras med overflow-y:visible — hidden hade tvingat med andra axeln
   och gjort etappen till en vertikal scrollare. */
.view-stage{position:relative}
/* Grannfliken under ett kantsvep (#454/#455). Ligger en hel bredd åt sidan,
   utanför flödet, så banan kan dras 1:1 med fingret och grannen kommer in i
   takt.
   KLIPPNINGEN SITTER PÅ ETAPPEN, som står still — inte på banan, som rör sig.
   Låg den på det som flyttades följde grannen med som barn och kom aldrig in i
   bild; man såg bara den gamla sidan glida bort. */
.view-stage.has-peek{overflow-x:clip;overflow-y:visible}
/* Samma regel för flikbytets bana: vågrätt är vårt, lodrätt webbläsarens. */
.view-track{position:relative;touch-action:pan-y}
.view-peek{position:absolute;top:0;left:0;right:0;pointer-events:none}
.view-peek--left{transform:translate3d(-100%,0,0)}
.view-peek--right{transform:translate3d(100%,0,0)}
.view-stage.is-moving{overflow-x:clip;overflow-y:visible}
.view-layer--out{position:absolute;left:0;right:0;animation:viewPageOut 360ms cubic-bezier(0.32,0.72,0,1) both}
.view-layer--in{animation:viewPageIn 360ms cubic-bezier(0.32,0.72,0,1) both}
.view-layer--out.back{animation-name:viewPageOutBack}
.view-layer--in.back{animation-name:viewPageInBack}
.view-enter{animation:fadeIn 320ms cubic-bezier(0.32,0.72,0,1)}
.view-slide{animation:viewInRight 360ms cubic-bezier(0.32,0.72,0,1)}
.view-slide-back{animation-name:viewInLeft}
/* §8 mönster 6: skelett → innehåll (base 220ms).
   Formen är densamma i båda lägena, så bytet ska inte synas som ett byte.
   Skelettet och innehållet ligger i SAMMA rutnätscell och korstonar: höjden
   är max av de två, och eftersom formerna matchar är det ingen skillnad.
   Enbart opacity — därför överlever den prefers-reduced-motion (§11). */
.skel-swap{display:grid}
.skel-swap>*{grid-area:1/1;min-width:0}
.skel-fade-out{animation:skelOut 220ms cubic-bezier(0.32,0.72,0,1) both;pointer-events:none}
.skel-fade-in{animation:skelIn 220ms cubic-bezier(0.32,0.72,0,1) both}
/* §8 mönster 2: detalj in/ut (slow 320ms) */
.detail-in{animation:detailIn 320ms cubic-bezier(0.32,0.72,0,1) both}
.detail-out{animation:detailOut 320ms cubic-bezier(0.32,0.72,0,1) both}
/* §8 mönster 3: ark/dialog (slow 320ms) */
.sheet-in{animation:sheetIn 320ms cubic-bezier(0.32,0.72,0,1) both}
.dialog-in{animation:dialogIn 320ms cubic-bezier(0.32,0.72,0,1) both}
/* §8 mönster 4: radexpansion via grid-template-rows-transition + opacity */
.row-expand{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows 220ms cubic-bezier(0.32,0.72,0,1),opacity 220ms cubic-bezier(0.32,0.72,0,1)}
.row-expand.row-expand--open{grid-template-rows:1fr;opacity:1}
.row-expand>*{overflow:hidden;min-height:0}
/* §8 mönster 5: listförändring (base 220ms) */
.row-enter{animation:rowEnter 220ms cubic-bezier(0.32,0.72,0,1) both}
.row-exit{animation:rowExit 220ms cubic-bezier(0.32,0.72,0,1) both}
.sheet-backdrop{animation:backdropIn 280ms ease}
button{-webkit-tap-highlight-color:transparent}
/* Knapp-/rad-feedback: subtil scale/opacity på :active (GPU). Inline-transform
   (t.ex. dock-ikoner) vinner över denna regel och behåller sin egen känsla. */
button,.press-dim{transition:transform 0.16s cubic-bezier(0.32,0.72,0,1),opacity 0.16s ease}
button:not(:disabled):not(.list-row):active{transform:scale(0.97)}
button.list-row:not(:disabled):active{opacity:0.7}
.press-dim:active{opacity:0.6}
input:focus,select:focus,textarea:focus{outline:none}
/* §11: synlig tangentbordsfokus — 2px accent-ring (pekfokus ger ingen ring) */
/* FÄLT FÅR RINGEN INUTI. Ett textfält ligger ofta kant i kant med sin
   behållare, och en outline ritas ALLTID utanför rutan: med offset 2px föll
   ringens vänster- och högersida utanför och klipptes bort av en förfader
   med overflow-x. Uppmätt i wizarden — fältet och varje förfader hade exakt
   samma vänster- och högerkant, alltså noll utrymme åt ringen, och Josua såg
   en markering som bara fanns upptill och nedtill.
   Negativ offset ritar ringen innanför kanten, där ingenting kan klippa den.
   Den läser dessutom som fältets egen kant, vilket är vad markeringen ska
   betyda.
   `border-radius` sätts INTE längre: raden gav det fokuserade fältet 6px
   hörn oavsett vad det hade innan, så rutan bytte form i samma ögonblick som
   man klickade i den. En outline följer elementets egen radie av sig själv. */
input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--focus, #5B9DFF);outline-offset:-2px}
button:focus-visible,a:focus-visible,[role="button"]:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--focus, #5B9DFF);outline-offset:2px;border-radius:6px}
/* §8 + §11: prefers-reduced-motion — stäng av transform-animationer, behåll opacity-fades */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation-duration:0.001ms!important;animation-delay:0ms!important;animation-iteration-count:1!important;transition-duration:0.001ms!important;scroll-behavior:auto!important}
  /* Opacity-fades återaktiveras (§8) */
  .sheet-backdrop,.row-enter,.row-exit,.view-enter,.skel-fade-in,.skel-fade-out{animation-duration:150ms!important;transition-duration:150ms!important}
  .row-expand,.row-expand.row-expand--open{transition-duration:150ms!important}
  button:not(:disabled):active{transform:none}
  button.list-row:not(:disabled):active{opacity:1}
  .press-dim:active{opacity:1}
}
/* Responsive breakpoints: tablet ≥768, desktop ≥900, large ≥1440
   Desktop-gränsen är 900 (inte 1024) sedan 2026-07-29 så att en telefon i
   landskap (iPhone Air: 912 px) får desktopläget. Måste hållas i synk med
   isDesktop i App.js. */
.app-content{width:100%;margin:0 auto;padding-bottom:24px}
/* Dockens klarning (§6.1). Docken är `position:fixed`, 50 px + safe-area, och
   ligger alltså OVANPÅ innehållet — utan plats nedanför göms den sista raden.
   Vyerna la tidigare ut var sin 24 px-avslutning, vilket är 26 px för lite:
   nedersta transaktionen kapades i både översikten och transaktionsfliken, och
   vyer utan egen avslutning hade ingen marginal alls.
   Regeln bor här i stället för i varje vy — skalet är det enda som vet om
   docken finns, och en vy ska inte behöva känna till navigationens höjd.
   Reserveringen räknas ur dockens FAKTISKA höjd via --dock-h (#495): ändras
   dockens bottenpadding måste klarningen ändras med, annars kapas sista raden
   igen. 24 px är luften ovanpå docken. */
.app-shell--dock .app-content{padding-bottom:calc(24px + var(--dock-h))}
@media(min-width:768px){
  .app-content{max-width:640px}
  .app-content.app-content--wide{max-width:960px}
}
@media(min-width:900px){
  .app-content{max-width:720px}
  .app-content.app-content--wide{max-width:1060px}
}
@media(min-width:1440px){
  .app-content{max-width:800px}
  .app-content.app-content--wide{max-width:1200px}
}
/* Kolumnflöde (§4.1, #421): läsbredden kapar EN spalt. När skalet har mätt att
   panelen rymmer flera kolumner sätter App.js den här klassen, och kapningen
   flyttas från panelen till kolumnen (COLUMN_MIN i layoutContract.js). Ingen
   mediafråga: klassen är redan villkorad av uppmätt panelyta.
   Taket 2440 = 3 kolumner (COLUMN_MAX) × 800 px läsbredd + 2 × 20 px mellanrum:
   över det växer kolumnerna själva förbi läsbredden, och då centreras
   innehållet i stället (§5.4:s regel för när ytan är större än innehållet). */
.app-content.app-content--columns{max-width:2440px}
/* Desktop hover states (pointer devices only) */
@media(hover:hover){
  .app-sidebar button:hover{opacity:1!important}
  .app-orb-level button:hover{opacity:1!important}
  .app-sidebar-child:hover{opacity:1!important}
  .card-hover:hover{transform:translateY(-1px);filter:brightness(1.02)}
  button.list-row:not(:disabled):hover{background:rgba(128,128,128,0.06)}
  .press-dim:hover{opacity:0.85}
}
/* Pointer-fine: tighter spacing for mouse/trackpad */
@media(pointer:fine){
  button.list-row{min-height:auto}
}
/* Slider (§7.1). Native input i botten — den ger piltangenter, Home/End och
   skärmläsarsemantik (role=slider, aria-valuenow) gratis; en div-baserad slider
   tappar allt det. Skinningen sker med pseudo-element, som inte går att nå med
   Reacts inline-style — därför här. Temafärgerna kommer in som CSS-variabler
   satta inline, eftersom CSS inte kan läsa JS-tokens.
   Höjd 44 = t.size.touch: hela kontrollen är träffyta, inte bara spåret. */
/* touch-action:none — reglaget ÄGER sin gest. Utan den ärvs pagerns pan-y,
   och då äger webbläsaren den lodräta axeln under hela draget: minsta lodräta
   rörelse rycker greppet till sidscrollen och reglaget "slutar reagera".
   Samma insikt som DESIGN-SYSTEM §7.2 — pan-y GER bort en axel. */
input[type="range"].ui-slider{-webkit-appearance:none;appearance:none;background:transparent;width:100%;height:44px;margin:0;padding:0;display:block;cursor:pointer;touch-action:none}
input[type="range"].ui-slider:disabled{cursor:not-allowed;opacity:.45}
input[type="range"].ui-slider::-webkit-slider-runnable-track{height:4px;border-radius:999px;background:var(--sl-track)}
input[type="range"].ui-slider::-moz-range-track{height:4px;border-radius:999px;background:var(--sl-track)}
/* -9px = (4 − 22) / 2 — centrerar 22px-greppet på 4px-spåret i WebKit. */
input[type="range"].ui-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:22px;height:22px;margin-top:-9px;border-radius:50%;background:var(--sl-thumb);border:2px solid var(--sl-thumb-border);box-shadow:0 1px 3px rgba(0,0,0,.28)}
input[type="range"].ui-slider::-moz-range-thumb{width:22px;height:22px;border-radius:50%;background:var(--sl-thumb);border:2px solid var(--sl-thumb-border);box-shadow:0 1px 3px rgba(0,0,0,.28)}
input[type="range"].ui-slider:focus{outline:none}
input[type="range"].ui-slider:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px var(--sl-ring)}
input[type="range"].ui-slider:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px var(--sl-ring)}
@media(hover:hover){input[type="range"].ui-slider:not(:disabled):hover::-webkit-slider-thumb{box-shadow:0 0 0 6px var(--sl-ring)}
input[type="range"].ui-slider:not(:disabled):hover::-moz-range-thumb{box-shadow:0 0 0 6px var(--sl-ring)}}
/* Pager (§7.2) — svep mellan syskon, transformbaserad.
   Första försöket använde scroll-snap. Mätning på riktig enhet (#438) visade
   varför det inte gick: CSS tillåter inte overflow-x:auto tillsammans med
   overflow-y:visible — den andra axeln tvingas med. Ett element som scrollar
   horisontellt är alltså ALLTID också en vertikal scrollcontainer, och på iOS
   svalde den de vertikala dragen i stället för att kedja vidare till sidan.
   Mätningen: 264 px vertikalt drag → 0 px rörelse på båda axlarna, med jämna
   16 ms-ramar. Alltså inte hackigt — blockerat.

   `overflow-x: clip` klipper grannarna UTAN att skapa en scrollcontainer, och
   får kombineras med overflow-y:visible. Vertikal scroll passerar orörd. */
/* touch-action:pan-y — webbläsaren äger den LODRÄTA axeln, vi den vågräta.
   Utan den avgör webbläsaren själv mitt i gesten och kunde fortsätta scrolla
   lodrätt efter att vi låst horisontellt: sidan gled i sidled och upp/ned
   samtidigt (#456). preventDefault räcker inte — hade scrollen redan startat
   respekteras den inte. Deklarationen gör valet FÖRE gesten i stället.
   OBS: pan-y ensamt räcker inte heller. Det GER webbläsaren lodrätt för hela
   gesten, så efter att draget låst sig i sidled gick sidan fortfarande att
   scrolla (Josua 2026-08-02). Låsningen görs därför i JS när riktningen är
   avgjord — se lockScrollY i utils/gesture.js. */
.ui-pager{position:relative;overflow-x:clip;overflow-y:visible;touch-action:pan-y}
.ui-pager-track{position:relative;will-change:transform}
.ui-pager-track.is-animating{transition:transform 220ms cubic-bezier(0.22,0.61,0.36,1)}
/* Grannarna ligger utanför flödet — de får aldrig påverka sidans höjd. */
.ui-pager-side{position:absolute;top:0;width:100%}
.ui-pager-side.prev{right:100%}
.ui-pager-side.next{left:100%}
@media(prefers-reduced-motion:reduce){.ui-pager-track.is-animating{transition:none}}
/* iOS auto-zoom prevention: text inputs ≥16px on mobile */
@media(max-width:767px){
  input[type="text"],input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="search"],input[type="tel"],input[type="url"],input:not([type]),select,textarea{font-size:16px!important}
  /* Transaktionsnamnet redigeras där namnet står, och namnet kan vara STÖRRE
     än 16 px (upp till 20, se TX-DETAIL-SPEC §2). Regeln ovan har !important
     och slår ut inline-stilen, så ett kort namn krympte 20 → 16 i det ögonblick
     man tryckte på pennan. Fältet sätter i stället sin storlek i variabeln, som
     TxRow klampar till minst 16 — zoomskyddet står kvar, taket också. */
  input.tx-name-input{font-size:var(--tx-name-size,16px)!important}
}
