nice clean recipes pear.dunkirk.sh
recipes
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: mobile slight refactor

+91 -4
+66
.github/workflows/deploy.yml
··· 1 + name: Deploy 2 + 3 + on: 4 + push: 5 + branches: [main] 6 + workflow_dispatch: 7 + 8 + concurrency: 9 + group: deploy 10 + cancel-in-progress: false 11 + 12 + jobs: 13 + deploy: 14 + name: pear → terebithia 15 + runs-on: ubuntu-latest 16 + environment: 17 + name: production 18 + url: https://pear.dunkirk.sh 19 + 20 + steps: 21 + - uses: actions/checkout@v4 22 + 23 + - name: Set up Go 24 + uses: actions/setup-go@v5 25 + with: 26 + go-version-file: go.mod 27 + 28 + - name: Build 29 + run: GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags "-X main.gitHash=$(git rev-parse --short HEAD)" -o pear . 30 + 31 + - name: Setup Tailscale 32 + uses: tailscale/github-action@v4 33 + with: 34 + oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} 35 + oauth-secret: ${{ secrets.TS_OAUTH_SECRET }} 36 + tags: tag:ci 37 + use-cache: "true" 38 + 39 + - name: Configure SSH 40 + run: | 41 + mkdir -p ~/.ssh 42 + echo "StrictHostKeyChecking accept-new" >> ~/.ssh/config 43 + 44 + - name: Deploy 45 + run: | 46 + scp pear pear@terebithia:~/app/pear-new 47 + ssh pear@terebithia << 'DEPLOY' 48 + set -e 49 + cd ~/app 50 + chmod +x pear-new 51 + mv -f pear-new pear 52 + sudo /run/current-system/sw/bin/systemctl restart pear.service 53 + DEPLOY 54 + 55 + - name: Health check 56 + run: | 57 + for i in $(seq 1 12); do 58 + if curl -sf -o /dev/null "https://pear.dunkirk.sh"; then 59 + echo "Healthy" 60 + exit 0 61 + fi 62 + echo "Attempt $i/12 failed" 63 + sleep 5 64 + done 65 + echo "Health check failed" 66 + exit 1
+20 -4
ui/static/style.css
··· 40 40 margin:0 auto; 41 41 padding:1.25rem 1.5rem; 42 42 width:100%; 43 + box-sizing:border-box; 43 44 } 44 45 nav a.wordmark{ 45 46 font-family:'Poppins',system-ui,sans-serif; ··· 56 57 margin:0 auto; 57 58 padding:0 1.5rem 2rem; 58 59 flex:1; 60 + width:100%; 59 61 } 60 62 61 63 footer{ 62 64 display:flex; 63 65 justify-content:space-between; 64 66 align-items:center; 65 - padding:1.25rem 2rem; 67 + padding:1.25rem 1.5rem; 66 68 color:var(--text-muted); 67 69 font-size:0.8rem; 68 70 font-family:'Poppins',system-ui,sans-serif; 69 71 border-top:1px solid var(--border); 70 72 margin-top:auto; 73 + width:100%; 74 + box-sizing:border-box; 71 75 } 72 76 footer a{color:var(--text-muted)} 73 77 footer a:hover{color:var(--accent)} ··· 155 159 156 160 .recipe-header{margin-bottom:1.5rem} 157 161 .recipe-header h2{font-size:2rem;line-height:1.3;letter-spacing:0;font-weight:600;margin-bottom:0.35rem} 158 - .recipe-meta{color:var(--text-muted);font-size:0.9rem} 162 + .recipe-meta{color:var(--text-muted);font-size:0.9rem;max-width:100%;overflow-wrap:break-word} 159 163 .recipe-meta span{margin-right:1rem} 160 164 .description{margin-top:0.5rem;color:var(--text-muted);font-size:0.95rem} 161 165 ··· 287 291 color:var(--text-muted); 288 292 transition:background 0.15s,color 0.15s,border-color 0.15s; 289 293 } 290 - .timer-btn:hover{border-color:var(--accent);background:var(--accent);color:#fff} 294 + .timer-running #timer-toggle{ 295 + border-color:var(--accent); 296 + background:var(--accent); 297 + color:#fff; 298 + } 291 299 .timer-btn:active{transform:scale(0.96)} 292 300 293 301 .tmr{font-weight:500;cursor:pointer;transition:color 0.2s;white-space:nowrap} ··· 431 439 nav{padding:1rem} 432 440 footer{padding:1rem} 433 441 .hero h1{font-size:1.8rem} 434 - .hero{padding:2.5rem 0 2rem} 442 + .hero{padding:1.5rem 0 0.5rem} 435 443 .search-form form{flex-direction:column} 444 + .search-form button{width:100%} 445 + .recipe-header h2{font-size:1.4rem} 446 + .recipe-meta{font-size:0.8rem} 447 + .recipe-meta span{margin-right:0.5rem} 448 + .recipe-image{max-height:250px} 449 + .recent-grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))} 436 450 .actions{flex-direction:column} 437 451 .actions a,.actions button{width:100%;justify-content:center} 452 + .timer-display{font-size:2.25rem} 453 + .instruction-list li{padding-left:1.5rem} 438 454 } 439 455 440 456 .cook-header{margin-bottom:1rem}
+5
ui/templates/recipe.html
··· 162 162 if (timerRunning) return; 163 163 timerRunning = true; 164 164 document.getElementById('timer-toggle').textContent = 'Pause'; 165 + document.getElementById('timer-widget').classList.add('timer-running'); 165 166 timerInterval = setInterval(() => { 166 167 timerRemaining--; 167 168 updateTimerDisplay(); ··· 175 176 timerRunning = false; 176 177 document.getElementById('timer-toggle').textContent = 'Start'; 177 178 document.getElementById('timer-display').classList.add('timer-done'); 179 + document.getElementById('timer-widget').classList.remove('timer-running'); 178 180 if ('Notification' in window && Notification.permission === 'granted') { 179 181 new Notification('Timer done!', { body: document.getElementById('timer-label').textContent + ' is done!' }); 180 182 } ··· 187 189 clearInterval(timerInterval); 188 190 timerInterval = null; 189 191 document.getElementById('timer-toggle').textContent = 'Unpause'; 192 + document.getElementById('timer-widget').classList.remove('timer-running'); 190 193 } 191 194 192 195 function resetTimer() { ··· 204 207 } 205 208 document.getElementById('timer-toggle').textContent = 'Start'; 206 209 document.getElementById('timer-display').classList.remove('timer-done'); 210 + document.getElementById('timer-widget').classList.remove('timer-running'); 207 211 updateTimerDisplay(); 208 212 } 209 213 ··· 215 219 if (timerSound) { timerSound.pause(); timerSound.currentTime = 0; } 216 220 document.getElementById('timer-widget').classList.remove('timer-open'); 217 221 document.getElementById('timer-display').classList.remove('timer-done'); 222 + document.getElementById('timer-widget').classList.remove('timer-running'); 218 223 timerRemaining = 0; 219 224 updateTimerDisplay(); 220 225 }