I Ripped Out a Carousel Library. CSS Replaced It.
The bug ticket said "carousel feels broken on trackpad." It took me forty minutes to find the actual...
找到 2952 篇相关文章
The bug ticket said "carousel feels broken on trackpad." It took me forty minutes to find the actual...
O contrato do cliente tá numa URL que qualquer um adivinha A tarefa parecia simples: o cliente precisa baixar a nota fiscal dele. Você salvou em storage/app/public/notas/ , rodou php artisan storage:link , mandou o link e foi feliz. https://app.com/storage/notas/nota-1042.pdf . Semanas depois cai a ficha. Aquele arquivo está aberto na internet . Sem login, sem nada. E o nome é sequencial: quem baixou a nota-1042.pdf só precisa de curiosidade e cinco segundos pra tentar a 1041 . E a 1040 . Então você faz a coisa certa: tira do disco público e cria um sistema pra controlar acesso. Tabela download_tokens , model, geração de UUID, coluna expires_at , controller que valida, e um comando no scheduler pra limpar os vencidos. Sessenta linhas depois, funciona. E aí alguém comenta no PR: "por que você não usou uma URL assinada?" O sistema que você não precisava construir // ❌ migration + model + controller + command. tudo isso pra um PDF. Schema :: create ( 'download_tokens' , function ( Blueprint $table ) { $table -> id (); $table -> uuid ( 'token' ) -> unique (); $table -> string ( 'path' ); $table -> foreignId ( 'user_id' ); $table -> timestamp ( 'expires_at' ); $table -> timestamps (); }); public function gerarLink ( NotaFiscal $nota ): string { $token = DownloadToken :: create ([ 'token' => Str :: uuid (), 'path' => $nota -> arquivo_path , 'user_id' => auth () -> id (), 'expires_at' => now () -> addMinutes ( 10 ), ]); return route ( 'download' , $token -> token ); } Não tem nada de errado tecnicamente. O problema é o custo: mais uma tabela crescendo pra sempre, mais um comando no scheduler, mais um caminho pra testar. E você vai manter isso enquanto o projeto existir. O Laravel resolve o mesmo problema com uma assinatura criptográfica na própria URL. Sem estado, sem tabela, sem limpeza. Como uma URL assinada funciona A ideia é bonita de simples: o Laravel monta a URL com os parâmetros que você quer, calcula um hash disso tudo usando a APP_KEY e cola o hash no final. /not
"Deu erro ao salvar, umas duas da tarde" É a única informação que você tem. O cliente não lembra o que clicou, não tirou print e já fechou a aba. Você abre o laravel.log . Quarenta mil linhas no dia. Faz um grep por "erro". Aparecem 1.200 ocorrências, e a maioria é isso: [2026-08-14 14:03:11] production.INFO: entrou [2026-08-14 14:03:11] production.INFO: erro aqui [2026-08-14 14:03:12] production.INFO: passou [2026-08-14 14:03:12] production.ERROR: Erro ao salvar Erro ao salvar o quê ? De qual usuário? Qual pedido? Qual valor? Aquele entrou da linha de cima é do mesmo request ou de outro cliente que estava usando o sistema no mesmo segundo? Você tem log. Você não tem informação. São coisas diferentes. O problema não é a falta de log. É o excesso de log inútil. public function emitir ( Pedido $pedido ) { Log :: info ( 'entrou no emitir' ); try { $nota = $this -> sefaz -> emitir ( $pedido ); Log :: info ( 'emitiu' ); } catch ( Throwable $e ) { // parabéns, você registrou que algo deu errado em algum lugar 🎉 Log :: error ( 'Erro ao emitir nota' ); return back () -> withErrors ( 'Falha na emissão' ); } } Repara no que esse catch jogou no lixo: a mensagem da exceção, o stack trace, o ID do pedido, o CNPJ, o retorno da SEFAZ. Tudo estava ali, na mão, e foi substituído por uma frase genérica. E os Log::info('entrou') espalhados? Aquilo foi debug que virou permanente. Hoje eles só servem pra empurrar as linhas úteis pra fora da tela. Duas perguntas que todo log precisa responder Um log serve pra duas plateias: você, com sono, às 3h da manhã — e uma máquina , filtrando milhões de linhas. As duas querem a mesma coisa: O que aconteceu , numa mensagem que não muda nunca. Com quem aconteceu , em dados separados da mensagem. Essa separação é o pulo do gato. Repare na diferença: // ❌ mensagem única pra cada pedido. impossível agrupar ou contar. Log :: error ( "Falha ao emitir nota do pedido { $pedido -> id } do cliente { $cliente -> nome } " ); // ✅ mensagem estável + contexto est
The Quest Begins (The "Why") I still remember the first time I tried to add login to a side‑project. I’d read a tutorial that said “just store a token in localStorage and you’re good,” slapped together a few fetch calls, and called it a day. A week later I got an email from a user: “Hey, I can’t log out, and someone else seems to be using my account.” My heart sank. I realized I’d bolted a flashy lock onto a screen door — it looked secure, but anyone with a screwdriver could walk right in. That moment kicked off a deep dive. I wanted to understand the trade‑offs between sessions , JSON Web Tokens (JWT) , and OAuth so I could pick the right tool for each job, not just the shiniest one. What followed felt like assembling a superhero squad: each member has a unique power, and knowing when to call on them makes the difference between saving the day and causing collateral damage. The Revelation (The Insight) Sessions – The Trusty Sidekick Sessions are the classic, server‑side approach. When a user logs in, the server creates a random identifier (the session ID), stores it in a database or cache (Redis, Memcached, etc.), and sends it back to the browser as an HttpOnly cookie. On every request, the browser automatically includes that cookie, the server looks up the ID, and pulls the associated user data. Why I love it: The secret never leaves the server, so stealing a cookie only gives an attacker a session ID that’s useless without the server’s store. Revoking a session is trivial — just delete the row from the store. Works great for traditional web apps where you control both front‑ and back‑end. Where it stumbles: Horizontal scaling requires a shared session store; otherwise each instance forgets who the user is. Every request does a database/lookup, which can add latency if the store isn’t fast enough. JWT – The Lone Wolf with a Signed Badge A JWT is a compact, URL‑safe string that contains claims (like sub , exp , roles ) and is cryptographically signed (HMAC or RSA).
Like many developers, designers, and digital marketers, my Instagram "Saved" collection had turned into a digital graveyard with over 5,000 bookmarked posts, reels, and carousels. The native Instagram web app offers virtually zero productivity tools: ❌ No full-text search across captions or hashtags ❌ No way to extract individual slides from carousel photo dumps ❌ No offline preservation (if a creator archives a post, it disappears forever) ❌ Existing web downloaders ask for account passwords, inject trackers, or bombard you with ads. So I spent the last few months developing Unlockt — a 100% free, MIT open-source, local-first Chromium extension and Node.js Express dashboard. --- ## 🏗️ Architecture & Engineering Highlights Here is how Unlockt is designed under the hood: ┌─────────────────────────────────┐ │ Chromium Extension (MV3) │ ──► Reads Instagram GraphQL via active session └────────────────┬────────────────┘ │ Local REST Sync ▼ ┌─────────────────────────────────┐ │ Express Backend (Port 3000) │ ──► SSRF-Hardened Proxy & HTTP 206 Video Streamer └────────────────┬────────────────┘ │ ┌────────┴────────┐ ▼ ▼ ┌──────────────┐ ┌───────────────────────────┐ │ data/saved. │ │ /thumbnails /videos │ │ json (DB) │ │ (Local High-DPI Storage) │ └──────────────┘ └───────────────────────────┘ 1. Zero-Password Session Scraping Rather than asking users for their credentials or running headless browser instances that trigger Meta account checkpoints, Unlockt operates as a Manifest V3 Chromium extension. It uses the cookies and CSRF tokens already present in your authenticated browser tab with randomized jitter delays (800ms - 2200ms) to respect rate limits. 2. 1-Click HTML5 Canvas Collage Studio One of my favorite features is the Carousel Studio . When you open a 10-slide photo dump, Unlockt extracts every slide and can render them onto an off-screen HTML5 <canvas> element to produce high-resolution moodboards ( 2x1 , 2x2 , 3x2 , 3x3 , and 5x2 ) with crisp 4px white margin div
The Day I Realized I Wasn't Building Apps For years, I thought I was building apps. That's what I called them anyway. A scheduler. A job bot. A healthcare platform. An AI project. A content tool. A browser automation system. Looking at my GitHub, they seem completely unrelated. Honestly, that's something I've worried about before. I have over a hundred repositories. If someone spends thirty seconds scrolling through them, I can imagine them thinking: "Wow. This person is all over the place." The funny thing is that I eventually realized the opposite was true. My GitHub is here: https://github.com/ashb4 The Scheduler That Wasn't A Scheduler One of my projects started life as a simple scheduler. That was the goal. I hated posting content manually. Open platform. Paste content. Upload image. Repeat. Again. And again. And again. It felt repetitive. It felt annoying. Most of all, it felt like something a computer should be doing instead of me. So I built a scheduler. At least, that's what I thought I was building. Then Things Got Weird The scheduler worked. But now I needed content. Then I needed analytics. Then I needed to know what content was working. Then I needed a way to track winners. Then I needed a way to reuse content. Then I needed platform-specific strategies. At some point I looked up and realized I wasn't building a scheduler anymore. I was building a system. A system for discovering, creating, publishing, measuring, and improving content. The scheduler was just one piece. Then I Started Looking At Everything Else That's when I noticed the same thing happening in almost every project I'd ever built. My job application tools weren't really job application tools. They were systems designed to reduce repetitive effort. My automation projects weren't really automation projects. They were systems designed to reduce repetitive effort. Even my AI projects weren't really about AI. They were systems designed to reduce repetitive effort. Different technologies. Diffe
Updating old codebases usually means hours of re-configuring environments, fixing broken dependencies, and hunting for lost secrets. In this walkthrough, we use Cursor IDE and the new Auth0 plugin to automatically resurrect a 2-year-old Python Flask application. Watch how AI seamlessly sets up the Auth0 CLI, generates environment variables, and configures our authentication tenant from scratch. What You'll Learn How to install and navigate the Auth0 plugin within Cursor IDE. Using AI prompts to automate Auth0 tenant creation and Flask secret key generation. Navigating the Auth0 CLI device authorization code flow inside an AI environment. Troubleshooting AI prompt timeouts and natively restarting development servers via Cursor. Resources & Links 🐙 GitHub Repo 💻 Auth0 Plugin in Cursor Marketplace 🔐 Auth0 Python/Flask Docs 📖 Auth0 CLI
ASP.NET Core Output Caching: How to Make Web APIs Faster in .NET When an API receives the same request repeatedly, performing the same database query and rebuilding the same response every time can waste valuable resources. For example, imagine this endpoint: GET /api/products If thousands of users request the same product catalog, your application might repeatedly: HTTP Request ↓ Controller ↓ Database Query ↓ Business Logic ↓ JSON Response For data that doesn't change frequently, this can create unnecessary database load. ASP.NET Core provides Output Caching to help solve this problem. Instead of executing the complete request pipeline every time, the application can temporarily store the generated response and reuse it for subsequent requests. In this tutorial, we'll look at how Output Caching works, how to configure it, how to invalidate cached responses, and when you should avoid using it. What Is Output Caching? Output caching stores the generated response from an endpoint. For example: First request ↓ GET /api/products ↓ Execute controller ↓ Query database ↓ Generate response ↓ Store response in cache Later: Second request ↓ GET /api/products ↓ Cached response ↓ Return immediately The database doesn't need to be queried again while the cached response is valid. Output Caching vs Response Caching These two concepts are often confused. Response Caching Response caching mainly relies on HTTP caching semantics and headers. Output Caching Output caching is controlled by ASP.NET Core and allows your application to decide which responses should be cached and for how long. Output caching provides more control over server-side response caching. 1. Add Output Caching Start by registering the output-cache services. var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddOutputCache(); var app = builder.Build(); app.UseOutputCache(); app.MapControllers(); app.Run(); The important pieces are: AddOutputCache() ↓ Configure cac
Shadcn, a design engineer at Vercel, has introduced new components for chat interfaces within the shadcn/ui project. This release includes components like MessageScroller and Message, focusing on conversation functionality. The approach emphasizes modular design, allowing developers to adapt elements without affecting underlying logic or styles. Support for headless components is also provided. By Daniel Curtis
One of the easiest security mistakes is assuming that a WordPress plugin is still trustworthy simply because it has been installed for a long time. The folder is familiar. The plugin name is familiar. WordPress still loads. But is the code on disk still the code you approved? That question became very real for me when MatrixSwarm reported an unexpected change inside a plugin directory on a production server. The alert did not claim that it had discovered malware. It said something more precise and defensible: This plugin no longer matches its trusted baseline. That integrity warning led to a manual investigation. Inside a forgotten WordPress test plugin, I found a PHP backdoor. The important part of this story is not that an automated agent magically understood the attacker’s intent. It did not. The important part is that it noticed a change that was easy for a person—and WordPress itself—to overlook. That incident shaped the design of MatrixSwarm’s WordPress Plugin Guard. The problem: familiarity is not integrity WordPress sites often accumulate history: plugins that are no longer actively maintained; test plugins that were never removed; emergency fixes applied directly on the server; auto-updates that legitimately replace files; abandoned folders that nobody remembers installing; writable PHP files inside a public web root. A traditional malware scanner looks for known suspicious patterns. That is valuable, but it answers a different question. Plugin Guard asks: Has anything inside this approved plugin changed since the operator trusted it? It does not need to recognize a specific web shell. It does not need a signature for a particular backdoor family. It detects the loss of integrity first, then gives the operator evidence and control. How the baseline works When an operator approves a plugin, Plugin Guard walks the plugin directory and computes a SHA-256 digest for every file. It stores those relative paths and hashes as the plugin’s trusted manifest. A simpli
I'm building EverQuill, an AI-powered tabletop RPG platform — a game where an AI acts as your dungeon master, narrating the story and reacting to what you do. Every turn of that story is a message to a language model, and every message costs money. That cost isn't a problem you get to solve later, once you're big. It's a set of choices you make before anyone shows up, because the defaults you pick decide whether a bad day costs you a few cents or a few hundred dollars. Here are three decisions I made before I opened the alpha, and why. 1. When the tier is unclear, I reach for the stronger model This is tier-based routing : which model serves a request depends on who's asking. Better models write better stories and cost more; cheaper ones are faster and rougher. So free players get Claude Haiku (cheaper, ~$0.80 per million input tokens) and paying players get Claude Sonnet (stronger, ~$3.00 per million) — plus, because the platform is designed to route across providers, the same decision could hand a turn to a local model running through Ollama instead of a hosted one. That part is straightforward. The interesting case is the ambiguous one. My routing hands over the cheaper model only when a request is clearly free-tier; everything else — paid, alpha testers, admin, anything the system can't cleanly place as free — gets the stronger model. The instinct most people have is the reverse: default to cheap, protect the margin, don't spend money you don't have to. The reasoning: a rough, disappointing experience for someone who should have gotten a good one is a real cost — it just doesn't show up on the bill. It shows up as someone deciding the product isn't worth paying for. Meanwhile the money risk is capped elsewhere (more on that below), so biasing toward quality can't run away from me. Given a bounded downside on money and an unbounded downside on reputation, I'd rather err toward quality than toward cheap. 2. I stopped paying full price for the part that never chang
A browser preview can look correct while the downloaded image is wrong. The usual failure is timing: CSS eventually applies the custom font to the preview, but Canvas draws once. If the font is not ready at that exact moment, fillText() can silently use a fallback face. The user sees one design and downloads another. I ran into this while building GraffForge, a browser-based graffiti text tool. The free editor compares the same user-entered word across multiple bundled styles, then exports the selected result as a transparent 2048 × 2048 PNG. That gave the export path a clear contract: preserve the exact text; use the selected font; keep spacing, outline, shadow, and skew; fit inside a safe area; preserve real transparency; never upload the user's text or image. Here is the approach that made the output deterministic. 1. Treat export as a separate rendering target Do not enlarge the preview DOM and take a screenshot. Create a fresh Canvas with explicit bitmap dimensions: const EXPORT_SIZE = 2048 ; const canvas = document . createElement ( ' canvas ' ); canvas . width = EXPORT_SIZE ; canvas . height = EXPORT_SIZE ; const context = canvas . getContext ( ' 2d ' ); if ( ! context ) { throw new Error ( ' Canvas rendering is unavailable. ' ); } The width and height attributes define the actual PNG pixel dimensions. CSS sizing and devicePixelRatio are useful for an on-screen preview, but neither should determine the export contract. A fixed bitmap size also makes automated verification straightforward. 2. Load the font before measuring anything Canvas does not redraw automatically when a font finishes loading. Load the exact family, weight, size, and text before calling measureText() : await document . fonts . load ( `400 160px " ${ fontFamily } "` , text ); Passing the actual text is useful because the browser can confirm that the required glyphs are available. After this point, set the Canvas font explicitly: context . font = `400 ${ fontSize } px " ${ fontFamily } "` ;
This is a submission for Frontend Challenge - Comfort Food Edition, Perfect Landing What I Built Warm Hearth — a landing page for a comfort food restaurant built around one idea: everything on the menu comes from the same wood-fired hearth in the back. Instead of treating "comfort food restaurant" as a generic brief, I anchored the whole page to that single hearth: An interactive hearth centerpiece. Right after the hero, there's a hand-drawn CSS/SVG fire pit you can click to "stoke." The flame flares, embers burst upward, and a small honest counter tracks how many times you've stoked it this visit — no fake global numbers, just a real, session-based response to your click. Four dishes, each with real cultural identity. Ramen, warm pies, a cheesy pasta bake, and gulab jamun — each with its own hand-drawn SVG illustration and a border motif pulled from its own cuisine (a jade-and-gold double line for the ramen, a scalloped pastry edge for the pies, an Italian tricolor accent for the pasta, gold paisley tones for the gulab jamun) rather than one generic card style stretched across all four. Living detail, not static photos. Steam rises off the ramen, pies, and pasta bake using the same wisp animation as the hero's hearth, so the whole page reads as one consistent "warmth" language. The gulab jamun gets a syrup shimmer and drip instead, since steam isn't the right detail for a syrup-soaked sweet. Price tags that hang like real kitchen tickets — pinned by a string, swaying gently, and giving a small "flicked" swing on hover instead of sitting flat on the card. Mira, an illustrated host in the corner who offers a rotating table tip when you click her — a small personal touch instead of a static "contact us" widget. Built for actual use, not just to look good in a screenshot: keyboard-focusable tab filters, a skip-to-content link, aria-live regions on the interactive parts, and full prefers-reduced-motion support that disables every animation without breaking the page. Dem
I run a small site that converts bank CSV exports into the file format QuickBooks Desktop accepts. The whole thing runs client-side, and the privacy claim it makes is unusually literal: every page ships a Content Security Policy with connect-src 'none' , so the browser refuses to let the page make any network request at all. Open the Network tab while you convert a file and it stays empty. That's the feature, not a nice-to-have on top of it. When I added Excel support last week, the obvious choice was SheetJS. I decided against it, and the reason wasn't bundle size. The claim I want to be able to make is "your file never leaves the browser, and here is the policy that enforces it." Pulling in a large third-party parser turns that into "…and also trust this dependency," which is a materially weaker claim for a tool that handles people's bank statements. So I wanted to find out how much of the format I actually needed. Less than I expected. An .xlsx file is a ZIP archive containing XML: xl/workbook.xml lists the sheets, xl/worksheets/sheet1.xml holds the cells, xl/sharedStrings.xml is a deduplicated string pool that cells reference by index, and xl/styles.xml carries the number formats. Reading that needs three capabilities, and the browser already provides two of them. Unzipping means walking the ZIP central directory, which is about forty lines. Decompression is DecompressionStream('deflate-raw') , which is native. For the XML I hand-rolled a tag scanner rather than reaching for DOMParser , because my tests run in Node where DOMParser doesn't exist, and I would rather have one code path than two. Dates The part that took the most care was dates, because Excel doesn't store them as dates. A cell holding 5 January 2024 contains the number 45296 , and whether that number should be displayed as a date depends on the cell's number format — which lives in a different file inside the archive. So the parser has to read styles.xml , work out which style indexes correspond to
Why Is Overdraw Particularly Important in WebGL? When optimizing a Unity WebGL application, developers often focus on polygon count, draw calls, texture size, and shader complexity. But there is another performance problem that can quietly become expensive: UI overdraw . A UI may look simple to the player while the GPU is actually rendering the same pixels several times. A typical game interface might contain: A full-screen background Transparent panels Multiple decorative images Buttons Shadows Icons Text Semi-transparent overlays Popups Individually, these elements may seem harmless. When stacked together, however, they can significantly increase the number of pixels the GPU needs to process. Unity identifies overlapping transparent UI, sprites, and particles as common contributors to overdraw. So, is UI overdraw really killing your WebGL performance? Let's understand when it matters and how to reduce it. What Exactly Is UI Overdraw? Overdraw occurs when the same screen pixel is rendered multiple times during a frame. Imagine a 1920×1080 WebGL game with this UI: Full-screen background ↓ Transparent dark overlay ↓ Semi-transparent panel ↓ Panel decoration ↓ Button background ↓ Button icon ↓ Button text A pixel covered by all these elements may be processed repeatedly. Conceptually: GPU ↓ ┌──────────────┐ │ Background │ ← 1 │ Overlay │ ← 2 │ Panel │ ← 3 │ Decoration │ ← 4 │ Button │ ← 5 │ Icon │ ← 6 │ Text │ ← 7 └──────────────┘ Same pixel processed multiple times This is particularly relevant when your UI contains large transparent elements covering substantial portions of the screen. Unity's graphics documentation specifically recommends identifying and reducing overdraw when fill rate becomes a GPU limitation. Why Is Overdraw Particularly Important in WebGL? WebGL runs inside a browser, so performance has additional constraints compared with a native desktop application. If your application is GPU-bound, rendering unnecessary pixels means the GPU has more work to
When building a Polymarket bot, the first version can be surprisingly small: market data ↓ strategy ↓ order That's enough to demonstrate an idea. It isn't enough to prove that the idea works. Once you care about realistic execution, the architecture becomes more interesting. Market Data ↓ Data Validation ↓ Signal Engine ↓ Risk Engine ↓ Execution Engine ↓ Trade Events ↓ Analytics This separation is what allows me to test the strategy independently from the infrastructure. 1. Don't backtest the API call One mistake I see in trading-bot development is mixing the strategy with execution. For example: if ( signal ) { await placeOrder (); } This is convenient for a prototype. But how do you test the strategy without sending an order? Instead: const signal = strategy . evaluate ( marketState ); const decision = riskEngine . check ( signal , portfolio ); if ( decision . allowed ) { await executionEngine . submit ( signal ); } Now each component can be tested independently. 2. Model execution separately A backtest shouldn't assume: signal price === fill price Instead, the execution simulator should model things such as: signal price spread slippage available liquidity fees latency Then: expected PnL ↓ execution model ↓ realistic PnL estimate The difference can be substantial. Polymarket's CLOB exposes order-book data and executable prices, making the order book an important part of any execution-aware strategy. 3. Separate in-sample and out-of-sample data Don't optimize and evaluate on the same dataset. A simple structure: Dataset ├── Train └── Test The strategy is developed using Train . Parameters are frozen. Then Test is used only for evaluation. For time-series trading, I prefer chronological splits rather than random shuffling: Past ───────────────────────> Future [ Training ][ Validation ][ Test ] This better represents the actual information flow of a trading system. 4. Measure more than win rate Win rate is useful, but insufficient. I want to measure: trades wins los
How to Host a Website Using GitLab Pages If you have a website made with HTML and CSS, you can host it for free using GitLab Pages . GitLab Pages takes the files from your GitLab repository and publishes them as a website. For this, you need to create a .gitlab-ci.yml file. This file tells GitLab how to deploy your website. After pushing the file to your repository, GitLab creates a pipeline. When the pipeline finishes successfully, GitLab Pages gives you a URL which you can open in a browser to see your live website. Understanding the Pipeline A pipeline is the process GitLab uses to run the instructions written in .gitlab-ci.yml . If the pipeline fails, the website will not be deployed correctly. Sometimes the pipeline can fail because of an invalid YAML file, incorrect indentation, or a problem in the deployment commands. Another common problem is trying to create a public folder when the folder already exists. For a simple HTML and CSS website, the important thing is that the public folder contains your website files and index.html should be directly inside it. For example, the structure should look like this: public/ ├── index.html ├── style.css └── images/ The index.html file is important because it is the main page GitLab Pages looks for when someone opens the website. Hosting More Than One Website You can host multiple websites using GitLab Pages, but if the websites are completely different projects, it is better to create a separate GitLab project for each website . For example, you can have one project called youtube-clone and another project called portfolio . Each project can have its own HTML, CSS, .gitlab-ci.yml , pipeline and Pages deployment. This makes the projects easier to manage and prevents one website from affecting another website. So, GitLab is not only a place to store your code. With GitLab Pages and CI/CD pipelines, you can also use it to turn your HTML and CSS project into a live website that can be accessed through the internet.
This is a submission for Weekend Challenge: Dog Days Edition I built Caninography, a small digital archive for exploring dog breeds from around the world. I wanted it to feel more like a digital museum than a normal dog website. You can explore breeds, their origins, history, countries, characteristics and connections between them. The whole design is dark, clean and visual. I also kept it silent, so there is no distracting audio or player UI. Live Demo: canonigraphy.vercel.app GitHub: https://github.com/maisamabbas0323/canonigraphy.git How I Built It I built it with React, Vite and TypeScript. For the visual side, I focused a lot on typography, photography, smooth transitions and responsive layouts. I also added an interactive world atlas and a constellation-style view to explore breed relationships. For the content, I used Google Gemini to help create short and interesting breed information. I didn't wanted to make another chatbot. Instead, Gemini stays behind the experience and helps make the archive content more rich while people simply explore it. Prize Categories Best Use of Google AI Caninography is submitted for Best Use of Google AI. I used Google Gemini to generate concise, breed-specific information based on the archive data. The idea was to use AI in the background, not put a chatbot in front of the user. Built With React Vite TypeScript Google Gemini CSS SVG Canvas A Little About The Idea I always felt dog breed information is mostly shown as simple lists. So I thought: What if a dog archive felt like a museum? That small idea became Caninography. A place to explore their stories, origins and history — one breed at a time.
Run the same salary through three different paycheck calculators and you'll get three different answers. None of them explain why. That bothered me enough to spend three weeks building an alternative. The result is payculate.org — a paycheck calculator for all 50 US states and DC where every deduction line opens up and shows its own arithmetic . The interesting problem wasn't the code The tax math itself is straightforward: progressive brackets are a loop, FICA is two multiplications with a cap. I had a working federal calculator in an afternoon. The hard part was that every state is a special case , and a generic model breaks on most of them: Wisconsin has a standard deduction that shrinks as you earn more — it starts at $13,230 and falls by 12 cents per dollar above a threshold, reaching zero around $126,000. Alabama lets you deduct your entire federal income tax before calculating state tax. The more federal tax you pay, the less Alabama income you have. Utah looks flat at 4.5%, but gives a taxpayer credit that phases out with income — so the effective rate climbs while the headline rate never moves. Ohio taxes nothing on the first $26,050, then a flat 2.75%. South Carolina rewrote its entire income tax in March 2026: six brackets became two (1.99% / 5.21%), and the federal standard deduction was replaced by a state-specific deduction that phases out above $40,000 of AGI. That last one I only caught during a routine data check last week. Most calculators I checked are still showing the old six-bracket system. The lines nobody counts The bigger discovery was what national calculators leave out entirely: employee-paid state payroll premiums . Washington charges no income tax at all. But Paid Family & Medical Leave (0.807%) and WA Cares (0.58%, uncapped) still take about $1,040 a year from a $75,000 salary . Most tools show $0 on that line. California's SDI lost its wage cap in 2024 and now takes 1.3% of every dollar — on a $200,000 salary that's $2,600 that appears
I live on the Taghazout coast in Morocco - a strip of Atlantic between Agadir and Imsouane that's basically one long right-hand point break after another. Two years ago the only way to know if tomorrow was worth it was to check three different global forecast sites, none of which knew the difference between Anchor Point and the beach break 400m south of it. So I built taghazout.io . It now covers 20 named breaks, runs in 10 languages, and costs me nothing per month. Here's how it's actually put together - including the parts I'd do differently. The stack is deliberately boring Hand-rolled PHP. No framework, no build step, no node_modules. About 4,800 files, server-rendered, no hydration. That sounds like a confession, but it was the right call for one reason: my readers are on phones, on cafe Wi-Fi, often on 3G. A server-rendered page that ships HTML and a little CSS beats anything I could have built with a client-side framework in that environment. Time-to-content is the only metric that matters when someone is standing on the beach deciding whether to paddle out. The hosting is a cheap shared plan. The forecast data is free and open. The whole thing runs at EUR 0/month recurring , which was a hard constraint from day one. The interesting part: two ocean models that disagree The forecast blends two sources: Open-Meteo (CC BY 4.0) - the primary, with a marine endpoint that covers our coastal cells. NOAA WaveWatch III via PacIOOS - the second opinion. Here's the thing nobody tells you: they disagree, a lot. On the same hour at the same break I've seen WaveWatch read ~55% higher than Open-Meteo (1.36m vs 0.88m). Offshore models resolve coastal bathymetry badly, and our points are exactly the kind of close-in, shallow-reef setups where that bias shows up. The wrong fix is to pick one and pretend. What I did instead: Run both, cache both. Compute agreement over a 72-hour window - a Pearson correlation on the swell rhythm plus a circular difference on direction (you can'