MoreRSS

site iconThe Practical DeveloperModify

A constructive and inclusive social network for software developers.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of The Practical Developer

🧛👻 BloodBound Academy: How I Built a Haunting AI Study Tool in ~7 Hrs Using Kiro's Spec-Driven Magic

2025-12-02 01:00:14

The Challenge

It's 2 AM. You're staring at your syllabus, trying to create study aids for your study sessions. Manually transcribing? Tedious. Organizing topics? Time-consuming. Creating comprehensive lesson plans? Exhausting.

What if AI could do all of this... wrapped in a haunting vampire aesthetic?

That's what I built. But here's the real story: I went from rough idea to production deployment in <7 hrs using Kiro's spec-driven development.

Let me show you exactly how.

The ~7-Hour Timeline (No BS)

Hour 0:00  → Brain dump ideas (messy, unstructured)
            ↓
Hour 0:40  → Kiro generates complete specs in 5 MINUTES! 🤯
            ↓
Hour 1:00  → Review & refine specs to match my vision
            • Adjust requirements.md for my specific needs
            • Tweak design.md architecture decisions
            • Finalize tasks.md execution plan
            ↓
Hour 4:00  → Click through 15 tasks → Working MVP ✅
            • From empty folder to functional app
            • Systematic task execution
            • All tests passing
            ↓
Hour 7:00  → Vibe code enhancements 🎨
            • Audio system (14 effects)
            • Enhanced animations (blood trails, spiders, bats)
            • Multiple themes (gothic, vampire)
            • Component polish & rendering improvements
            ↓
Hour 7:30  → Refine specs to match final code 📚
            • Kiro analyzes all files
            • Updates specs = living documentation
            ↓
Hour 7:45  → Deploy to Netlify 🚀
            • Git workflow setup
            • Push to production
            • Live on the internet!

Result: A fully functional, production-ready app with:

  • 🤖 AI-powered lesson generation from syllabus images
  • 🧛‍♂️ Immersive vampire theme with 14 audio effects
  • 📜 Ancient scroll viewer with flickering candles
  • 🎨 20+ CSS animations and atmospheric effects
  • ✅ Property-based testing with 100+ iterations
  • � Living documentation that matches the code

🎯 The Two Game-Changing Moments

Moment #1: Specs in 5 Minutes

I gave Kiro this messy brain dump:

A site with a landing page, and another page where user can
- Upload syllabus images
- AI extracts course structure
- Users pick topics
- Chose between :- Overview, In-depth explanation, or Key Takeaways of the Topic chosen
- Generate study aids based on topic, unit, content type
- Ancient scroll display with candles
- Blood trails, screen cracks, flying bats
- Thunder, lightning, gothic fonts
- Audio effects everywhere
- Multiple themes
- Retro horror game vibes

One prompt later:

"Create comprehensive specs for this project using spec-driven development."

5 minutes. THREE complete files:

📄 requirements.md - 12 requirements, 60+ EARS-compliant acceptance criteria

📄 design.md - Complete architecture, 13 correctness properties, testing strategy

📄 tasks.md - 15 major tasks, 50+ executable subtasks

This wasn't documentation. This was a complete blueprint for building the entire application.

What would have taken me a couple hrs of planning took less than 10 mins.

Moment #2: Click-to-Execute Tasks

Here's where my mind exploded.

In the tasks.md file, I could click on each task to execute it:

- [ ] 2. Implement image upload and validation
  - [ ] 2.1 Create UploadZone component
  - [ ] 2.2 Implement file validation
  - [ ] 2.3 Write property test for file size

Click task 2.1 → Kiro generates complete UploadZone component with drag-and-drop

Click task 2.2 → Kiro implements validation logic with error handling

Click task 2.3 → Kiro writes property-based test with fast-check

Each task built on the previous one. Zero context loss. No "what was I doing again?" moments.

This could be the future of software development.

My journey: Hour by Hour

Hour 0: The Messy Beginning (40 mins)

I opened a document and brain-dumped everything:

  • User flow: Upload → Extract → Select → Generate → View → Export
  • Pages: Dramatic landing page, Generator with upload zone
  • UI: Flickering candles, blood trails, screen cracks, flying bats
  • Features: OCR extraction, AI lesson generation, multiple exports
  • Vibes: Retro, spooky, haunting, Halloween, vampire, gothic

No structure. No implementation plan. Just ideas and vibes.

Hour 0.67: The 5-Minute Miracle

I fed my mess to Kiro:

"Create comprehensive specs for this project using spec-driven development. Generate requirements.md, design.md, and tasks.md files."

5 minutes later:

12 detailed requirements with 60+ acceptance criteria

Complete system architecture with component interfaces

13 correctness properties for property-based testing

15 major tasks broken into 50+ actionable subtasks

Testing strategy with fast-check integration

Security considerations and error handling

I had a complete blueprint for building a production application.

Hours 1-4: Click, Click, Click (3-4 hours)

Starting with an empty folder, I systematically clicked through tasks:

✅ Task 1: Initialize Next.js (~10 min)

✅ Task 2: Upload & validation (~20 min)

✅ Task 3: Text extraction engine (~25 min)

✅ Task 4: AI integration (~30 min)

✅ Task 5: Checkpoint - tests pass ✓ (~5 min)

✅ Task 6: Scroll viewer component (~35 min)

✅ Task 7: Atmospheric effects (~40 min)

✅ Task 8: Export functionality (~20 min)

✅ Task 9: Accessibility (~15 min)

✅ Task 10: Security (~15 min)

✅ Task 11: Application flow (~30 min)

✅ Task 12: Checkpoint - tests pass ✓ (~5 min)

✅ Task 13: Polish UI (~25 min)

✅ Task 14: Deployment config (~10 min)

✅ Task 15: Final checkpoint ✓ (~5 min)

Result: Working MVP in about 4 hrs.

Every component integrated perfectly because Kiro had full context from the specs.

Hours 4-7: Vibe Coding Magic (2-3 hours)

With a working MVP, I switched to creative mode:

Audio System (1 hour):

Me: "Add multiple sound effects with contextual triggers. Act as a veteran professional designer cum dev with an expertise in retro, spooky, halloween, haunting sites & special effects."
Kiro: *generates AudioManager with heartbeat, thunder, whispers*

Me: "Make heartbeat intensify during AI processing. Add more sound effects and handle the audio system for dramatization, more haunting/spooky vibes. I want different & yet overlapping sounds for different actions on the site."
Kiro: *implements dynamic volume control*

Me: "Add vampire ambience that loops"
Kiro: *creates VampireAudio component*

Enhanced Animations (45 min):

Me: "Add crawling spiders on screen edges"
Kiro: *creates CSS keyframes with random delays*

Me: "Blood trails that fade smoothly"
Kiro: *implements trail system with cleanup*

Me: "Screen crack on every click"
Kiro: *generates crack overlay with glass-break sound*

Me: "I want the animations to be realistic, use better css. Act as a veteran designer with an expertise in these sites."

Gothic Styling (30 min):

Me: "Use authentic medieval fonts"
Kiro: *imports Cinzel, Crimson Text, Special Elite*

Me: "Text glitch effects that self-correct"
Kiro: *creates glitch-text CSS with data attributes*

Me: "Darker, more blood-red palette"
Kiro: *updates entire theme system*

Multiple Themes (45 min):

Me: "Vampire theme with blood moon and bats"
Kiro: *generates complete theme with unique effects*

Me: "Theme switching system"
Kiro: *implements useTheme hook with persistence*

The secret? Kiro had access to my complete specs. Every enhancement integrated seamlessly with the existing architecture.

Hour 7: Closing the Loop (30 minutes):

After all the enhancements, my code had evolved beyond the original specs.

So I asked Kiro:

"Analyze all files in the project and refine the specs to match the final implementation"

Kiro scanned every component, hook, and utility, then:

  • ✅ Updated requirements.md with new features
  • ✅ Refined design.md with actual architecture
  • ✅ Adjusted tasks.md to reflect what was built

Now my specs are living documentation - they perfectly describe the production system.

Hour 7.25: Git Workflow & Production Deployment (45 minutes):

Setting Up Git (10 minutes):

git init
git add .gitignore
git commit -m "🧛‍♂️ Initial setup - project structure"

Committing Features Incrementally:

git add components/UploadZone.tsx components/SyllabusViewer.tsx
git commit -m "✨ Add upload and syllabus viewer components"

git add components/AncientScrollViewer.tsx
git commit -m "📜 Add ancient scroll viewer with candles"
.
.
.

git add .kiro/specs/
git commit -m "📚 Add Kiro specs (requirements, design, tasks)"

git push origin main

Deployment to Netlify (15 minutes):

  1. Connected GitHub repo to Netlify
  2. Configured build settings:
    • Build command: npm run build
    • Publish directory: .next
  3. Added environment variables (GEMINI_API_KEY)
  4. Triggered first deployment

Testing & Verification (20 minutes):

This was crucial. I tested everything:

Upload Flow:

  • Drag & drop works?
  • File validation catches invalid files?
  • Error messages display correctly?

AI Processing:

  • Syllabus extraction working?
  • Topic selection modal appears?
  • Lesson plan generation completes?
  • Loading states show properly?

Scroll Viewer:

  • Content renders correctly?
  • Page navigation works?
  • Candles flicker?
  • Markdown formatting preserved?

Audio System:

  • All 14 sound effects load?
  • Contextual triggers work?
  • Volume levels appropriate?
  • Mute/unmute functions?

Atmospheric Effects:

  • Blood trails follow cursor?
  • Screen cracks on click?
  • Spiders crawl correctly?
  • Lightning flashes sync with thunder?

Theme Switching:

  • Vampire theme loads all effects?
  • Theme persistence works?
  • Color palettes apply correctly?

Export Functionality:

  • Markdown export works?
  • TXT export works?
  • Filenames correct?

Found and Fixed Issues:

  • Image upload size validation needed adjustment → Fixed in validation.ts
  • Theme switching caused brief flash → Added transition smoothing
  • Export filename had special characters → Sanitized filenames

Final Verification:

  • Tested on Chrome, Firefox, Safari
  • Tested on desktop
  • Verified all API routes working
  • Confirmed no console errors

🎨 What I Actually Built

BloodBound Academy

Upload a syllabus image, AI extracts the course structure, select a topic, get a comprehensive lesson plan displayed on an ancient scroll with flickering candles, blood trails, crawling spiders, and 14 atmospheric audio effects.

User Experience:

  1. Upload a syllabus image (drag & drop)
  2. AI extracts course structure with Gemini Vision
  3. Select a topic from the extracted units
  4. AI generates comprehensive lesson plan
  5. View on ancient scroll with flickering candles
  6. Export as Markdown or TXT

🧪 The Property-Based Testing Revelation

Traditional tests: "Does this specific input work?"

Property-based tests: "Does this work for ALL possible inputs?"

Using fast-check, I wrote tests that ran 100+ iterations with random inputs:

/**
 * Feature: bloodbound-academy, Property 4: Chunk division completeness
 * Validates: Requirements 4.1
 */
it('should preserve complete text when chunking and merging', () => {
  fc.assert(
    fc.property(
      fc.string({ minLength: 0, maxLength: 100000 }),
      fc.integer({ min: 100, max: 50000 }),
      (text, chunkSize) => {
        const chunks = chunkText(text, chunkSize);
        const merged = mergeChunks(chunks);
        expect(merged).toBe(text);
      }
    ),
    { numRuns: 100 }
  );
});

This found bugs I would NEVER have caught manually:

  • Unicode surrogate pair handling
  • Chunk boundary edge cases
  • Overlap region special characters
  • Empty string handling

8 property-based tests. 100+ iterations each. Comprehensive coverage.

📊🔢 The Numbers

Development Metrics

  • 5 minutes: Specs generation (would take 3-4 hours manually)
  • 3-4 hours: MVP from empty folder (would take 2-3 days)
  • 7 hours total: Idea to production (would take 2-3 weeks)

Implementation Stats

  • 15 major tasks with 50+ subtasks executed systematically
  • 12 requirements with 60+ acceptance criteria
  • 13 correctness properties defined and tested
  • 8 property-based tests with 100+ iterations each
  • 14 audio effects perfectly synchronized
  • 20+ CSS animations for atmospheric effects
  • 100% spec coverage - every requirement implemented

Code Quality

  • Full TS type safety
  • Property-based testing
  • No persistent data storage (privacy-first)
  • Retry logic with exponential backoff
  • CORS and security headers
  • Accessible (WCAG AA compliant)

🎯 Why This Workflow Changes Everything

Before Kiro

Day 1: Plan architecture (3-4 hrs)

Day 2: Set up project, start coding (6-8 hrs)

Day 3: Build core features (8 hrs)

Day 4: Add UI polish (8 hrs)

Day 5: Testing and bug fixes (3-4 hrs)

Day 6: Deployment and docs (2-3 hrs)

Total: 30-35 hours over 4-5 days

Constant context switching. Forgetting edge cases. Refactoring nightmares. Documentation as an afterthought.

With Kiro

Hour 0-1: Brain dump + Specs generation

Hour 1-4: Click through tasks → MVP

Hour 4-7: Vibe code enhancements

Hour 7-8: Refine specs + Deploy

Total: 7-8 hrs in one day

Crystal clear roadmap. Systematic progress. Comprehensive testing. Living documentation.

That's 4-5x faster. And better quality.

🏆 The Three Superpowers

1. Specs Generation (5 mins)

requirements.md

design.md

tasks.md

Transform messy ideas into:

  • Structured requirements (EARS-compliant)
  • Complete architecture (components, APIs, data models)
  • Correctness properties (property-based testing)
  • Executable task list (50+ subtasks)

Saves hours of planning. Prevents implementation mistakes.

2. Click-to-Execute Tasks

The tasks.md file is an interactive implementation guide:

  • Click a task → Kiro executes with full context
  • No copy-pasting
  • No context loss
  • No "what was I building?"

This is revolutionary.

3. Living Documentation

kiro- project struncture

Specs refinement

After vibe coding, refine specs to match reality:

  • Analyze all files
  • Update requirements
  • Refine design
  • Adjust tasks

Perfect documentation that matches production code.

💡 Key Insights

Insight #1: Specs First, Code Second

Starting with specs means:

  • ✅ Clear requirements before writing code
  • ✅ Defined correctness properties
  • ✅ Systematic implementation plan
  • ✅ Comprehensive test coverage
  • ✅ Living documentation

You know exactly what to build before you build it.

Insight #2: Vibe Coding + Specs = Magic

The combination is powerful:

  • Specs provide structure and correctness
  • Vibe coding provides creative freedom
  • Specs refinement closes the loop

Fast development without sacrificing quality.

Insight #3: Property-Based Testing is Essential

Testing with random inputs finds bugs you'd never imagine:

  • Edge cases
  • Boundary conditions
  • Unicode handling
  • Empty states

8 tests with 100+ iterations each = comprehensive coverage.

🎃 The Costume Contest Angle

Haunting User Interface ✓

  • Blood moon with pulsing glow
  • Interactive blood trails
  • Screen crack effects with sound
  • Crawling spiders on edges
  • Flying vampire bats
  • Lightning flashes with thunder
  • Flickering candles
  • Gothic typography

Immersive Audio ✓

14 contextual sound effects:

  • Vampire ambience (looping)
  • Heartbeat during processing
  • Thunder with lightning
  • Glass break on clicks
  • Door creaks on navigation
  • Random whispers, footsteps, drips

Polished Design ✓

  • Multiple themes (Gothic, Vampire)
  • Smooth animations (20+ CSS keyframes)
  • Ancient scroll viewer
  • Ornamental decorations
  • Text glitch effects
  • Bleeding text animations

Every detail crafted for the best possible haunting, spooky, retro themed user experience.

🤖⚡ The Bottom Line

I built a production-ready application in 7 hours.

Not a prototype. Not a demo. A fully functional, deployed, tested application with:

  • Comprehensive requirements
  • Solid architecture
  • Property-based testing
  • Living documentation
  • Production deployment

This is the future of software development.

The workflow:

Brain Dump (40 min)
    ↓
Kiro Specs (5 min) ← GAME CHANGER
    ↓
Click Tasks (3-4 hrs) ← REVOLUTIONARY
    ↓
Vibe Code (2-3 hrs) ← CREATIVE FREEDOM
    ↓
Refine Specs (30 min) ← LIVING DOCS
    ↓
Deploy (15 min)
    ↓
Production! (7 hrs total)

🎮 Experience the Dark Magic Yourself

🔗 Live Demo: bloodbound-academy.netlify.app

📱 Source Code:

GitHub logo Divya4879 / SpecterScript

A haunted PDF-to-lesson-plan generator for the Kiroween hackathon's Costume Contest category.

🧛‍♂️ BloodBound Academy

Next.js TypeScript TailwindCSS Gemini AI API

Transform your ordinary image coursework into magical study aids with immersive vampire-themed UI and spine-chilling interactive effects.

Check it out here:- https://bloodbound-academy.netlify.app

bloodbound acad

🎃 Overview

BloodBound Academy is a Halloween-themed web application built for the Kiroween Hackathon (Costume Contest category) that combines educational technology with horror aesthetics. Upload image files of syllabi or course materials, and watch as OCR extracts the text while AI transforms it into comprehensive lesson plans surrounded by atmospheric vampire effects, interactive blood trails, and haunting audio.

🏆 Hackathon Category: Costume Contest

  • Haunting User Interface: Polished vampire theme with dripping blood effects, crawling spiders, and atmospheric elements
  • Interactive Horror Effects: Blood cursor trails, screen cracks, lightning flashes, and phantom notifications
  • Immersive Audio: 14 layered horror sound effects including vampire ambience, heartbeats, and thunder
  • Functional Application: Real Image Content Extraction with AI-powered study guides generation

✨ Features

🧛‍♂️ Vampire Theme Interface

🎥 Project Video Demo:

📸 Glimpses from the Shadows

Where cursed documents meet AI magic

Landing Page

The Haunted Scroll

Content extracted from syllabus

Content topic choices for the topic chosen

Study Aid created

Study Aid download options

🧛♂️ Dare to Enter?

Upload a syllabus image. Select a topic. Watch as ancient AI spirits summon comprehensive lesson plans from the very essence
of darkness.

But beware...
Don't blame me if you hear whispers echoing through your study sessions... or if shadows seem to move in your peripheral vision... or if you catch yourself humming haunting melodies that weren't there before... or if you feel an inexplicable presence watching over your shoulder, even in an empty room...

Some knowledge comes with a price. Are you willing to pay it?👻🌙

Ready to summon your study materials from the abyss? The blood moon awaits your courage... 🩸

How to Add Feature Flags to Your App in 5 Minutes

2025-12-02 00:55:35

Feature flags are one of those things that seem optional until you need to roll back a broken feature at 2am on a Saturday.

Instead of reverting commits, redeploying, and praying — you just flip a toggle. Feature gone. Crisis over.

Here's how to add feature flags to your app in about 5 minutes.

What We're Building
A simple setup where you can:

  • Toggle features on/off instantly
  • Roll out features to a percentage of users
  • Use different settings per environment (dev/staging/prod)

Step 1: Get Your API Key
For this tutorial, I'm using SetBit — a feature flag service I built because I got tired of LaunchDarkly's complexity and pricing. Free tier works fine for this.

Sign up, create a project, and grab your SDK key from Account → API Keys.

Step 2: Install the SDK

npm install @setbit/js

Or if you're using Python:

pip install setbit

Step 3: Initialize

import { SetBit } from '@setbit/js';

const setbit = new SetBit({
  apiKey: 'your-sdk-key',
  environment: 'production'
});

await setbit.initialize();

Step 4: Create Your First Flag
In the SetBit dashboard, click + Create Flag and set up:

Name: new-checkout-flow
Type: Boolean
Default: false

Step 5: Use It In Your Code

if (setbit.isEnabled('new-checkout-flow')) {
  return <NewCheckout />;
} else {
  return <OldCheckout />;
}

That's it. Now you can toggle new-checkout-flow on or off from your dashboard — no redeployment needed.

Going Further: Percentage Rollouts

Want to test that new checkout with just 10% of users first?

Change your flag type to Rollout and set the percentage. SetBit handles the consistent bucketing so the same user always gets the same experience.

// Same code — rollout logic is handled server-side
if (setbit.isEnabled('new-checkout-flow', { userId: user.id })) {
  return <NewCheckout />;
}

When to Use Feature Flags

  • Risky deployments — Ship the code, enable the feature later
  • A/B testing — Test variations with real users
  • Kill switches — Instantly disable a broken feature
  • Gradual rollouts — 10% → 50% → 100%
  • Beta features — Enable only for specific users

The Real Win
The peace of mind. Ship on Friday. If something breaks, flip a toggle from your phone. No laptop required.

I built SetBit for developers who want feature flags without the enterprise complexity or pricing. Free tier available if you want to try it. Feel free to contact me if you have any questions or feedback, I really appreciate it!

  • Jason

Building a Robust Bonus Engine in Go: Mastering Accrual, Wager, and Compliance

2025-12-02 00:35:04

Building a Robust Bonus Engine in Go: Mastering Accrual, Wager, and Compliance

Бонуси — це хліб з маслом у багатьох онлайн-бізнесах, від ігрових платформ до e-commerce. Вони приваблюють нових клієнтів і утримують існуючих. Але за привабливою оболонкою бонусних пропозицій ховається складна логіка, яка потребує точної і надійної реалізації. Неправильне нарахування або облік бонусів може призвести до фінансових втрат, проблем з регуляторами або незадоволення клієнтів.

У цій статті ми зануримося у світ бонусних двигунів (Bonus Engines), розберемо ключові компоненти їх архітектури та розглянемо, як мова програмування Go може допомогти нам створити ефективне, масштабоване та відмовостійке рішення.

1. Типи бонусів: Різноманіття мотивації

Перед тим як говорити про реалізацію, давайте окреслимо основні типи бонусів, з якими нам доведеться працювати. Кожен тип має свою специфіку і вимагає гнучкої системи обліку.

  • Deposit Bonus (Бонус на депозит): Найпоширеніший тип. Гравець отримує відсоток від свого депозиту як бонусні кошти. Наприклад, "100% бонус до 100$" означає, що при депозиті в 100$ гравець отримує ще 100$ бонусних коштів.
  • Freespins (Безкоштовні обертання): Зазвичай використовується в ігрових автоматах. Гравець отримує певну кількість обертань на конкретному слоті без використання власних коштів. Виграші з фріспінів часто нараховуються як бонусні кошти, що підлягають відіграшу.
  • Cashback (Кешбек): Повернення частини програних коштів або відсотків від загальної суми ставок за певний період. Це може бути прямий кеш (реальні кошти) або бонусний кеш (з вейджер-вимогами).
  • Rakeback (Рейкбек): Поширений у покері та деяких інших карткових іграх. Повернення відсотка від рейку (комісії, що стягується з банку кожної роздачі) гравцеві.

Go-підхід: У Go ми могли б моделювати різні типи бонусів за допомогою інтерфейсів та структур.

package bonusengine

import "time"

type BonusType string

const (
    DepositBonusType  BonusType = "deposit_bonus"
    FreespinsBonusType BonusType = "freespins"
    CashbackBonusType BonusType = "cashback"
    RakebackBonusType BonusType = "rakeback"
)

type Bonus struct {
    ID         string
    UserID     string
    Type       BonusType
    Amount     float64 // Розмір бонусу
    Currency   string
    IssuedAt   time.Time
    ExpiresAt  time.Time
    Status     BonusStatus
    Wager      WagerDetails // Деталі по відіграшу
    Meta       map[string]interface{} // Для специфічних даних (напр., id гри для фріспінів)
}

type BonusStatus string

const (
    Pending  BonusStatus = "pending"
    Active   BonusStatus = "active"
    Wagering BonusStatus = "wagering"
    Completed BonusStatus = "completed"
    Cancelled BonusStatus = "cancelled"
    Expired  BonusStatus = "expired"
)

2. Wager Requirements Calculation (Розрахунок вимог до відіграшу)

Саме тут починаються справжні складнощі. Wager (вейджер) — це вимога до гравця зробити ставки на певну суму, перш ніж бонусні кошти та виграші від них стануть доступними для виведення.

  • Формула: Загальна сума ставок = Розмір Бонусу * Коефіцієнт Вейджера. Наприклад, бонус 100$ з вейджером x30 означає, що гравець повинен зробити ставки на 3000$ (100 * 30).
  • Прогрес відіграшу: Система повинна постійно відстежувати, скільки гравець вже відіграв і скільки ще залишилося.

2.1. Game Contribution до Wager

Не всі ігри однаково сприяють відіграшу. Це критичний аспект, який захищає оператора від швидкого "відмивання" бонусів.

  • Slots (Ігрові автомати): Зазвичай 100% внеску. Ставка в 1$ повністю йде в залік вейджера.
  • Blackjack, Roulette, Baccarat (Настільні ігри): Зазвичай 10-20% внеску. Ставка в 1$ може зараховуватися як 0.10$ або 0.20$ до вейджера через меншу маржу казино і можливість "безпечних" ставок.
  • Live Casino, Sports Betting: Можуть мати власні, часто менші відсотки або складніші правила внеску.

Go-підхід: Для розрахунку прогресу відіграшу нам потрібна функція, яка враховуватиме тип гри та її внесок.

package bonusengine

// GameContributionRates визначає відсоток внеску кожної гри до вейджера
var GameContributionRates = map[string]float64{
    "slot_game":    1.0,  // 100%
    "blackjack":    0.1,  // 10%
    "roulette":     0.2,  // 20%
    "baccarat":     0.15, // 15%
    "live_poker":   0.05, // 5%
    "sport_betting": 0.5, // 50%
}

type WagerDetails struct {
    RequiredAmount float64
    CurrentProgress float64
    WagerMultiplier float64
}

// CalculateWagerProgress оновлює прогрес відіграшу бонусу
func (b *Bonus) CalculateWagerProgress(betAmount float64, gameType string) {
    if b.Status != Wagering {
        return // Бонус не знаходиться в стані відіграшу
    }

    contributionRate, exists := GameContributionRates[gameType]
    if !exists {
        contributionRate = 0.0 // Якщо тип гри не визначено, внесок 0%
    }

    effectiveContribution := betAmount * contributionRate
    b.Wager.CurrentProgress += effectiveContribution

    if b.Wager.CurrentProgress >= b.Wager.RequiredAmount {
        b.Status = Completed
        b.Wager.CurrentProgress = b.Wager.RequiredAmount // Запобігаємо переповненню
    }
}

Ми також повинні бути уважними до конкурентного доступу до b.Wager.CurrentProgress, якщо обробляємо ставки паралельно. Використання каналів або sync.Mutex буде доречним.

3. Bonus Wallet vs Real Wallet (Бонусний гаманець проти реального гаманця)

Це фундаментальне розрізнення у бонусному двигуні. Кошти гравця повинні бути чітко розділені.

  • Real Wallet (Реальний гаманець): Містить кошти, які гравець може вивести в будь-який момент. Вони не підлягають вейджер-вимогам.
  • Bonus Wallet (Бонусний гаманець): Містить бонусні кошти та виграші, отримані за рахунок бонусних коштів. Ці кошти заблоковані для виведення до повного відіграшу вейджера.

Правила використання:

  1. При розміщенні ставки, система спочатку використовує кошти з реального гаманця.
  2. Якщо реальних коштів недостатньо, або якщо це дозволено правилами бонусу, використовуються кошти з бонусного гаманця.
  3. Виграші від ставок, зроблених за реальні кошти, йдуть на реальний гаманець.
  4. Виграші від ставок, зроблених за бонусні кошти, йдуть на бонусний гаманець.
  5. Після повного відіграшу вейджера, кошти з бонусного гаманця переводяться на реальний гаманець.

Go-підхід: Структура UserWallet повинна чітко розділяти баланси.

package bonusengine

import "sync"

type UserWallet struct {
    UserID        string
    RealFunds     float64
    BonusFunds    float64
    ActiveBonuses []string // ID активних бонусів
    mu            sync.Mutex // Для забезпечення потокобезпечності
}

// DebitFunds намагається списати кошти, спочатку з реального, потім з бонусного
func (uw *UserWallet) DebitFunds(amount float64) (debitedReal float64, debitedBonus float64, err error) {
    uw.mu.Lock()
    defer uw.mu.Unlock()

    if uw.RealFunds >= amount {
        uw.RealFunds -= amount
        debitedReal = amount
        return debitedReal, 0, nil
    }

    remainingAmount := amount - uw.RealFunds
    debitedReal = uw.RealFunds
    uw.RealFunds = 0

    if uw.BonusFunds >= remainingAmount {
        uw.BonusFunds -= remainingAmount
        debitedBonus = remainingAmount
        return debitedReal, debitedBonus, nil
    }

    return 0, 0, ErrInsufficientFunds // Припустимо, що ErrInsufficientFunds визначено
}

// CreditFunds додає кошти, залежно від джерела (чи була ставка зроблена за бонусні кошти)
func (uw *UserWallet) CreditFunds(amount float64, fromBonusStake bool) {
    uw.mu.Lock()
    defer uw.mu.Unlock()

    if fromBonusStake {
        uw.BonusFunds += amount
    } else {
        uw.RealFunds += amount
    }
}

// WithdrawBonus переводить бонусні кошти на реальний гаманець після відіграшу
func (uw *UserWallet) WithdrawBonus() {
    uw.mu.Lock()
    defer uw.mu.Unlock()

    uw.RealFunds += uw.BonusFunds
    uw.BonusFunds = 0
}

4. Bonus Expiration та Auto-Cancel

Бонуси не можуть бути вічними. Вони часто мають термін дії, після якого вони автоматично анулюються, якщо не були відіграні. Це запобігає "зависанню" бонусів і спрощує облік.

  • Expiration: Бонус має поле ExpiresAt (тип time.Time у Go).
  • Auto-Cancel: Система повинна мати механізм для регулярної перевірки та анулювання прострочених бонусів. Це може бути фоновий Go-routine, cron-job або event-driven підхід.

Go-підхід: Горутини та канали чудово підходять для створення фонових "прибиральників".

package bonusengine

// BonusExpirationService відповідає за обробку прострочених бонусів
type BonusExpirationService struct {
    bonusRepo BonusRepository // Інтерфейс для взаємодії з сховищем бонусів
    walletService WalletService // Інтерфейс для взаємодії з гаманцями
    stopCh      chan struct{}
}

func NewBonusExpirationService(repo BonusRepository, ws WalletService) *BonusExpirationService {
    return &BonusExpirationService{
        bonusRepo:   repo,
        walletService: ws,
        stopCh:      make(chan struct{}),
    }
}

func (s *BonusExpirationService) Start(interval time.Duration) {
    ticker := time.NewTicker(interval)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            s.CheckAndExpireBonuses()
        case <-s.stopCh:
            return
        }
    }
}

func (s *BonusExpirationService) Stop() {
    close(s.stopCh)
}

func (s *BonusExpirationService) CheckAndExpireBonuses() {
    // Отримати всі активні бонуси, які прострочені
    expiredBonuses, err := s.bonusRepo.GetExpiredActiveBonuses(time.Now())
    if err != nil {
        // Логуємо помилку
        return
    }

    for _, bonus := range expiredBonuses {
        if bonus.Status == Active || bonus.Status == Wagering {
            // Анулювати бонус
            bonus.Status = Expired
            bonus.BonusFunds = 0 // Обнуляємо бонусні кошти
            s.bonusRepo.UpdateBonus(bonus) // Оновлюємо статус у сховищі
            s.walletService.RemoveBonusFundsFromWallet(bonus.UserID, bonus.Amount) // Списати кошти з гаманця
            // Логувати подію анулювання
        }
    }
}

5. Bonus Abuse Detection (Виявлення зловживань бонусами)

Це одна з найскладніших частин. Шахраї постійно шукають способи обійти правила. Детекція зловживань є ключовою для захисту прибутковості.

  • Multi-accounting: Створення кількох облікових записів для отримання одного і того ж бонусу багато разів.
  • Collusion: Змова між гравцями для маніпуляції ігровим процесом або бонусними умовами.
  • Exploiting system bugs: Використання вразливостей системи для отримання незаслужених бонусів.
  • Abuse of max bet rules: Розміщення ставок, що перевищують максимально дозволену суму під час відіграшу бонусу.

Go-підхід:

  • Моніторинг поведінки користувачів: IP-адреси, унікальні пристрої, моделі ставок.
  • Системи правил (Rule Engine): Завдання чітких правил, які тригерять підозрілі події.
  • Машинне навчання: Для складніших патернів аномалій.
  • Для Multi-accounting можна використовувати аналіз IP-адрес, email-адрес, номерів телефонів, даних пристроїв (device fingerprinting).

6. Max Bet Restrictions (Обмеження максимальної ставки)

Для бонусів з вейджером часто діє обмеження на максимальну ставку. Це не дозволяє гравцям швидко відіграти бонус, зробивши одну дуже велику ставку.

  • Приклад: Якщо максимальна ставка з активним бонусом становить 5$, то будь-яка ставка понад 5$ не буде врахована до вейджера або може призвести до анулювання бонусу.

Go-підхід: Перевірка максимальної ставки має відбуватися при кожній спробі розміщення ставки.

package bonusengine

// CheckMaxBet перевіряє, чи не перевищує ставка максимально дозволену для активного бонусу
func (b *Bonus) CheckMaxBet(betAmount float64) error {
    if b.Status == Active || b.Status == Wagering {
        if maxBet, ok := b.Meta["max_bet"].(float64); ok {
            if betAmount > maxBet {
                return ErrMaxBetExceeded // Припустимо, що ErrMaxBetExceeded визначено
            }
        }
    }
    return nil
}

7. Audit Trail для Compliance (Аудиторський слід для відповідності)

У фінансових та ігрових індустріях суворе регулювання. Кожна дія, пов'язана з бонусами та коштами, повинна бути зафіксована. Це необхідно для:

  • Регуляторної відповідності: Демонстрація чесності та прозорості.
  • Вирішення спорів: Чітка історія транзакцій для розслідування скарг гравців.
  • Внутрішнього контролю: Моніторинг та аналіз роботи бонусної системи.

Що фіксувати:

  • Видача бонусу (коли, кому, який бонус, сума).
  • Зміна статусу бонусу (активація, відіграш, завершення, анулювання, прострочення).
  • Кожне оновлення прогресу вейджера (сума ставки, гра, внесок до вейджера).
  • Рух коштів між бонусним та реальним гаманцями.

Go-підхід: Використання структурованих логів (наприклад, з пакетами logrus або zap) та окремої таблиці/сервісу для аудиту.

package bonusengine

import (
    "encoding/json"
    "log" // або logrus/zap
    "time"
)

type AuditLogEntry struct {
    Timestamp  time.Time              `json:"timestamp"`
    UserID     string                 `json:"user_id"`
    EventType  string                 `json:"event_type"` // e.g., "BONUS_ISSUED", "WAGER_PROGRESS_UPDATE", "BONUS_EXPIRED"
    EntityID   string                 `json:"entity_id"`  // ID бонусу, ID транзакції
    Details    map[string]interface{} `json:"details"`    // Специфічні дані події
}

// LogBonusEvent фіксує важливі події, пов'язані з бонусами
func LogBonusEvent(userID, eventType, entityID string, details map[string]interface{}) {
    entry := AuditLogEntry{
        Timestamp:  time.Now(),
        UserID:     userID,
        EventType:  eventType,
        EntityID:   entityID,
        Details:    details,
    }

    // Серіалізація в JSON для збереження або відправки в лог-систему
    logBytes, err := json.Marshal(entry)
    if err != nil {
        log.Printf("Error marshalling audit log entry: %v", err)
        return
    }

    // У реальному додатку це може бути відправка в Kafka, базу даних, або централізовану систему логів
    log.Printf("AUDIT: %s", string(logBytes))
}

// Приклад використання:
// LogBonusEvent(bonus.UserID, "BONUS_ISSUED", bonus.ID, map[string]interface{}{
//     "bonus_type": bonus.Type,
//     "amount": bonus.Amount,
//     "wager_multiplier": bonus.Wager.WagerMultiplier,
// })
// LogBonusEvent(bonus.UserID, "WAGER_PROGRESS_UPDATE", bonus.ID, map[string]interface{}{
//     "bet_amount": betAmount,
//     "game_type": gameType,
//     "old_progress": oldProgress,
//     "new_progress": bonus.Wager.CurrentProgress,
// })

Go's Role in Building a Robust Engine

Go чудово підходить для розробки бонусного двигуна завдяки своїм основним перевагам:

  • Concurrency (Паралелізм): Go-рутини та канали дозволяють легко обробляти велику кількість одночасних запитів, виконувати фонові завдання (наприклад, перевірка прострочених бонусів) без блокування основного потоку. Це критично для систем з високим навантаженням.
  • Performance (Продуктивність): Go скомпільований, що забезпечує низьку затримку та високу пропускну здатність, що є важливим для фінансових транзакцій.
  • Strong Typing (Сильна типізація): Забезпечує чіткість даних та знижує ймовірність помилок, що є надзвичайно важливим у системах, що працюють з грошима.
  • Modularity (Модульність): Чистий дизайн Go, акцент на інтерфейсах, дозволяє легко розділяти компоненти (наприклад, BonusService, WalletService, AuditService), роблячи систему легшою для розуміння, тестування та підтримки.
  • Error Handling (Обробка помилок): Явна обробка помилок у Go сприяє написанню більш надійного коду, де кожен потенційний збій враховується.
  • Testing (Тестування): Простота написання юніт- та інтеграційних тестів у Go допомагає забезпечити коректність складної бонусної логіки.

Висновок

Побудова бонусного двигуна в Go — це захоплюючий, але складний проект. Він вимагає глибокого розуміння бізнес-логіки, уваги до деталей у розрахунках, ретельної архітектури для управління станами та коштами, а також потужних механізмів для виявлення зловживань та забезпечення відповідності.

Використовуючи потужні функції Go, такі як паралелізм, сильна типізація та модульність, ми можемо створити систему, яка є не тільки ефективною та масштабованою, але й надзвичайно надійною та безпечною. Пам'ятайте, що правильне нарахування бонусів — це не просто функціонал, це запорука довіри клієнтів та фінансової стабільності вашого бізнесу.

Що ви вважаєте найскладнішим аспектом у розробці бонусного двигуна? Поділіться своїми думками в коментарях!

Tags

go golang backend microservices fintech gaming softwarearchitecture bonusengine devto programming engineering

Building in Public as a High School Founder: Fushi

2025-12-02 00:31:38

I’m a high school student currently juggling homework, AP classes, exams, extracurriculars, a social life, and building a SaaS startup from scratch. This is the story of how I spent months building a calendar app that ultimately flopped—but led me to a genuinely useful product: Fushi.

The First Attempt: My Failed Calendar App

Like a lot of beginner founders, I built something that I thought was cool.

I created a calendar app with a unique UI, focus session features and detailed analytics that made sense to my brain. I spent months coding, designing, planning, polishing—then finally deployed and started marketing.

I posted it to Reddit for feedback and got roasted.

Not because it was buggy.
Not because it was ugly.
But because it wasn’t actually useful for other people.

It didn’t solve a pain point.
It didn’t improve someone’s workflow.
It was basically “my personal taste in calendar form.”

I had built a product for myself—not a product for a market.

What I Learned

  • Don’t validate after building. Validate first.
  • A product that solves a real frustration will outperform a polished product that solves none.
  • User feedback > assumptions
  • If your potential users need to adapt to your system, it’s probably wrong. Their current workflow should feed into your product, not the other way around.

The Next Idea: Fushi

After recovering from thinking about how horrifying that went and the hours "wasted", I assembled a group of my friends and spent the next month doing market research: asking friends, talking to people who struggle with scheduling and assignment management, going on the internet and browsing comments, etc.

The pain became obvious:

  • Some students have packed daily schedules and too much homework
  • Some constantly forget assignments even though they don’t feel disorganized
  • Some procrastinate until due dates crush them
  • Existing tools either treat tasks like events, require too much manual effort, or are too rigid

That led to Fushi: a productivity tool for students who have too much to do and no clear system for working through it.

What Fushi does:

  • Fetches assignments from school platforms automatically
  • Asks for minimal input: time needed + priority
  • Generates an optimized order for your day
  • Helps you focus on one thing at a time
  • Keeps work from slipping through the cracks

This isn’t just another calendar.
It’s a decision-assistance webapp for overwhelmed students.

Current Progress

I’ve just begun building Fushi. The dashboard is in early development. Here’s a look at some current UI progress:

Fushi Dashboard

(More images will be included as the build continues.)

Tech Stack

  • Vite (React)
  • Supabase backend
  • Google Cloud authentication + Calendar scope integration

Building in Public

I’ll be documenting how development goes as I continue building Fushi:

  • early tech stack choices
  • UI/UX decisions
  • onboarding design
  • database considerations
  • scaling strategies
  • marketing and testing with real users

If you're interested in productivity, student tooling, startup building, or you want to follow this journey as it unfolds, keep an eye on my upcoming posts—I’ll be publishing at least weekly.

And if you want to contact me directly, discuss the product, give feedback, or test the MVP, feel free to email me:

[email protected]

Thanks for reading. More to come soon.

🎨 CSS Opacity: The Simplest Way to Control Transparency on the Web

2025-12-02 00:30:06

Originally Published - Makemychance.com
CSS opacity helps you control how transparent an element appears on your webpage. Whether you're designing hover effects, overlays, or smooth UI transitions, opacity is one of the easiest and most useful CSS properties to master.

🔍 What Is CSS Opacity?

Opacity defines how visible or transparent an element is.
It accepts a value between 0 and 1:

  • 1 = fully visible
  • 0 = fully transparent
  • 0.5 = 50% transparent

✅ Basic Example

.box {
  opacity: 0.5;
}

🖱️ Opacity on Hover (Popular UI Effect)

img:hover {
  opacity: 0.7;
  transition: 0.3s ease;
}

This creates smooth hover effects widely used in modern UI/UX design.

🎭 Opacity vs RGBA (Important Difference)

Opacity affects the entire element, including children.
If you want transparency only for background, use RGBA:

background-color: rgba(0, 0, 0, 0.5);

🎒 Use Cases of Opacity

  • Image hover effects
  • Modal & overlay backgrounds
  • Smooth text fade-in/out
  • Transparent buttons
  • Highlighting elements

📌 External Helpful Resources

🚀 Final Thoughts

Opacity is simple but powerful. With just a single property, you can elevate the feel of your UI, create depth, and build attention-grabbing hover effects.