/* GENERERAD av apps/journal/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);
  /* HUR MYCKET AV SAFE-AREAN VI MEDVETET TAR TILLBAKA.
     Talet stod inbakat i --shell-top och var därmed osynligt för alla som
     behövde känna till det. Det behöver kännas till: de här pixlarna ligger
     BAKOM statusraden, så allt som ritas där göms.
     Uppmätt på Josuas telefon 2026-08-30 (iPhone, Dynamic Island, dpr 3):
     safe-area-inset-top 68 px, applicerad klarning 61 px. Headern börjar
     alltså på y=61 medan statusraden slutar på 68 — och brickan i
     HeaderActionBtn, som satt 3 px in i raden, skars av upptill.
     Josuas besked: hellre mindre luft upptill än mer. Trimmen står alltså
     kvar, och det som ritas högt får i stället hålla sig nedanför den. */
  --shell-trim:7px;
  --shell-top:max(0px,calc(var(--safe-t) - var(--shell-trim)));
  /* HUR MYCKET AV SAFE-AREAN SOM ÄR TÄCKT MEN OPADDAD (#932).
     Trimmen ovan är avsiktlig, och följden är att rullningsytans översta
     pixlar ligger bakom klockan. Regeln i stycket ovanför säger att det som
     ritas högt får hålla sig nedanför dem — och DET HÄR är talet man ska hålla
     sig nedanför. Det stod ingenstans, så varje yta som behövde det fick
     skriva av `--shell-trim` i stället, vilket är fel på desktop.

     `min()` OCH INTE RÅA `--shell-trim`: den täckta mängden är trimmen bara så
     länge safe-arean är större än den. På desktop är safe-t = 0, alltså är
     ingenting täckt, alltså ska klarningen vara 0 — en rå 7px hade lagt
     dödkött över varje topprad på en skärm utan statusrad.

     Uppmätt på Josuas telefon (iPhone, Dynamic Island, dpr 3): safe-t 68,
     klarning 61, statusraden slutar 68 → 7 px täckta. Verktygsradens
     markeringsram hamnade på y=65 och kapades; se
     apps/mail/test/e2e-safearea.test.mjs, som mäter det utan telefon genom
     att sätta --safe-t för hand. */
  --topp-klarning:min(var(--safe-t),var(--shell-trim));
  /* === RÖRELSESKALAN — CSS-SIDAN AV DESIGN_TOKENS.motion ===
     Skalan bor i packages/shared-ui/themes.js. Raderna här är samma tal
     uttryckta för CSS, av ett skäl som inte går runt: animationerna nedan
     körs vid FÖRSTA MÅLNINGEN, före bundeln finns, alltså kan de inte läsa
     ett JS-objekt. Samma uppdelning som boot-theme.js har för färgerna.

     Två källor som ska säga samma sak är exakt den divergensklass som är
     dyrast i det här repot, så de jämförs mot VARANDRA av
     scripts/check-motion-tokens.js — åt båda håll: ett tal som glidit isär
     fäller, och så gör en variabel utan token eller en token utan variabel.

     Mailens skal-CSS HÄRLEDS ur det här blocket vid bygget (apps/mail/build.sh),
     så variablerna finns i båda apparna utan en andra deklaration. */
  --motion-fast:140ms;
  --motion-base:220ms;
  --motion-slow:320ms;
  --motion-ease:cubic-bezier(0.32,0.72,0,1);
  --motion-release:cubic-bezier(0.22,1,0.36,1);
  --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)}}
/* Obestämd förloppsindikator (#932): ett band som vandrar när vi vet ATT något
   pågår men inte hur långt det kommit. `transform` och inte `left`, så den
   ligger på kompositorn och inte kostar en layout per bildruta. */
@keyframes barSweep{0%{transform:translateX(-100%)}100%{transform:translateX(300%)}}
/* 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 (slow).
   Steget `page` 360 fanns här och är avskaffat 2026-09-08 — sidbytet kör
   samma tal som detaljen och arket. Skriv aldrig tillbaka ett tal i den här
   kommentaren: en siffra i prosa är precis den sorten som ruttnar tyst.
   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 var(--motion-slow) var(--motion-ease) both}
.view-layer--in{animation:viewPageIn var(--motion-slow) var(--motion-ease) both}
.view-layer--out.back{animation-name:viewPageOutBack}
.view-layer--in.back{animation-name:viewPageInBack}
.view-enter{animation:fadeIn var(--motion-slow) var(--motion-ease)}
.view-slide{animation:viewInRight var(--motion-slow) var(--motion-ease)}
.view-slide-back{animation-name:viewInLeft}
/* §8 mönster 6: skelett → innehåll (base).
   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 var(--motion-base) var(--motion-ease) both;pointer-events:none}
.skel-fade-in{animation:skelIn var(--motion-base) var(--motion-ease) both}
/* §8 mönster 2: detalj in/ut (slow) */
.detail-in{animation:detailIn var(--motion-slow) var(--motion-ease) both}
.detail-out{animation:detailOut var(--motion-slow) var(--motion-ease) both}
/* §8 mönster 3: ark/dialog (slow) */
.sheet-in{animation:sheetIn var(--motion-slow) var(--motion-ease) both}
.dialog-in{animation:dialogIn var(--motion-slow) var(--motion-ease) 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 var(--motion-base) var(--motion-ease),opacity var(--motion-base) var(--motion-ease)}
.row-expand.row-expand--open{grid-template-rows:1fr;opacity:1}
.row-expand>*{overflow:hidden;min-height:0}
/* EN STÄNGD VERKTYGSPANEL TAR INGEN PLATS (Josua 2026-09-07). Behållaren i
   packages/shared-ui/verktygsfalt.js ritas alltid; dess innehåll returnerar
   null när ingen panel är öppen. Utan den här raden stod dess marginal kvar
   som en 8 px remsa av sidans bakgrund mellan verktygsraden och listan — i
   BÅDA apparna, eftersom mailens skal-CSS härleds ur det här blocket.
   `:empty` mäter det RENDERADE resultatet; ingen React-prop kan göra det. */
.toolbar-panels:empty{margin:0!important;padding:0!important}
/* §8 mönster 5: listförändring (base) */
.row-enter{animation:rowEnter var(--motion-base) var(--motion-ease) both}
.row-exit{animation:rowExit var(--motion-base) var(--motion-ease) both}
.sheet-backdrop{animation:backdropIn var(--motion-slow) var(--motion-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 var(--motion-fast) var(--motion-ease),opacity var(--motion-fast) var(--motion-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) — men BARA opacity.
     `.view-enter` och `.row-enter`/`.row-exit` stod i den här raden och är
     INTE opacity-fades: `fadeIn` bär translateY(6px) + scale(0.997) och
     rowEnter/rowExit translateY(±4px). Alltså flyttade och skalade appen sitt
     innehåll även för den som uttryckligen bett den låta bli — i ett år, under
     en rubrik som säger motsatsen.
     Rättat genom att BYTA KEYFRAME i stället för att bara korta den: `skelIn`
     och `skelOut` är rena opacity-toningar och finns redan. */
  .sheet-backdrop,.row-enter,.row-exit,.view-enter,.skel-fade-in,.skel-fade-out{animation-duration:var(--motion-fast)!important;transition-duration:var(--motion-fast)!important}
  .view-enter,.row-enter{animation-name:skelIn!important}
  .row-exit{animation-name:skelOut!important}
  .row-expand,.row-expand.row-expand--open{transition-duration:var(--motion-fast)!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}
/* HELBREDD (Josua 2026-09-09): "vi ska nyttja mycket av bredden när den finns."
   En OPT-IN-klass, inte en ändring av taken ovan — och skälet är att mailens
   skalstilar HÄRLEDS ur det här blocket av apps/mail/build.sh. Sänker eller
   höjer man `.app-content` här ändras mailens inkorg i samma andetag, utan att
   någon vakt säger ifrån (samma "en fil, båda apparna"-klass som CSP-felet i
   #860). Med en egen klass måste varje yta säga ja själv.
   Vyn deklarerar `width: "full"` i utils/layoutContract.js; mail sätter klassen
   direkt, eftersom den inte har något layoutkontrakt. */
.app-content.app-content--full{max-width:none}
/* 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 var(--motion-base) var(--motion-release)}
/* 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}
}
