✨ The Prompt Phrase
Build a role-based access control system for a web application: define roles as named collections of permissions, where each permission is a combination of a resource type and an allowed action such as invoice:read or invoice:write. Assign roles to users. On each request, resolve the requesting user's permissions from their assigned roles and evaluate whether the requested resource and action are permitted. Support role inheritance so a senior role includes all permissions of a junior role automatically.

💻 Code Preview

📦 All-in-One Code
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>RBAC – Role-Based Access Control · Interactive Tutorial</title>
  <link rel="preconnect" href="https://fonts.googleapis.com"/>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet"/>
  <style>
    /* ── CSS Variables ─────────────────────────────────────── */
    :root {
      --bg:          #0d0f1a;
      --surface:     #141728;
      --surface-2:   #1c2038;
      --surface-3:   #242847;
      --border:      #2e3260;
      --text:        #e2e4f0;
      --text-2:      #9ca3c4;
      --text-3:      #5b6290;
      --purple:      #8b5cf6;
      --purple-l:    #a78bfa;
      --blue:        #3b82f6;
      --blue-l:      #60a5fa;
      --green:       #10b981;
      --green-l:     #34d399;
      --amber:       #f59e0b;
      --red:         #ef4444;
      --pink:        #ec4899;
      --grad-1: linear-gradient(135deg,#8b5cf6,#3b82f6);
      --grad-2: linear-gradient(135deg,#3b82f6,#10b981);
      --grad-3: linear-gradient(135deg,#ec4899,#8b5cf6);
      --radius: 14px;
      --shadow: 0 8px 32px rgba(0,0,0,0.4);
    }
    *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
    html{scroll-behavior:smooth}
    body{
      font-family:'Inter',system-ui,sans-serif;
      background:var(--bg);
      color:var(--text);
      line-height:1.7;
      overflow-x:hidden;
    }
    ::selection{background:rgba(139,92,246,0.35);color:#fff}
    ::-webkit-scrollbar{width:6px}
    ::-webkit-scrollbar-track{background:var(--bg)}
    ::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}

    /* ── Utility ───────────────────────────────────────────── */
    .grad-text{
      background:var(--grad-1);
      -webkit-background-clip:text;
      -webkit-text-fill-color:transparent;
      background-clip:text;
    }
    .badge{
      display:inline-flex;align-items:center;gap:.35rem;
      padding:.25rem .75rem;border-radius:20px;
      font-size:.72rem;font-weight:700;letter-spacing:.06em;
      text-transform:uppercase;
    }
    .badge-purple{background:rgba(139,92,246,.18);color:var(--purple-l)}
    .badge-blue  {background:rgba(59,130,246,.18);color:var(--blue-l)}
    .badge-green {background:rgba(16,185,129,.18);color:var(--green-l)}
    .badge-amber {background:rgba(245,158,11,.18);color:var(--amber)}
    .badge-red   {background:rgba(239,68,68,.18);color:var(--red)}
    .badge-pink  {background:rgba(236,72,153,.18);color:var(--pink)}

    /* ── Layout ────────────────────────────────────────────── */
    .container{max-width:900px;margin:0 auto;padding:0 1.25rem}

    /* ── Progress bar ──────────────────────────────────────── */
    #progress-bar{
      position:fixed;top:0;left:0;height:3px;width:0%;
      background:var(--grad-1);z-index:9999;
      transition:width .1s linear;
    }

    /* ── Nav ───────────────────────────────────────────────── */
    nav{
      position:sticky;top:0;z-index:100;
      background:rgba(13,15,26,.85);
      backdrop-filter:blur(16px);
      border-bottom:1px solid var(--border);
      padding:.75rem 1.25rem;
    }
    .nav-inner{
      max-width:900px;margin:0 auto;
      display:flex;align-items:center;justify-content:space-between;gap:1rem;
    }
    .nav-brand{
      font-weight:800;font-size:1rem;
      background:var(--grad-1);
      -webkit-background-clip:text;-webkit-text-fill-color:transparent;
      background-clip:text;white-space:nowrap;
    }
    .nav-links{
      display:flex;gap:.25rem;list-style:none;
      overflow-x:auto;scrollbar-width:none;
    }
    .nav-links::-webkit-scrollbar{display:none}
    .nav-links a{
      font-size:.72rem;font-weight:600;
      color:var(--text-2);text-decoration:none;
      padding:.3rem .6rem;border-radius:6px;
      transition:background .15s,color .15s;white-space:nowrap;
    }
    .nav-links a:hover{background:var(--surface-2);color:var(--text)}

    /* ── Hero ──────────────────────────────────────────────── */
    .hero{
      padding:5rem 1.25rem 3.5rem;
      text-align:center;
      position:relative;overflow:hidden;
    }
    .hero-glow{
      position:absolute;top:-80px;left:50%;transform:translateX(-50%);
      width:600px;height:400px;
      background:radial-gradient(ellipse at center,rgba(139,92,246,.18) 0%,transparent 70%);
      pointer-events:none;
    }
    .hero-eyebrow{margin-bottom:1rem}
    .hero h1{
      font-size:clamp(2rem,5vw,3.5rem);
      font-weight:900;line-height:1.1;
      letter-spacing:-.03em;margin-bottom:1.25rem;
    }
    .hero-sub{
      font-size:1.05rem;color:var(--text-2);
      max-width:580px;margin:0 auto 2rem;
    }
    .hero-stats{
      display:flex;flex-wrap:wrap;gap:1rem;
      justify-content:center;margin-top:2rem;
    }
    .hero-stat{
      background:var(--surface);border:1px solid var(--border);
      border-radius:12px;padding:.75rem 1.25rem;
      display:flex;flex-direction:column;align-items:center;gap:.2rem;
    }
    .hero-stat-num{font-size:1.5rem;font-weight:800}
    .hero-stat-lbl{font-size:.72rem;color:var(--text-3);font-weight:600;text-transform:uppercase;letter-spacing:.07em}

    /* ── Section ───────────────────────────────────────────── */
    section{padding:3rem 0}
    .section-header{
      display:flex;align-items:center;gap:.75rem;
      margin-bottom:1.75rem;
    }
    .section-icon{
      width:42px;height:42px;border-radius:12px;
      display:flex;align-items:center;justify-content:center;
      font-size:1.2rem;flex-shrink:0;
    }
    .section-header h2{font-size:1.5rem;font-weight:800;letter-spacing:-.02em}

    /* ── Cards ─────────────────────────────────────────────── */
    .card{
      background:var(--surface);border:1px solid var(--border);
      border-radius:var(--radius);padding:1.5rem;
      box-shadow:var(--shadow);
    }
    .card-grid{
      display:grid;
      grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
      gap:1rem;
    }
    .feature-card{
      background:var(--surface-2);border:1px solid var(--border);
      border-radius:var(--radius);padding:1.25rem;
      transition:transform .2s,box-shadow .2s,border-color .2s;
    }
    .feature-card:hover{
      transform:translateY(-3px);
      box-shadow:0 12px 40px rgba(139,92,246,.15);
      border-color:rgba(139,92,246,.4);
    }
    .feature-card .icon{font-size:1.75rem;margin-bottom:.75rem}
    .feature-card h3{font-size:.95rem;font-weight:700;margin-bottom:.4rem}
    .feature-card p{font-size:.83rem;color:var(--text-2);line-height:1.6}

    /* ── Accordion ─────────────────────────────────────────── */
    .accordion{display:flex;flex-direction:column;gap:.6rem}
    .acc-item{
      background:var(--surface);border:1px solid var(--border);
      border-radius:var(--radius);overflow:hidden;
    }
    .acc-trigger{
      width:100%;display:flex;align-items:center;justify-content:space-between;
      padding:1rem 1.25rem;background:none;border:none;
      color:var(--text);font-family:inherit;font-size:.95rem;font-weight:600;
      cursor:pointer;text-align:left;gap:1rem;
      transition:background .15s;
    }
    .acc-trigger:hover{background:var(--surface-2)}
    .acc-trigger .step-num{
      width:28px;height:28px;border-radius:50%;
      background:var(--grad-1);
      display:flex;align-items:center;justify-content:center;
      font-size:.75rem;font-weight:800;flex-shrink:0;color:#fff;
    }
    .acc-trigger .chevron{
      transition:transform .25s;flex-shrink:0;
      color:var(--text-3);font-size:1.1rem;
    }
    .acc-trigger[aria-expanded="true"] .chevron{transform:rotate(180deg)}
    .acc-body{
      max-height:0;overflow:hidden;
      transition:max-height .35s cubic-bezier(.4,0,.2,1);
    }
    .acc-body-inner{padding:0 1.25rem 1.25rem}
    .acc-body-inner p{font-size:.875rem;color:var(--text-2);margin-bottom:.75rem}
    .acc-body-inner p:last-child{margin-bottom:0}

    /* ── Code blocks ───────────────────────────────────────── */
    .code-wrap{position:relative;margin:1rem 0}
    .code-label{
      display:flex;align-items:center;justify-content:space-between;
      background:var(--surface-3);border:1px solid var(--border);
      border-bottom:none;border-radius:var(--radius) var(--radius) 0 0;
      padding:.5rem 1rem;
      font-size:.72rem;font-weight:700;color:var(--text-3);
      text-transform:uppercase;letter-spacing:.07em;
    }
    .copy-btn{
      background:var(--surface-2);border:1px solid var(--border);
      color:var(--text-2);font-family:inherit;font-size:.72rem;font-weight:600;
      padding:.25rem .65rem;border-radius:6px;cursor:pointer;
      transition:background .15s,color .15s;
    }
    .copy-btn:hover{background:var(--purple);color:#fff;border-color:var(--purple)}
    pre{
      background:var(--surface-3);border:1px solid var(--border);
      border-radius:0 0 var(--radius) var(--radius);
      padding:1.25rem;overflow-x:auto;
      font-family:'Fira Code',monospace;font-size:.82rem;line-height:1.7;
    }
    .code-wrap:first-of-type pre,.code-wrap:only-child pre{
      border-radius:var(--radius);
    }
    /* Syntax colours */
    .kw{color:#c084fc}   /* keyword   */
    .fn{color:#60a5fa}   /* function  */
    .str{color:#34d399}  /* string    */
    .cm{color:#4b5563;font-style:italic} /* comment */
    .num{color:#fb923c}  /* number    */
    .cls{color:#f9a8d4}  /* class     */
    .prop{color:#93c5fd} /* property  */
    .op{color:#e2e8f0}   /* operator  */

    /* Annotated code */
    .ann-line{
      display:flex;gap:1rem;align-items:flex-start;
      padding:.2rem .5rem;border-radius:6px;
      transition:background .15s;cursor:default;
    }
    .ann-line:hover{background:rgba(139,92,246,.08)}
    .ann-line code{flex:1;font-family:'Fira Code',monospace;font-size:.82rem}
    .ann-note{
      font-size:.72rem;color:var(--purple-l);
      white-space:nowrap;padding-top:.15rem;
      opacity:0;transition:opacity .2s;
    }
    .ann-line:hover .ann-note{opacity:1}

    /* ── Tabs ──────────────────────────────────────────────── */
    .tabs{display:flex;gap:.4rem;margin-bottom:1rem;flex-wrap:wrap}
    .tab-btn{
      padding:.45rem 1rem;border-radius:8px;
      background:var(--surface-2);border:1px solid var(--border);
      color:var(--text-2);font-family:inherit;font-size:.82rem;font-weight:600;
      cursor:pointer;transition:all .15s;
    }
    .tab-btn.active{
      background:var(--grad-1);border-color:transparent;color:#fff;
    }
    .tab-btn:hover:not(.active){background:var(--surface-3);color:var(--text)}
    .tab-panel{display:none}
    .tab-panel.active{display:block}

    /* ── RBAC Demo ─────────────────────────────────────────── */
    .demo-grid{
      display:grid;
      grid-template-columns:1fr 1fr;
      gap:1rem;
    }
    @media(max-width:620px){.demo-grid{grid-template-columns:1fr}}
    .demo-panel{
      background:var(--surface-2);border:1px solid var(--border);
      border-radius:var(--radius);padding:1.1rem;
    }
    .demo-panel h4{
      font-size:.78rem;font-weight:700;text-transform:uppercase;
      letter-spacing:.08em;color:var(--text-3);margin-bottom:.85rem;
    }
    .role-tag{
      display:inline-flex;align-items:center;gap:.3rem;
      background:rgba(139,92,246,.15);border:1px solid rgba(139,92,246,.3);
      color:var(--purple-l);font-size:.75rem;font-weight:600;
      padding:.2rem .6rem;border-radius:6px;margin:.2rem;cursor:pointer;
      transition:all .15s;
    }
    .role-tag.selected,.role-tag:hover{
      background:rgba(139,92,246,.35);border-color:var(--purple);
    }
    .perm-chip{
      display:inline-flex;align-items:center;gap:.25rem;
      font-size:.72rem;font-weight:600;
      padding:.15rem .55rem;border-radius:20px;margin:.15rem;
    }
    .perm-read {background:rgba(59,130,246,.15);color:var(--blue-l)}
    .perm-write{background:rgba(16,185,129,.15);color:var(--green-l)}
    .perm-delete{background:rgba(239,68,68,.15);color:var(--red)}
    .perm-admin{background:rgba(245,158,11,.15);color:var(--amber)}

    .check-row{
      display:flex;align-items:center;gap:.75rem;
      padding:.6rem .85rem;border-radius:8px;
      background:var(--surface-3);margin-bottom:.5rem;
    }
    .check-row select,.check-row input{
      background:var(--surface);border:1px solid var(--border);
      color:var(--text);font-family:inherit;font-size:.82rem;
      padding:.35rem .65rem;border-radius:6px;flex:1;min-width:0;
      outline:none;
    }
    .check-row select:focus,.check-row input:focus{border-color:var(--purple)}
    .check-btn{
      padding:.4rem .9rem;border-radius:8px;
      background:var(--grad-1);border:none;
      color:#fff;font-family:inherit;font-size:.82rem;font-weight:700;
      cursor:pointer;white-space:nowrap;flex-shrink:0;
      transition:opacity .15s;
    }
    .check-btn:hover{opacity:.85}
    .result-box{
      padding:.75rem 1rem;border-radius:8px;
      font-size:.875rem;font-weight:600;
      display:flex;align-items:center;gap:.6rem;
      transition:all .3s;
    }
    .result-allow{background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.3);color:var(--green-l)}
    .result-deny {background:rgba(239,68,68,.12); border:1px solid rgba(239,68,68,.3); color:var(--red)}
    .result-idle {background:var(--surface-3);border:1px solid var(--border);color:var(--text-3)}

    /* Inheritance tree */
    .tree{font-family:'Fira Code',monospace;font-size:.82rem;line-height:2}
    .tree-node{display:flex;align-items:center;gap:.5rem;padding:.1rem 0}
    .tree-indent{padding-left:1.5rem;border-left:2px solid var(--border);margin-left:.6rem}
    .tree-arrow{color:var(--purple-l);font-size:.9rem}

    /* ── Mistakes ──────────────────────────────────────────── */
    .mistake-card{
      background:var(--surface);border:1px solid rgba(239,68,68,.25);
      border-left:4px solid var(--red);
      border-radius:var(--radius);padding:1.25rem;margin-bottom:.75rem;
    }
    .mistake-card h4{
      color:var(--red);font-size:.9rem;font-weight:700;
      display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;
    }
    .fix-card{
      background:rgba(16,185,129,.06);border:1px solid rgba(16,185,129,.2);
      border-left:4px solid var(--green);
      border-radius:var(--radius);padding:1rem;margin-top:.6rem;
    }
    .fix-card p{font-size:.82rem;color:var(--green-l)}

    /* ── Tips ──────────────────────────────────────────────── */
    .tip-card{
      background:var(--surface);border:1px solid rgba(139,92,246,.25);
      border-left:4px solid var(--purple);
      border-radius:var(--radius);padding:1.25rem;margin-bottom:.75rem;
      display:flex;gap:1rem;
    }
    .tip-icon{font-size:1.5rem;flex-shrink:0;margin-top:.1rem}
    .tip-card h4{font-size:.9rem;font-weight:700;color:var(--purple-l);margin-bottom:.4rem}
    .tip-card p{font-size:.83rem;color:var(--text-2);line-height:1.65}

    /* ── Quiz ──────────────────────────────────────────────── */
    .quiz-card{
      background:var(--surface);border:1px solid var(--border);
      border-radius:var(--radius);padding:1.5rem;margin-bottom:1rem;
    }
    .quiz-q{font-size:.95rem;font-weight:700;margin-bottom:1rem}
    .quiz-q span{color:var(--purple-l)}
    .quiz-opts{display:flex;flex-direction:column;gap:.5rem}
    .quiz-opt{
      display:flex;align-items:center;gap:.75rem;
      padding:.75rem 1rem;border-radius:10px;
      background:var(--surface-2);border:1.5px solid var(--border);
      cursor:pointer;transition:all .15s;font-size:.875rem;
    }
    .quiz-opt:hover{border-color:var(--purple);background:var(--surface-3)}
    .quiz-opt.correct{background:rgba(16,185,129,.12);border-color:var(--green);color:var(--green-l)}
    .quiz-opt.wrong  {background:rgba(239,68,68,.12); border-color:var(--red);  color:var(--red)}
    .quiz-opt.disabled{pointer-events:none;opacity:.6}
    .quiz-opt.correct.disabled{opacity:1}
    .quiz-feedback{
      margin-top:.85rem;padding:.75rem 1rem;border-radius:8px;
      font-size:.83rem;font-weight:600;display:none;
    }
    .quiz-feedback.show{display:flex;align-items:center;gap:.5rem}
    .quiz-feedback.ok {background:rgba(16,185,129,.1);color:var(--green-l)}
    .quiz-feedback.no {background:rgba(239,68,68,.1); color:var(--red)}
    .quiz-score{
      text-align:center;padding:1.5rem;
      background:var(--surface);border:1px solid var(--border);
      border-radius:var(--radius);display:none;
    }
    .quiz-score .score-num{
      font-size:3rem;font-weight:900;
      background:var(--grad-1);
      -webkit-background-clip:text;-webkit-text-fill-color:transparent;
      background-clip:text;
    }

    /* Confetti canvas */
    #confetti-canvas{
      position:fixed;inset:0;pointer-events:none;z-index:9998;
    }

    /* ── Cheat sheet ───────────────────────────────────────── */
    .cheat-grid{
      display:grid;
      grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
      gap:1rem;
    }
    .cheat-item{
      background:var(--surface-2);border:1px solid var(--border);
      border-radius:var(--radius);padding:1rem;
    }
    .cheat-item h4{
      font-size:.72rem;font-weight:700;text-transform:uppercase;
      letter-spacing:.08em;color:var(--text-3);margin-bottom:.6rem;
    }
    .cheat-item ul{list-style:none;display:flex;flex-direction:column;gap:.35rem}
    .cheat-item li{font-size:.8rem;color:var(--text-2);display:flex;gap:.5rem}
    .cheat-item li::before{content:"→";color:var(--purple-l);flex-shrink:0}

    /* ── Footer ────────────────────────────────────────────── */
    footer{
      background:var(--surface);border-top:1px solid var(--border);
      padding:2.5rem 1.25rem;text-align:center;
      color:var(--text-3);font-size:.8rem;
    }
    footer .footer-brand{
      font-size:1.1rem;font-weight:800;
      background:var(--grad-1);
      -webkit-background-clip:text;-webkit-text-fill-color:transparent;
      background-clip:text;margin-bottom:.5rem;
    }

    /* ── Animations ────────────────────────────────────────── */
    @keyframes fadeUp{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:none}}
    @keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
    @keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
    @keyframes shimmer{
      0%{background-position:-200% 0}
      100%{background-position:200% 0}
    }
    .fade-up{opacity:0;animation:fadeUp .6s ease forwards}
    .delay-1{animation-delay:.1s}.delay-2{animation-delay:.2s}
    .delay-3{animation-delay:.3s}.delay-4{animation-delay:.4s}
    .float-anim{animation:float 3s ease-in-out infinite}

    @media(prefers-reduced-motion:reduce){
      *{animation-duration:.01ms!important;transition-duration:.01ms!important}
    }

    /* ── Responsive ────────────────────────────────────────── */
    @media(max-width:640px){
      .hero h1{font-size:1.9rem}
      .nav-links{display:none}
      .demo-grid{grid-template-columns:1fr}
      .cheat-grid{grid-template-columns:1fr}
    }
  </style>
</head>
<body>

<div id="progress-bar" role="progressbar" aria-label="Reading progress"></div>
<canvas id="confetti-canvas" aria-hidden="true"></canvas>

<!-- ░░ NAV ░░ -->
<nav aria-label="Tutorial navigation">
  <div class="nav-inner">
    <span class="nav-brand">🔐 RBAC Tutorial</span>
    <ul class="nav-links" role="list">
      <li><a href="#what">What</a></li>
      <li><a href="#why">Why</a></li>
      <li><a href="#how">How</a></li>
      <li><a href="#demo">Demo</a></li>
      <li><a href="#code">Code</a></li>
      <li><a href="#mistakes">Mistakes</a></li>
      <li><a href="#tips">Tips</a></li>
      <li><a href="#quiz">Quiz</a></li>
      <li><a href="#cheat">Cheat Sheet</a></li>
    </ul>
  </div>
</nav>

<!-- ░░ HERO ░░ -->
<header class="hero" id="top">
  <div class="hero-glow" aria-hidden="true"></div>
  <div class="container">
    <div class="hero-eyebrow fade-up">
      <span class="badge badge-purple">🎓 Interactive Tutorial</span>
      <span class="badge badge-blue" style="margin-left:.5rem">⏱ 8 min read</span>
      <span class="badge badge-green" style="margin-left:.5rem">🎮 3 interactive demos</span>
    </div>
    <h1 class="fade-up delay-1">
      Role-Based<br/>
      <span class="grad-text">Access Control</span>
    </h1>
    <p class="hero-sub fade-up delay-2">
      Learn how to build a <strong>RBAC system</strong> — the security backbone of every serious web app.
      Roles, permissions, inheritance, and request evaluation — all explained simply. 🚀
    </p>
    <div class="hero-stats fade-up delay-3">
      <div class="hero-stat">
        <span class="hero-stat-num grad-text">3</span>
        <span class="hero-stat-lbl">Core Concepts</span>
      </div>
      <div class="hero-stat">
        <span class="hero-stat-num" style="color:var(--blue-l)">∞</span>
        <span class="hero-stat-lbl">Scalability</span>
      </div>
      <div class="hero-stat">
        <span class="hero-stat-num" style="color:var(--green-l)">100%</span>
        <span class="hero-stat-lbl">Industry Standard</span>
      </div>
      <div class="hero-stat">
        <span class="hero-stat-num" style="color:var(--pink)">🔐</span>
        <span class="hero-stat-lbl">Secure by Design</span>
      </div>
    </div>
  </div>
</header>

<main>

<!-- ══════════════════════════════════════════════════════════
     SECTION 1 — WHAT IS IT?
══════════════════════════════════════════════════════════ -->
<section id="what">
  <div class="container">
    <div class="section-header">
      <div class="section-icon" style="background:rgba(139,92,246,.15)">🤔</div>
      <h2>What Is It?</h2>
    </div>

    <div class="card" style="margin-bottom:1.25rem">
      <p style="font-size:1.05rem;line-height:1.8;margin-bottom:1rem">
        Imagine a <strong>hotel</strong>. 🏨 A <em>guest</em> can open their own room.
        A <em>cleaner</em> can open any room but not the safe.
        A <em>manager</em> can do everything.
        Nobody needs a separate key for every single door — instead, their
        <strong>role</strong> determines what they can access.
      </p>
      <p style="font-size:.9rem;color:var(--text-2)">
        <strong>Role-Based Access Control (RBAC)</strong> works the same way in software.
        Instead of granting permissions directly to individual users, you define
        <em>roles</em> (like <code>admin</code>, <code>editor</code>, <code>viewer</code>),
        attach <em>permissions</em> to those roles, and then assign roles to users.
        When a user makes a request, the system checks: <em>"Does this user's role
        allow this action on this resource?"</em>
      </p>
    </div>

    <div class="card-grid">
      <div class="feature-card">
        <div class="icon">👤</div>
        <h3>User</h3>
        <p>A person or service making requests. Has one or more roles assigned.</p>
      </div>
      <div class="feature-card">
        <div class="icon">🎭</div>
        <h3>Role</h3>
        <p>A named collection of permissions. e.g. <code>editor</code> = can read + write articles.</p>
      </div>
      <div class="feature-card">
        <div class="icon">🔑</div>
        <h3>Permission</h3>
        <p>A specific allowed action on a resource. Format: <code>resource:action</code> e.g. <code>invoice:read</code>.</p>
      </div>
      <div class="feature-card">
        <div class="icon">📦</div>
        <h3>Resource</h3>
        <p>The thing being accessed — an invoice, a user record, a report, an API endpoint.</p>
      </div>
      <div class="feature-card">
        <div class="icon">⚡</div>
        <h3>Action</h3>
        <p>What you want to do: <code>read</code>, <code>write</code>, <code>delete</code>, <code>publish</code>.</p>
      </div>
      <div class="feature-card">
        <div class="icon">🧬</div>
        <h3>Inheritance</h3>
        <p>A senior role automatically includes all permissions of a junior role it extends.</p>
      </div>
    </div>
  </div>
</section>

<!-- ══════════════════════════════════════════════════════════
     SECTION 2 — WHY USE IT?
══════════════════════════════════════════════════════════ -->
<section id="why" style="background:var(--surface);border-top:1px solid var(--border);border-bottom:1px solid var(--border)">
  <div class="container">
    <div class="section-header">
      <div class="section-icon" style="background:rgba(59,130,246,.15)">💡</div>
      <h2>Why Use It?</h2>
    </div>
    <div class="card-grid">
      <div class="feature-card">
        <div class="icon">🔒</div>
        <h3>Least Privilege</h3>
        <p>Users only get exactly what they need — nothing more. A billing clerk can't delete users.</p>
      </div>
      <div class="feature-card">
        <div class="icon">📈</div>
        <h3>Scales Easily</h3>
        <p>Add 1000 new editors? Assign them the <code>editor</code> role. One change, done.</p>
      </div>
      <div class="feature-card">
        <div class="icon">🛡️</div>
        <h3>Audit-Friendly</h3>
        <p>Easy to answer "who can do what?" — critical for compliance (GDPR, SOC 2, HIPAA).</p>
      </div>
      <div class="feature-card">
        <div class="icon">🧩</div>
        <h3>Separation of Duties</h3>
        <p>No single user has unchecked power. Approvers can't also be requesters.</p>
      </div>
      <div class="feature-card">
        <div class="icon">🔄</div>
        <h3>Easy Revocation</h3>
        <p>Remove a role from a user and all associated permissions vanish instantly.</p>
      </div>
      <div class="feature-card">
        <div class="icon">🏗️</div>
        <h3>Industry Standard</h3>
        <p>Used by AWS IAM, GitHub, Google Cloud, Kubernetes, and virtually every SaaS product.</p>
      </div>
    </div>
  </div>
</section>

<!-- ══════════════════════════════════════════════════════════
     SECTION 3 — HOW DOES IT WORK?
══════════════════════════════════════════════════════════ -->
<section id="how">
  <div class="container">
    <div class="section-header">
      <div class="section-icon" style="background:rgba(16,185,129,.15)">⚙️</div>
      <h2>How Does It Work?</h2>
    </div>
    <p style="color:var(--text-2);margin-bottom:1.5rem;font-size:.9rem">
      Click each step to expand the full explanation 👇
    </p>
    <div class="accordion" id="how-accordion">

      <div class="acc-item">
        <button class="acc-trigger" aria-expanded="false" aria-controls="step1-body">
          <div style="display:flex;align-items:center;gap:.75rem">
            <span class="step-num">1</span>
            <span>Define Permissions — the atomic unit</span>
          </div>
          <span class="chevron" aria-hidden="true">▾</span>
        </button>
        <div class="acc-body" id="step1-body" role="region">
          <div class="acc-body-inner">
            <p>A <strong>permission</strong> is the smallest unit of access. It combines a <em>resource</em> and an <em>action</em> with a colon separator:</p>
            <div class="code-wrap">
              <div class="code-label"><span>Permission format</span></div>
              <pre><span class="cm">// resource:action</span>
<span class="str">"invoice:read"</span>    <span class="cm">// can view invoices</span>
<span class="str">"invoice:write"</span>   <span class="cm">// can create/edit invoices</span>
<span class="str">"invoice:delete"</span>  <span class="cm">// can delete invoices</span>
<span class="str">"user:read"</span>       <span class="cm">// can view users</span>
<span class="str">"user:write"</span>      <span class="cm">// can create/edit users</span>
<span class="str">"report:publish"</span>  <span class="cm">// can publish reports</span></pre>
            </div>
            <p>Think of each permission as a <strong>key</strong>. A role is a <strong>keyring</strong> holding several keys.</p>
          </div>
        </div>
      </div>

      <div class="acc-item">
        <button class="acc-trigger" aria-expanded="false" aria-controls="step2-body">
          <div style="display:flex;align-items:center;gap:.75rem">
            <span class="step-num">2</span>
            <span>Define Roles — named permission collections</span>
          </div>
          <span class="chevron" aria-hidden="true">▾</span>
        </button>
        <div class="acc-body" id="step2-body" role="region">
          <div class="acc-body-inner">
            <p>A <strong>role</strong> is a named set of permissions. You can also declare <em>inherits</em> to build a hierarchy:</p>
            <div class="code-wrap">
              <div class="code-label"><span>Role definitions (JS object)</span></div>
              <pre><span class="kw">const</span> <span class="prop">roles</span> = {
<span class="str">viewer</span>: {
  <span class="prop">permissions</span>: [<span class="str">"invoice:read"</span>, <span class="str">"report:read"</span>]
},
<span class="str">editor</span>: {
  <span class="prop">inherits</span>: [<span class="str">"viewer"</span>],          <span class="cm">// gets all viewer perms</span>
  <span class="prop">permissions</span>: [<span class="str">"invoice:write"</span>, <span class="str">"report:write"</span>]
},
<span class="str">admin</span>: {
  <span class="prop">inherits</span>: [<span class="str">"editor"</span>],          <span class="cm">// gets all editor perms</span>
  <span class="prop">permissions</span>: [<span class="str">"user:write"</span>, <span class="str">"invoice:delete"</span>]
}
};</pre>
            </div>
            <p>The <code>admin</code> role ends up with <em>every</em> permission from <code>viewer</code> + <code>editor</code> + its own — automatically, via inheritance.</p>
          </div>
        </div>
      </div>

      <div class="acc-item">
        <button class="acc-trigger" aria-expanded="false" aria-controls="step3-body">
          <div style="display:flex;align-items:center;gap:.75rem">
            <span class="step-num">3</span>
            <span>Assign Roles to Users</span>
          </div>
          <span class="chevron" aria-hidden="true">▾</span>
        </button>
        <div class="acc-body" id="step3-body" role="region">
          <div class="acc-body-inner">
            <p>Each user record stores an array of role names. A user can hold <em>multiple</em> roles simultaneously — their effective permissions are the <strong>union</strong> of all assigned roles.</p>
            <div class="code-wrap">
              <div class="code-label"><span>User records</span></div>
              <pre><span class="kw">const</span> <span class="prop">users</span> = {
alice: { <span class="prop">roles</span>: [<span class="str">"admin"</span>] },
bob:   { <span class="prop">roles</span>: [<span class="str">"editor"</span>] },
carol: { <span class="prop">roles</span>: [<span class="str">"viewer"</span>] },
dave:  { <span class="prop">roles</span>: [<span class="str">"viewer"</span>, <span class="str">"editor"</span>] } <span class="cm">// multi-role</span>
};</pre>
            </div>
          </div>
        </div>
      </div>

      <div class="acc-item">
        <button class="acc-trigger" aria-expanded="false" aria-controls="step4-body">
          <div style="display:flex;align-items:center;gap:.75rem">
            <span class="step-num">4</span>
            <span>Resolve Permissions — flatten the inheritance tree</span>
          </div>
          <span class="chevron" aria-hidden="true">▾</span>
        </button>
        <div class="acc-body" id="step4-body" role="region">
          <div class="acc-body-inner">
            <p>When a request arrives, collect all permissions for the user by walking up the inheritance chain recursively, deduplicating as you go:</p>
            <div class="code-wrap">
              <div class="code-label"><span>Resolve function</span></div>
              <pre><span class="kw">function</span> <span class="fn">resolvePermissions</span>(roleName, visited = <span class="kw">new</span> <span class="cls">Set</span>()) {
<span class="kw">if</span> (visited.<span class="fn">has</span>(roleName)) <span class="kw">return</span> []; <span class="cm">// guard cycles</span>
visited.<span class="fn">add</span>(roleName);

<span class="kw">const</span> role = roles[roleName];
<span class="kw">if</span> (!role) <span class="kw">return</span> [];

<span class="kw">let</span> perms = [...(role.permissions ?? [])];

<span class="kw">for</span> (<span class="kw">const</span> parent <span class="kw">of</span> (role.inherits ?? [])) {
  perms = perms.<span class="fn">concat</span>(<span class="fn">resolvePermissions</span>(parent, visited));
}
<span class="kw">return</span> [...<span class="kw">new</span> <span class="cls">Set</span>(perms)]; <span class="cm">// deduplicate</span>
}</pre>
            </div>
          </div>
        </div>
      </div>

      <div class="acc-item">
        <button class="acc-trigger" aria-expanded="false" aria-controls="step5-body">
          <div style="display:flex;align-items:center;gap:.75rem">
            <span class="step-num">5</span>
            <span>Evaluate the Request — allow or deny</span>
          </div>
          <span class="chevron" aria-hidden="true">▾</span>
        </button>
        <div class="acc-body" id="step5-body" role="region">
          <div class="acc-body-inner">
            <p>On every incoming request, build the user's full permission set and check whether the required permission is present. Default is <strong>deny</strong>.</p>
            <div class="code-wrap">
              <div class="code-label"><span>canDo() — the gate function</span></div>
              <pre><span class="kw">function</span> <span class="fn">canDo</span>(userId, resource, action) {
<span class="kw">const</span> user = users[userId];
<span class="kw">if</span> (!user) <span class="kw">return false</span>;

<span class="cm">// Collect all permissions across all assigned roles</span>
<span class="kw">const</span> allPerms = user.roles
  .<span class="fn">flatMap</span>(r => <span class="fn">resolvePermissions</span>(r));

<span class="kw">const</span> required = <span class="str">`</span><span class="op">${</span>resource<span class="op">}</span><span class="str">:</span><span class="op">${</span>action<span class="op">}</span><span class="str">`</span>;
<span class="kw">return</span> allPerms.<span class="fn">includes</span>(required);
}

<span class="cm">// Usage on an API route:</span>
<span class="kw">if</span> (!<span class="fn">canDo</span>(req.userId, <span class="str">"invoice"</span>, <span class="str">"delete"</span>)) {
<span class="kw">return</span> res.<span class="fn">status</span>(<span class="num">403</span>).<span class="fn">json</span>({ error: <span class="str">"Forbidden"</span> });
}</pre>
            </div>
          </div>
        </div>
      </div>

    </div><!-- /accordion -->

    <!-- Inheritance tree visual -->
    <div class="card" style="margin-top:1.5rem">
      <h3 style="font-size:.9rem;font-weight:700;margin-bottom:1rem;color:var(--text-2)">
        🧬 Inheritance Tree — how permissions flow upward
      </h3>
      <div class="tree">
        <div class="tree-node">
          <span class="badge badge-purple">admin</span>
          <span style="color:var(--text-3);font-size:.75rem;margin-left:.5rem">user:write, invoice:delete</span>
        </div>
        <div class="tree-indent">
          <div class="tree-node">
            <span class="tree-arrow">↳</span>
            <span class="badge badge-blue">editor</span>
            <span style="color:var(--text-3);font-size:.75rem;margin-left:.5rem">invoice:write, report:write</span>
          </div>
          <div class="tree-indent">
            <div class="tree-node">
              <span class="tree-arrow">↳</span>
              <span class="badge badge-green">viewer</span>
              <span style="color:var(--text-3);font-size:.75rem;margin-left:.5rem">invoice:read, report:read</span>
            </div>
          </div>
        </div>
      </div>
      <p style="font-size:.78rem;color:var(--text-3);margin-top:.85rem">
        ✅ An <strong>admin</strong> automatically inherits <em>all 6 permissions</em> from the chain above.
      </p>
    </div>

  </div>
</section>

<!-- ══════════════════════════════════════════════════════════
     SECTION 4 — LIVE DEMO
══════════════════════════════════════════════════════════ -->
<section id="demo" style="background:var(--surface);border-top:1px solid var(--border);border-bottom:1px solid var(--border)">
  <div class="container">
    <div class="section-header">
      <div class="section-icon" style="background:rgba(236,72,153,.15)">🎮</div>
      <h2>Live Demo</h2>
    </div>
    <p style="color:var(--text-2);margin-bottom:1.5rem;font-size:.9rem">
      Select a user, pick a resource and action, then hit <strong>Check Access</strong> to see RBAC in action. ⚡
    </p>

    <div class="demo-grid">

      <!-- Left: Role map -->
      <div class="demo-panel">
        <h4>👥 Users &amp; Their Roles</h4>
        <div id="user-role-map"></div>
        <div style="margin-top:1rem">
          <h4 style="margin-bottom:.6rem">🔑 Resolved Permissions</h4>
          <div id="resolved-perms" style="min-height:32px"></div>
        </div>
      </div>

      <!-- Right: Access check -->
      <div class="demo-panel">
        <h4>🔍 Check Access</h4>
        <div class="check-row">
          <select id="demo-user" aria-label="Select user">
            <option value="alice">alice (admin)</option>
            <option value="bob">bob (editor)</option>
            <option value="carol">carol (viewer)</option>
            <option value="dave">dave (viewer+editor)</option>
          </select>
        </div>
        <div class="check-row">
          <select id="demo-resource" aria-label="Select resource">
            <option value="invoice">invoice</option>
            <option value="report">report</option>
            <option value="user">user</option>
          </select>
          <select id="demo-action" aria-label="Select action">
            <option value="read">read</option>
            <option value="write">write</option>
            <option value="delete">delete</option>
          </select>
        </div>
        <button class="check-btn" id="demo-check-btn" onclick="runDemoCheck()">
          ⚡ Check Access
        </button>
        <div id="demo-result" class="result-box result-idle" style="margin-top:.85rem" aria-live="polite">
          Select options and click Check Access
        </div>
        <div style="margin-top:1rem">
          <h4 style="margin-bottom:.5rem">📋 Permission required:</h4>
          <code id="demo-perm-required" style="font-family:'Fira Code',monospace;font-size:.85rem;color:var(--purple-l)">invoice:read</code>
        </div>
      </div>

    </div>
  </div>
</section>

<!-- ══════════════════════════════════════════════════════════
     SECTION 5 — CODE BREAKDOWN
══════════════════════════════════════════════════════════ -->
<section id="code">
  <div class="container">
    <div class="section-header">
      <div class="section-icon" style="background:rgba(59,130,246,.15)">💻</div>
      <h2>Code Breakdown</h2>
    </div>

    <div class="tabs" role="tablist" aria-label="Code examples">
      <button class="tab-btn active" role="tab" aria-selected="true"  data-tab="tab-full">Full System</button>
      <button class="tab-btn"        role="tab" aria-selected="false" data-tab="tab-middleware">Middleware</button>
      <button class="tab-btn"        role="tab" aria-selected="false" data-tab="tab-db">Database Schema</button>
    </div>

    <!-- Tab: Full System -->
    <div class="tab-panel active" id="tab-full">
      <div class="code-wrap">
        <div class="code-label">
          <span>rbac.js — Complete annotated implementation</span>
          <button class="copy-btn" onclick="copyCode('code-full')">Copy</button>
        </div>
        <pre id="code-full"><span class="cm">// ── 1. Permission definitions ──────────────────────────────</span>
<span class="cm">// A permission = "resource:action" string</span>
<span class="kw">const</span> <span class="prop">PERMISSIONS</span> = {
INVOICE_READ:   <span class="str">'invoice:read'</span>,
INVOICE_WRITE:  <span class="str">'invoice:write'</span>,
INVOICE_DELETE: <span class="str">'invoice:delete'</span>,
REPORT_READ:    <span class="str">'report:read'</span>,
REPORT_WRITE:   <span class="str">'report:write'</span>,
USER_READ:      <span class="str">'user:read'</span>,
USER_WRITE:     <span class="str">'user:write'</span>,
};

<span class="cm">// ── 2. Role definitions with inheritance ──────────────────</span>
<span class="kw">const</span> <span class="prop">ROLES</span> = {
viewer: {
  permissions: [
    PERMISSIONS.INVOICE_READ,
    PERMISSIONS.REPORT_READ,
    PERMISSIONS.USER_READ,
  ],
},
editor: {
  inherits: [<span class="str">'viewer'</span>],           <span class="cm">// ← inherits viewer's perms</span>
  permissions: [
    PERMISSIONS.INVOICE_WRITE,
    PERMISSIONS.REPORT_WRITE,
  ],
},
admin: {
  inherits: [<span class="str">'editor'</span>],           <span class="cm">// ← inherits editor + viewer</span>
  permissions: [
    PERMISSIONS.INVOICE_DELETE,
    PERMISSIONS.USER_WRITE,
  ],
},
};

<span class="cm">// ── 3. Resolve all permissions for a role (recursive) ─────</span>
<span class="kw">function</span> <span class="fn">resolvePermissions</span>(roleName, visited = <span class="kw">new</span> <span class="cls">Set</span>()) {
<span class="kw">if</span> (visited.<span class="fn">has</span>(roleName)) <span class="kw">return</span> []; <span class="cm">// prevent cycles</span>
visited.<span class="fn">add</span>(roleName);

<span class="kw">const</span> role = ROLES[roleName];
<span class="kw">if</span> (!role) <span class="kw">return</span> [];

<span class="kw">let</span> perms = [...(role.permissions ?? [])];

<span class="kw">for</span> (<span class="kw">const</span> parentRole <span class="kw">of</span> (role.inherits ?? [])) {
  perms = perms.<span class="fn">concat</span>(<span class="fn">resolvePermissions</span>(parentRole, visited));
}

<span class="kw">return</span> [...<span class="kw">new</span> <span class="cls">Set</span>(perms)]; <span class="cm">// deduplicate</span>
}

<span class="cm">// ── 4. Check if a user can perform an action ──────────────</span>
<span class="kw">function</span> <span class="fn">canDo</span>(user, resource, action) {
<span class="kw">if</span> (!user?.roles?.length) <span class="kw">return false</span>;

<span class="kw">const</span> effectivePerms = user.roles
  .<span class="fn">flatMap</span>(role => <span class="fn">resolvePermissions</span>(role));

<span class="kw">return</span> effectivePerms.<span class="fn">includes</span>(<span class="str">`</span><span class="op">${</span>resource<span class="op">}</span><span class="str">:</span><span class="op">${</span>action<span class="op">}</span><span class="str">`</span>);
}

<span class="cm">// ── 5. Example usage ──────────────────────────────────────</span>
<span class="kw">const</span> alice = { id: <span class="str">'alice'</span>, roles: [<span class="str">'admin'</span>]  };
<span class="kw">const</span> carol = { id: <span class="str">'carol'</span>, roles: [<span class="str">'viewer'</span>] };

console.<span class="fn">log</span>(<span class="fn">canDo</span>(alice, <span class="str">'invoice'</span>, <span class="str">'delete'</span>)); <span class="cm">// true</span>
console.<span class="fn">log</span>(<span class="fn">canDo</span>(carol, <span class="str">'invoice'</span>, <span class="str">'delete'</span>)); <span class="cm">// false</span>
console.<span class="fn">log</span>(<span class="fn">canDo</span>(carol, <span class="str">'invoice'</span>, <span class="str">'read'</span>));   <span class="cm">// true</span></pre>
      </div>
    </div>

    <!-- Tab: Middleware -->
    <div class="tab-panel" id="tab-middleware">
      <div class="code-wrap">
        <div class="code-label">
          <span>Express.js middleware — protect routes</span>
          <button class="copy-btn" onclick="copyCode('code-mw')">Copy</button>
        </div>
        <pre id="code-mw"><span class="cm">// requirePermission middleware factory</span>
<span class="kw">function</span> <span class="fn">requirePermission</span>(resource, action) {
<span class="kw">return</span> (req, res, next) => {
  <span class="kw">const</span> user = req.user; <span class="cm">// set by auth middleware</span>

  <span class="kw">if</span> (!user) {
    <span class="kw">return</span> res.<span class="fn">status</span>(<span class="num">401</span>).<span class="fn">json</span>({ error: <span class="str">'Unauthenticated'</span> });
  }

  <span class="kw">if</span> (!<span class="fn">canDo</span>(user, resource, action)) {
    <span class="kw">return</span> res.<span class="fn">status</span>(<span class="num">403</span>).<span class="fn">json</span>({
      error: <span class="str">'Forbidden'</span>,
      required: <span class="str">`</span><span class="op">${</span>resource<span class="op">}</span><span class="str">:</span><span class="op">${</span>action<span class="op">}</span><span class="str">`</span>,
    });
  }

  <span class="fn">next</span>(); <span class="cm">// ✅ permitted — continue to route handler</span>
};
}

<span class="cm">// Apply to routes:</span>
app.<span class="fn">get</span>(<span class="str">'/invoices'</span>,
<span class="fn">requirePermission</span>(<span class="str">'invoice'</span>, <span class="str">'read'</span>),
invoiceController.list
);

app.<span class="fn">delete</span>(<span class="str">'/invoices/:id'</span>,
<span class="fn">requirePermission</span>(<span class="str">'invoice'</span>, <span class="str">'delete'</span>),
invoiceController.remove
);

app.<span class="fn
Live Preview