[{"data":1,"prerenderedAt":1089},["ShallowReactive",2],{"blog-\u002Fpl\u002Fblog\u002F2026\u002Fnaglowki-agrestapp":3,"blog-project-\u002Fpl\u002Fblog\u002F2026\u002Fnaglowki-agrestapp":948},{"id":4,"title":5,"art":6,"body":7,"date":932,"description":933,"draft":934,"extension":935,"image":936,"kind":937,"meta":938,"navigation":137,"ogImage":939,"path":940,"project":941,"seo":942,"slug":936,"stem":943,"tags":944,"updated":936,"__hash__":947},"blog_pl\u002Fpl\u002Fblog\u002F2026\u002Fnaglowki-agrestapp.md","Nagłówki AgrestApp: 36 px dla myszy, 44 px dla palca","\u002Fart\u002Fblog\u002Fnaglowki-agrestapp.svg",{"type":8,"value":9,"toc":924},"minimark",[10,14,19,34,43,50,57,60,345,348,351,359,363,366,373,459,466,470,473,503,506,606,614,618,629,635,792,799,807,814,818,821,889,896,900,917,920],[11,12,13],"p",{},"AgrestApp ma dwa nagłówki: na stronie firmowej i w panelu po zalogowaniu. Oba działały, ale każdy był zbudowany inaczej, a rozmiary brały się z przypadkowych pikseli. 15 października przepisałem oba. Wyszło 8 commitów, 40 zmienionych plików i +1424 \u002F −923 linii, a stary nagłówek panelu (643 linie w jednym pliku) zastąpiły trzy mniejsze komponenty. W kolejnych dwóch dniach poprawiłem jeszcze kolory ikon. Zrzuty poniżej są z lokalnej wersji na zmyślonych danych.",[15,16,18],"h2",{"id":17},"menu-na-stronie-firmowej","Menu na stronie firmowej",[11,20,21,22,26,27,26,30,33],{},"Stare menu działało tylko po najechaniu myszą. Miało jedną grupę „System Agrest”, a w niej podmenu w podmenu: Branże, Rozwiązania, a nawet Cennik. Szerokości i odstępy były wpisane na sztywno (",[23,24,25],"code",{},"w-[180px]",", ",[23,28,29],{},"gap-[14px]",[23,31,32],{},"gap-[30px]",").",[11,35,36],{},[37,38],"img",{"alt":39,"height":40,"src":41,"width":42},"Pasek menu strony – u góry przed zmianą, na dole po",184,"\u002Fblog\u002F2026\u002Fnaglowki-agrestapp\u002Fsite-header.webp",1400,[11,44,45,46,49],{},"Najważniejsze sekcje wyszły na wierzch jako osobne grupy, a Cennik stał się zwykłym linkiem. Cała struktura jest w jednej liście w ",[23,47,48],{},"mainNav.js",". Z niej powstaje i pasek na komputerze, i menu na telefonie, więc nowa pozycja nie wymaga zmian w dwóch miejscach.",[11,51,52],{},[37,53],{"alt":54,"height":55,"src":56,"width":42},"Rozwinięta grupa „System Agrest” – u góry przed zmianą (podmenu w podmenu), na dole po",736,"\u002Fblog\u002F2026\u002Fnaglowki-agrestapp\u002Fsite-dropdown.webp",[11,58,59],{},"Zamiast osobnej listy dla każdej grupy jest jeden panel. Przy przejściu między grupami przesuwa się pod wskazaną pozycję i dopasowuje szerokość i wysokość do treści. Pozycję i rozmiar liczy JavaScript, a Tailwind tylko je czyta ze zmiennych CSS:",[61,62,67],"pre",{"className":63,"code":64,"language":65,"meta":66,"style":66},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup>\n\u002F\u002F Pozycja i rozmiar panelu – liczone przy otwarciu grupy\nconst panelBox = ref({ x: 0, width: 0, height: 0 })\n\nconst panelStyle = computed(() => ({\n  '--panel-x': `${panelBox.value.x}px`,\n  '--panel-w': `${panelBox.value.width}px`,\n  '--panel-h': `${panelBox.value.height}px`,\n}))\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv\n    :style=\"panelStyle\"\n    class=\"absolute translate-x-(--panel-x) w-(--panel-w) h-(--panel-h)\n           transition-[translate,width,height] duration-200\"\n  >\n    \u003C!-- treść aktywnej grupy -->\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","vue","",[23,68,69,89,96,132,139,161,194,219,244,250,260,265,275,284,296,307,313,319,325,336],{"__ignoreMap":66},[70,71,74,78,82,86],"span",{"class":72,"line":73},"line",1,[70,75,77],{"class":76},"sVt8B","\u003C",[70,79,81],{"class":80},"s9eBZ","script",[70,83,85],{"class":84},"sScJk"," setup",[70,87,88],{"class":76},">\n",[70,90,92],{"class":72,"line":91},2,[70,93,95],{"class":94},"sqY6-","\u002F\u002F Pozycja i rozmiar panelu – liczone przy otwarciu grupy\n",[70,97,99,103,107,110,113,116,119,122,124,127,129],{"class":72,"line":98},3,[70,100,102],{"class":101},"szBVR","const",[70,104,106],{"class":105},"sj4cs"," panelBox",[70,108,109],{"class":101}," =",[70,111,112],{"class":84}," ref",[70,114,115],{"class":76},"({ x: ",[70,117,118],{"class":105},"0",[70,120,121],{"class":76},", width: ",[70,123,118],{"class":105},[70,125,126],{"class":76},", height: ",[70,128,118],{"class":105},[70,130,131],{"class":76}," })\n",[70,133,135],{"class":72,"line":134},4,[70,136,138],{"emptyLinePlaceholder":137},true,"\n",[70,140,142,144,147,149,152,155,158],{"class":72,"line":141},5,[70,143,102],{"class":101},[70,145,146],{"class":105}," panelStyle",[70,148,109],{"class":101},[70,150,151],{"class":84}," computed",[70,153,154],{"class":76},"(() ",[70,156,157],{"class":101},"=>",[70,159,160],{"class":76}," ({\n",[70,162,164,168,171,174,177,180,183,185,188,191],{"class":72,"line":163},6,[70,165,167],{"class":166},"sZZnC","  '--panel-x'",[70,169,170],{"class":76},": ",[70,172,173],{"class":166},"`${",[70,175,176],{"class":76},"panelBox",[70,178,179],{"class":166},".",[70,181,182],{"class":76},"value",[70,184,179],{"class":166},[70,186,187],{"class":76},"x",[70,189,190],{"class":166},"}px`",[70,192,193],{"class":76},",\n",[70,195,197,200,202,204,206,208,210,212,215,217],{"class":72,"line":196},7,[70,198,199],{"class":166},"  '--panel-w'",[70,201,170],{"class":76},[70,203,173],{"class":166},[70,205,176],{"class":76},[70,207,179],{"class":166},[70,209,182],{"class":76},[70,211,179],{"class":166},[70,213,214],{"class":76},"width",[70,216,190],{"class":166},[70,218,193],{"class":76},[70,220,222,225,227,229,231,233,235,237,240,242],{"class":72,"line":221},8,[70,223,224],{"class":166},"  '--panel-h'",[70,226,170],{"class":76},[70,228,173],{"class":166},[70,230,176],{"class":76},[70,232,179],{"class":166},[70,234,182],{"class":76},[70,236,179],{"class":166},[70,238,239],{"class":76},"height",[70,241,190],{"class":166},[70,243,193],{"class":76},[70,245,247],{"class":72,"line":246},9,[70,248,249],{"class":76},"}))\n",[70,251,253,256,258],{"class":72,"line":252},10,[70,254,255],{"class":76},"\u003C\u002F",[70,257,81],{"class":80},[70,259,88],{"class":76},[70,261,263],{"class":72,"line":262},11,[70,264,138],{"emptyLinePlaceholder":137},[70,266,268,270,273],{"class":72,"line":267},12,[70,269,77],{"class":76},[70,271,272],{"class":80},"template",[70,274,88],{"class":76},[70,276,278,281],{"class":72,"line":277},13,[70,279,280],{"class":76},"  \u003C",[70,282,283],{"class":80},"div\n",[70,285,287,290,293],{"class":72,"line":286},14,[70,288,289],{"class":84},"    :style",[70,291,292],{"class":76},"=",[70,294,295],{"class":166},"\"panelStyle\"\n",[70,297,299,302,304],{"class":72,"line":298},15,[70,300,301],{"class":84},"    class",[70,303,292],{"class":76},[70,305,306],{"class":166},"\"absolute translate-x-(--panel-x) w-(--panel-w) h-(--panel-h)\n",[70,308,310],{"class":72,"line":309},16,[70,311,312],{"class":166},"           transition-[translate,width,height] duration-200\"\n",[70,314,316],{"class":72,"line":315},17,[70,317,318],{"class":76},"  >\n",[70,320,322],{"class":72,"line":321},18,[70,323,324],{"class":94},"    \u003C!-- treść aktywnej grupy -->\n",[70,326,328,331,334],{"class":72,"line":327},19,[70,329,330],{"class":76},"  \u003C\u002F",[70,332,333],{"class":80},"div",[70,335,88],{"class":76},[70,337,339,341,343],{"class":72,"line":338},20,[70,340,255],{"class":76},[70,342,272],{"class":80},[70,344,88],{"class":76},[11,346,347],{},"Menu reaguje na sposób obsługi. Mysz otwiera grupę najechaniem, a zamyka ją dopiero 150 ms po zjechaniu, żeby niedokładny ruch nie zamykał panelu. Dotyk przełącza grupę tapnięciem. Klawiatura obsługuje Enter, Esc i wyjście fokusem.",[11,349,350],{},"Na telefonie grupy są akordeonami, a przyciski logowania stoją przyklejone na dole ekranu. Przewija się tylko lista.",[11,352,353],{},[37,354],{"alt":355,"height":356,"src":357,"width":358},"Menu na telefonie – po lewej przed zmianą, po prawej po",844,"\u002Fblog\u002F2026\u002Fnaglowki-agrestapp\u002Fsite-phone-menu.webp",796,[15,360,362],{"id":361},"jedna-zasada-rozmiaru-mysz-36-px-dotyk-44-px","Jedna zasada rozmiaru: mysz 36 px, dotyk 44 px",[11,364,365],{},"W panelu przyciski-ikony miały różne rozmiary: 30, 36, 40, a awatar 56 px. Teraz jest jedna zasada. Przy myszy przyciski mają ok. 36 px, bo kursor jest precyzyjny i gęstszy układ jest wygodny. Przy dotyku mają 44 px, czyli minimum z WCAG 2.5.5.",[11,367,368,369,372],{},"Nie użyłem do tego breakpointów, tylko wariantu ",[23,370,371],{},"pointer-coarse:"," z Tailwinda v4. Rozmiar zależy więc od urządzenia wskazującego, a nie od szerokości ekranu. Tablet z myszą dostaje 36 px, a laptop z ekranem dotykowym 44 px.",[61,374,378],{"className":375,"code":376,"language":377,"meta":66,"style":66},"language-html shiki shiki-themes github-light github-dark","\u003C!-- ikona 18 px + padding: 36 px przy myszy, 44 px przy dotyku -->\n\u003Cbutton class=\"p-2 pointer-coarse:p-3 [&_svg]:size-4.5\" aria-label=\"Powiadomienia\">\n  \u003CBellIcon \u002F>\n\u003C\u002Fbutton>\n\n\u003C!-- pozycja menu: wysokość z paddingu, przy dotyku co najmniej 44 px -->\n\u003Ca class=\"px-3 py-2 pointer-coarse:min-h-11\">Moja firma\u003C\u002Fa>\n","html",[23,379,380,385,410,421,429,433,438],{"__ignoreMap":66},[70,381,382],{"class":72,"line":73},[70,383,384],{"class":94},"\u003C!-- ikona 18 px + padding: 36 px przy myszy, 44 px przy dotyku -->\n",[70,386,387,389,392,395,397,400,403,405,408],{"class":72,"line":91},[70,388,77],{"class":76},[70,390,391],{"class":80},"button",[70,393,394],{"class":84}," class",[70,396,292],{"class":76},[70,398,399],{"class":166},"\"p-2 pointer-coarse:p-3 [&_svg]:size-4.5\"",[70,401,402],{"class":84}," aria-label",[70,404,292],{"class":76},[70,406,407],{"class":166},"\"Powiadomienia\"",[70,409,88],{"class":76},[70,411,412,414,418],{"class":72,"line":98},[70,413,280],{"class":76},[70,415,417],{"class":416},"s7hpK","BellIcon",[70,419,420],{"class":76}," \u002F>\n",[70,422,423,425,427],{"class":72,"line":134},[70,424,255],{"class":76},[70,426,391],{"class":80},[70,428,88],{"class":76},[70,430,431],{"class":72,"line":141},[70,432,138],{"emptyLinePlaceholder":137},[70,434,435],{"class":72,"line":163},[70,436,437],{"class":94},"\u003C!-- pozycja menu: wysokość z paddingu, przy dotyku co najmniej 44 px -->\n",[70,439,440,442,445,447,449,452,455,457],{"class":72,"line":196},[70,441,77],{"class":76},[70,443,444],{"class":80},"a",[70,446,394],{"class":84},[70,448,292],{"class":76},[70,450,451],{"class":166},"\"px-3 py-2 pointer-coarse:min-h-11\"",[70,453,454],{"class":76},">Moja firma\u003C\u002F",[70,456,444],{"class":80},[70,458,88],{"class":76},[11,460,461,462,465],{},"Ta sama zasada działa w menu konta, w menu strony i w polu wyszukiwania (",[23,463,464],{},"h-9 pointer-coarse:h-11","). Przycisk menu na telefonie ma stałe 44 × 44 px, więc zamiana ☰ na × nie przesuwa paska.",[15,467,469],{"id":468},"ikony-w-jednym-kolorze","Ikony w jednym kolorze",[11,471,472],{},"Po pierwszej wersji zobaczyłem, że ikony w nagłówku i w menu konta mają różne kolory. Część brała kolor tekstu, a część szarość z innego miejsca. Ustawiłem więc jedną zasadę:",[474,475,476,487,494,497],"ul",{},[477,478,479,480,483,484,179],"li",{},"W spoczynku każda ikona-przycisk w pasku i każda ikona w menu konta ma ten sam drugorzędny kolor: ",[23,481,482],{},"fill-slate-500",", a w trybie ciemnym ",[23,485,486],{},"fill-slate-400",[477,488,489,490,493],{},"Po najechaniu na pozycję w menu konta ikona bierze kolor tekstu (",[23,491,492],{},"group-hover:fill-current","), więc tekst i ikona zmieniają się razem.",[477,495,496],{},"Przycisk aktywnej strony dostaje pełną wersję ikony zamiast samego tła, np. koło ratunkowe na stronie pomocy.",[477,498,499,500,179],{},"Dzwonek z powiadomieniem to osobna ikona, a nie czerwona kropka doklejona przez ",[23,501,502],{},"absolute",[11,504,505],{},"Wariant ikony wybiera mała funkcja. Ta sama logika obsłuży koszyk i inne ikony z licznikiem:",[61,507,511],{"className":508,"code":509,"language":510,"meta":66,"style":66},"language-js shiki shiki-themes github-light github-dark","\u002F\u002F Jedna z czterech nazw: zwykła, pełna, z powiadomieniem, z powiadomieniem pełna\nfunction notificationIcon(icon, notification, hasNotification, active) {\n  const name = hasNotification ? notification : icon\n  return active ? `${name}-solid` : name\n}\n","js",[23,512,513,518,551,576,601],{"__ignoreMap":66},[70,514,515],{"class":72,"line":73},[70,516,517],{"class":94},"\u002F\u002F Jedna z czterech nazw: zwykła, pełna, z powiadomieniem, z powiadomieniem pełna\n",[70,519,520,523,526,529,533,535,538,540,543,545,548],{"class":72,"line":91},[70,521,522],{"class":101},"function",[70,524,525],{"class":84}," notificationIcon",[70,527,528],{"class":76},"(",[70,530,532],{"class":531},"siNPP","icon",[70,534,26],{"class":76},[70,536,537],{"class":531},"notification",[70,539,26],{"class":76},[70,541,542],{"class":531},"hasNotification",[70,544,26],{"class":76},[70,546,547],{"class":531},"active",[70,549,550],{"class":76},") {\n",[70,552,553,556,559,561,564,567,570,573],{"class":72,"line":98},[70,554,555],{"class":101},"  const",[70,557,558],{"class":105}," name",[70,560,109],{"class":101},[70,562,563],{"class":76}," hasNotification ",[70,565,566],{"class":101},"?",[70,568,569],{"class":76}," notification ",[70,571,572],{"class":101},":",[70,574,575],{"class":76}," icon\n",[70,577,578,581,584,586,589,592,595,598],{"class":72,"line":134},[70,579,580],{"class":101},"  return",[70,582,583],{"class":76}," active ",[70,585,566],{"class":101},[70,587,588],{"class":166}," `${",[70,590,591],{"class":76},"name",[70,593,594],{"class":166},"}-solid`",[70,596,597],{"class":101}," :",[70,599,600],{"class":76}," name\n",[70,602,603],{"class":72,"line":141},[70,604,605],{"class":76},"}\n",[11,607,608],{},[37,609],{"alt":610,"height":611,"src":612,"width":613},"Menu konta – po lewej przed zmianą, po prawej po: ikony w jednym kolorze",460,"\u002Fblog\u002F2026\u002Fnaglowki-agrestapp\u002Fpanel-account-menu.webp",1016,[15,615,617],{"id":616},"wysokość-nagłówka-w-jednej-zmiennej","Wysokość nagłówka w jednej zmiennej",[11,619,620,621,624,625,628],{},"Najwięcej porządku dała mała zmiana. Boczne menu i przyklejone paski stron w panelu leżą pod nagłówkiem, więc muszą znać jego wysokość. Wcześniej była wpisana na sztywno: ",[23,622,623],{},"top-[75px]",", a gdy administrator był zalogowany jako inna firma i nad nagłówkiem pojawiał się pasek „Zaloguj jako” – ",[23,626,627],{},"!top-[125px]",". Ten sam warunek powtarzał się w kilku plikach. Jeden pasek i tak chował się 11 px pod nagłówkiem.",[11,630,631,632,572],{},"Teraz pasek „Zaloguj jako” jest częścią nagłówka, a nagłówek sam mierzy swoją wysokość i zapisuje ją w zmiennej CSS na elemencie ",[23,633,634],{},"\u003Chtml>",[61,636,638],{"className":63,"code":637,"language":65,"meta":66,"style":66},"\u003Cscript setup>\nimport { useElementSize } from '@vueuse\u002Fcore'\n\nconst header = ref(null)\nconst { height } = useElementSize(header, undefined, { box: 'border-box' })\n\n\u002F\u002F Wysokość nagłówka dla elementów, które leżą pod nim\nwatch(height, (value) => {\n  document.documentElement.style.setProperty('--panel-header-height', `${value || 72}px`)\n}, { immediate: true })\n\u003C\u002Fscript>\n",[23,639,640,650,664,668,687,718,722,727,745,774,784],{"__ignoreMap":66},[70,641,642,644,646,648],{"class":72,"line":73},[70,643,77],{"class":76},[70,645,81],{"class":80},[70,647,85],{"class":84},[70,649,88],{"class":76},[70,651,652,655,658,661],{"class":72,"line":91},[70,653,654],{"class":101},"import",[70,656,657],{"class":76}," { useElementSize } ",[70,659,660],{"class":101},"from",[70,662,663],{"class":166}," '@vueuse\u002Fcore'\n",[70,665,666],{"class":72,"line":98},[70,667,138],{"emptyLinePlaceholder":137},[70,669,670,672,675,677,679,681,684],{"class":72,"line":134},[70,671,102],{"class":101},[70,673,674],{"class":105}," header",[70,676,109],{"class":101},[70,678,112],{"class":84},[70,680,528],{"class":76},[70,682,683],{"class":105},"null",[70,685,686],{"class":76},")\n",[70,688,689,691,694,696,699,701,704,707,710,713,716],{"class":72,"line":141},[70,690,102],{"class":101},[70,692,693],{"class":76}," { ",[70,695,239],{"class":105},[70,697,698],{"class":76}," } ",[70,700,292],{"class":101},[70,702,703],{"class":84}," useElementSize",[70,705,706],{"class":76},"(header, ",[70,708,709],{"class":105},"undefined",[70,711,712],{"class":76},", { box: ",[70,714,715],{"class":166},"'border-box'",[70,717,131],{"class":76},[70,719,720],{"class":72,"line":163},[70,721,138],{"emptyLinePlaceholder":137},[70,723,724],{"class":72,"line":196},[70,725,726],{"class":94},"\u002F\u002F Wysokość nagłówka dla elementów, które leżą pod nim\n",[70,728,729,732,735,737,740,742],{"class":72,"line":221},[70,730,731],{"class":84},"watch",[70,733,734],{"class":76},"(height, (",[70,736,182],{"class":531},[70,738,739],{"class":76},") ",[70,741,157],{"class":101},[70,743,744],{"class":76}," {\n",[70,746,747,750,753,755,758,760,762,764,767,770,772],{"class":72,"line":246},[70,748,749],{"class":76},"  document.documentElement.style.",[70,751,752],{"class":84},"setProperty",[70,754,528],{"class":76},[70,756,757],{"class":166},"'--panel-header-height'",[70,759,26],{"class":76},[70,761,173],{"class":166},[70,763,182],{"class":76},[70,765,766],{"class":101}," ||",[70,768,769],{"class":105}," 72",[70,771,190],{"class":166},[70,773,686],{"class":76},[70,775,776,779,782],{"class":72,"line":252},[70,777,778],{"class":76},"}, { immediate: ",[70,780,781],{"class":105},"true",[70,783,131],{"class":76},[70,785,786,788,790],{"class":72,"line":262},[70,787,255],{"class":76},[70,789,81],{"class":80},[70,791,88],{"class":76},[11,793,794,795,798],{},"Każdy element pod nagłówkiem ma teraz tę samą klasę ",[23,796,797],{},"top-(--panel-header-height)",". Nie musi wiedzieć, czy pasek „Zaloguj jako” jest widoczny, ani ile nagłówek ma wysokości na danym ekranie.",[11,800,801],{},[37,802],{"alt":803,"height":804,"src":805,"width":806},"Nagłówek panelu – u góry przed zmianą (75 px), na dole po (72 px)",122,"\u002Fblog\u002F2026\u002Fnaglowki-agrestapp\u002Fpanel-header.webp",1440,[11,808,809],{},[37,810],{"alt":811,"height":812,"src":813,"width":358},"Panel na telefonie z otwartym menu bocznym – po lewej przed zmianą, po prawej po: sam znak logo i przycisk zamykania",560,"\u002Fblog\u002F2026\u002Fnaglowki-agrestapp\u002Fpanel-phone.webp",[15,815,817],{"id":816},"wartości-ze-skali-zamiast-pikseli-w-nawiasach","Wartości ze skali zamiast pikseli w nawiasach",[11,819,820],{},"Tailwind v4 przyjmuje każdą wielokrotność 0.25rem, więc większość wartości w nawiasach dało się zamienić na zwykłe klasy:",[822,823,824,837],"table",{},[825,826,827],"thead",{},[828,829,830,834],"tr",{},[831,832,833],"th",{},"Przed",[831,835,836],{},"Po",[838,839,840,854,867,879],"tbody",{},[828,841,842,848],{},[843,844,845],"td",{},[23,846,847],{},"h-[75px]",[843,849,850,853],{},[23,851,852],{},"h-18"," (72 px)",[828,855,856,861],{},[843,857,858],{},[23,859,860],{},"w-[239px]",[843,862,863,866],{},[23,864,865],{},"w-60"," (240 px)",[828,868,869,874],{},[843,870,871],{},[23,872,873],{},"py-[22px]",[843,875,876],{},[23,877,878],{},"py-5.5",[828,880,881,886],{},[843,882,883],{},[23,884,885],{},"dark:bg-[#161e30]",[843,887,888],{},"kolor z palety projektu",[11,890,891,892,895],{},"W pasku strony jeden odstęp wymagał płynnej wartości. Między logo, menu i przyciskami jest ",[23,893,894],{},"clamp()",", od 16 px przy szerokości 1024 px do 84 px przy 1440 px. Dzięki temu całe menu mieści się już od 1024 px, bez dodatkowych breakpointów.",[15,897,899],{"id":898},"co-z-tego-zostało","Co z tego zostało",[474,901,902,905,908,911,914],{},[477,903,904],{},"Jedna lista menu dla komputera i telefonu.",[477,906,907],{},"Jedna zasada rozmiaru (36 \u002F 44 px) zamiast kilku przypadkowych.",[477,909,910],{},"Jeden kolor ikon w spoczynku i pełna ikona dla aktywnej strony.",[477,912,913],{},"Jedna zmienna z wysokością nagłówka zamiast liczb 75 i 125 w kilku plikach.",[477,915,916],{},"Menu, które działa myszą, dotykiem i klawiaturą.",[11,918,919],{},"Rozwijane menu na stronie ma te same odstępy co menu konta w panelu. Obie części wyglądają teraz jak jeden system, choć są osobnymi aplikacjami.",[921,922,923],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sqY6-, html code.shiki .sqY6-{--shiki-default:#6A737D;--shiki-dark:#90A1B9}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html pre.shiki code .siNPP, html code.shiki .siNPP{--shiki-default:#CA3500;--shiki-dark:#FFAB70}",{"title":66,"searchDepth":91,"depth":91,"links":925},[926,927,928,929,930,931],{"id":17,"depth":91,"text":18},{"id":361,"depth":91,"text":362},{"id":468,"depth":91,"text":469},{"id":616,"depth":91,"text":617},{"id":816,"depth":91,"text":817},{"id":898,"depth":91,"text":899},"2026-10-17","Jak w jeden dzień przepisałem nagłówek strony i panelu AgrestApp – jedno przesuwane menu, rozmiary zależne od sposobu obsługi i wysokość nagłówka w zmiennej CSS zamiast liczb wpisanych w kilku plikach.",false,"md",null,"case-study",{},"\u002Fog\u002Fblog\u002Fnaglowki-agrestapp-pl.jpg","\u002Fpl\u002Fblog\u002F2026\u002Fnaglowki-agrestapp","\u002Fprojects\u002F2026\u002Fagrestapp",{"title":5,"description":933},"pl\u002Fblog\u002F2026\u002Fnaglowki-agrestapp",[945,65,946],"tailwind","accessibility","D2tVbnJBjFiVeSOHbAC9tyZJ5RzlUmKTDD80MaWzpWc",{"id":949,"title":950,"agency":951,"body":952,"compare":936,"cover":1015,"date":1020,"description":1021,"draft":934,"extension":935,"facts":1022,"featured":934,"gallery":1036,"galleryLead":1057,"highlights":1058,"image":1074,"links":1075,"meta":1077,"navigation":137,"note":1078,"ogImage":1079,"path":1080,"seo":1081,"stack":1082,"stem":1086,"tags":1087,"__hash__":1088},"projects_pl\u002Fpl\u002Fprojects\u002F2026\u002Fagrestapp\u002Findex.md","AgrestApp","Pesi",{"type":8,"value":953,"toc":1012},[954,957,961],[11,955,956],{},"AgrestApp to system zakupów dla firm z branży spożywczej: zamówienia i przetargi na surowce, monitoring cen, oferty dostawców i analityka oszczędności. Pracuję przy nim w zespole Pesi od października 2025. Front-end w Vue 3 i Inertia tworzy kilka osób, dlatego poniżej opisuję tylko części, przy których front-end jest mój. Najbardziej cieszy mnie to, że wiele rzeczy mogę tu napisać po swojemu. Serwis zmieniam małymi etapami: tu nowa tabela, tam nowy przycisk, gdzie indziej nagłówek. Każdy nowy element zastępuje kilka starszych wersji, więc z czasem w projekcie jest coraz mniej zbędnych komponentów.",[15,958,960],{"id":959},"co-zrobiłem","Co zrobiłem",[474,962,963,970,976,982,988,994,1000,1006],{},[477,964,965,969],{},[966,967,968],"strong",{},"Nagłówek panelu"," – nowy nagłówek z paskiem „Zaloguj jako”, menu konta i wspólnym komponentem rozwijanej listy",[477,971,972,975],{},[966,973,974],{},"Zestaw komponentów UI"," – przyciski, akordeon, podpowiedzi, pola liczbowe i 69 ikon jako komponenty Vue; przeniesienie przycisków aplikacji na jeden komponent i wspólny przełącznik motywu",[477,977,978,981],{},[966,979,980],{},"Strona firmowa"," – strona „O systemie”, zestaw sekcji, z których powstały strony dla piekarni, cukierni, lodziarni i HoReCa, kafelki cenowe i cennik z przełącznikiem płatności",[477,983,984,987],{},[966,985,986],{},"Analityka"," – główny wykres, wybór zakresu dat z trybem porównania, najlepsi dostawcy, podsumowanie oszczędności i rentowność produktów",[477,989,990,993],{},[966,991,992],{},"Zarządzanie ofertą dostawcy"," – rozbudowa istniejącego ekranu: okno ceny indywidualnej, dodawanie artykułów i przechodzenie Enterem między polami",[477,995,996,999],{},[966,997,998],{},"Zaznaczanie zakresu"," – Shift + klik w 9 listach panelu (surowce, produkty, ceny, powiadomienia i inne)",[477,1001,1002,1005],{},[966,1003,1004],{},"Sklep"," (dziś zamknięty) – koszyk z minimum logistycznym każdego dostawcy, zakup bez konta, historia zamówień i reklamacje",[477,1007,1008,1011],{},[966,1009,1010],{},"Migracja na Tailwind CSS v4"," całego projektu",{"title":66,"searchDepth":91,"depth":91,"links":1013},[1014],{"id":959,"depth":91,"text":960},{"desktop":1016,"phone":1017,"width":806,"height":1018,"alt":1019},"\u002Fprojects\u002F2026\u002Fagrestapp\u002Fhome-top.webp","\u002Fprojects\u002F2026\u002Fagrestapp\u002Fhome-phone-top.webp",900,"AgrestApp – pierwszy ekran strony głównej","2026-10-01","System zakupów surowców dla piekarni, cukierni, lodziarni i gastronomii – w zespole Pesi robię front-end: komponenty UI, stronę firmową, analitykę i zarządzanie ofertą dostawcy.",[1023,1027,1030,1033],{"value":1024,"suffix":1025,"text":1026},"12"," mies.","w projekcie – od października 2025",{"value":1028,"text":1029},"69","ikon jako komponenty Vue w nowym zestawie UI",{"value":1031,"text":1032},"4","strony branżowe – piekarnie, cukiernie, lodziarnie i HoReCa – zbudowane z tych samych sekcji",{"value":1034,"text":1035},"296","klas w szablonach bez zmian przy przejściu na Tailwind v4",[1037,1041,1045,1049,1053],{"src":1038,"width":806,"height":1018,"alt":1039,"caption":1040},"\u002Fprojects\u002F2026\u002Fagrestapp\u002Fanalytics-top.webp","Analityka w trybie porównania","Analityka – zakres dat z suwakiem i polami, porównanie dwóch okresów, oszczędności i wykres (zmyślone dane)",{"src":1042,"full":137,"alt":1043,"caption":1044},"\u002Fprojects\u002F2026\u002Fagrestapp\u002Fhome-full.webp","Strona główna AgrestApp","Strona główna – od pierwszego ekranu do newslettera",{"src":1046,"full":137,"alt":1047,"caption":1048},"\u002Fprojects\u002F2026\u002Fagrestapp\u002Fabout-system-full.webp","Strona „O systemie”","„O systemie” – sekcje z opisem modułów",{"src":1050,"full":137,"alt":1051,"caption":1052},"\u002Fprojects\u002F2026\u002Fagrestapp\u002Findustry-ice-cream-full.webp","Strona dla lodziarni","Strona branżowa dla lodziarni – zbudowana z tych samych sekcji co pozostałe",{"src":1054,"full":137,"alt":1055,"caption":1056},"\u002Fprojects\u002F2026\u002Fagrestapp\u002Findustry-confectionery-full.webp","Strona dla cukierni","Strona branżowa dla cukierni","Strona firmowa i panel aplikacji – analityka na zmyślonych danych. Kliknij zrzut, żeby go powiększyć.",[1059,1062,1065,1068,1071],{"title":1060,"text":1061},"Nagłówek panelu bez liczb na sztywno","Przebudowałem nagłówek panelu. Pasek „Zaloguj jako” jest teraz jego częścią, a nagłówek sam mierzy swoją wysokość i zapisuje ją w zmiennej CSS. Menu boczne i przyklejone paski stron korzystają z tej zmiennej zamiast liczb 75 i 125 px wpisanych w kilku plikach. Menu konta obsłużysz z klawiatury i zamkniesz klawiszem Esc.",{"title":1063,"text":1064},"Zakres dat w trzech miejscach naraz","W analityce suwak, pola dat i adres strony pokazują ten sam zakres. Zmiana w jednym miejscu od razu zmienia pozostałe, a dane odświeżają się dopiero chwilę po ostatniej zmianie. Kalendarz nie pozwala ustawić początku po końcu, a domyślny zakres zaczyna się w dniu rejestracji firmy.",{"title":1066,"text":1067},"Tabela cen jak arkusz","W zarządzaniu ofertą dostawcy Enter przenosi do tego samego pola w wierszu niżej, a Shift + Enter – wyżej. Zawartość pola jest od razu zaznaczona do nadpisania. W 9 listach panelu Shift z kliknięciem zaznacza cały zakres wierszy, jak w poczcie.",{"title":1069,"text":1070},"Cena indywidualna bez pomyłek","Okno ceny indywidualnej pilnuje minimalnej ceny netto, nie przyjmuje zera ani pustej wartości i podpowiada wartość domyślną dla pojedynczej pozycji. Cena podświetla się na zielono, gdy jest niższa lub równa najniższej cenie.",{"title":1072,"text":1073},"Tailwind v4 bez przepisywania szablonów","Zamiast zmieniać setki klas przywróciłem w konfiguracji trzy wartości z v3. Potem porównałem wynikowy CSS z poprzednią wersją, przejrzałem strony i poprawiłem regresje, których nie zgłosił build – m.in. okna, które po migracji chowały się pod własnym przyciemnieniem.","\u002Fprojects\u002F2026\u002Fagrestapp\u002Fthumbnail.webp",{"live":1076},"https:\u002F\u002Fagrestapp.pl",{},"Pracuję w zespole – opisuję tylko części, przy których front-end jest mój. Panel aplikacji nie jest publiczny.","\u002Fog\u002Fprojects\u002Fagrestapp-pl.jpg","\u002Fpl\u002Fprojects\u002F2026\u002Fagrestapp",{"title":950,"description":1021},[1083,1084,1085],"Vue 3","Inertia","Tailwind CSS v4","pl\u002Fprojects\u002F2026\u002Fagrestapp\u002Findex",[65],"9K9-hLbbaGH3H17k_QO2mHzbXXsCfmWEW5rHxmSri_M",1792397256932]