Files
oci-arch-utils/sites/oracle-ai-atp-comparison/Oracle-AI-ATP-comparison.html

1791 lines
107 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Oracle Autonomous AI Database — Flavor &amp; Version Advisor</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,500;0,9..40,700;1,9..40,300&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
<style>
:root {
--oracle-red: #C74634;
--surface-0: #F5F3F0;
--surface-1: #FFFFFF;
--surface-2: #F9F8F6;
--surface-3: #EDECEA;
--surface-4: #E2E0DD;
--border: #D6D3D0;
--border-light: #C5C2BF;
--text-primary: #1C1917;
--text-secondary: #57534E;
--text-muted: #A8A29E;
--shared-color: #2563EB;
--shared-bg: rgba(37,99,235,0.08);
--spool-color: #9333EA;
--spool-bg: rgba(147,51,234,0.08);
--pool-color: #D97706;
--pool-bg: rgba(217,119,6,0.08);
--dedicated-color: #059669;
--dedicated-bg: rgba(5,150,105,0.08);
--dedpool-color: #0891B2;
--dedpool-bg: rgba(8,145,178,0.08);
--accent-glow: rgba(199,70,52,0.08);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { font-size: 18px; }
body { font-family: 'DM Sans', sans-serif; background: var(--surface-0); color: var(--text-primary); line-height: 1.6; min-height: 100vh; font-size: 1rem; }
.header { background: linear-gradient(135deg, var(--surface-1) 0%, var(--surface-0) 100%); border-bottom: 1px solid var(--border); padding: 2rem 2rem 1.5rem; position: relative; overflow: hidden; }
.header::before { content: ''; position: absolute; top: -50%; right: -10%; width: 500px; height: 500px; background: radial-gradient(circle, var(--accent-glow) 0%, transparent 70%); pointer-events: none; }
.header-content { max-width: 1400px; margin: 0 auto; position: relative; z-index: 1; }
.header-badge { display: inline-flex; align-items: center; gap: 0.5rem; background: var(--surface-3); border: 1px solid var(--border-light); padding: 0.25rem 0.75rem; border-radius: 100px; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-secondary); margin-bottom: 1rem; }
.header-badge .dot { width: 6px; height: 6px; background: var(--oracle-red); border-radius: 50%; animation: pulse 2s infinite; }
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
.header h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.03em; margin-bottom: 0.25rem; }
.header h1 span { color: var(--oracle-red); }
.header p { color: var(--text-secondary); font-size: 1rem; }
.main-content { max-width: 1400px; margin: 0 auto; padding: 0 2rem 3rem; }
input[name="main-tab"] { display: none; }
.tab-nav { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-bottom: 2rem; position: sticky; top: 0; background: var(--surface-0); z-index: 100; padding-top: 1rem; overflow-x: auto; transition: transform 0.25s ease; }
.tab-nav.nav-hidden { transform: translateY(-100%); }
.tab-nav label { padding: 0.75rem 1.25rem; font-size: 0.95rem; font-weight: 500; color: var(--text-muted); cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.2s; white-space: nowrap; user-select: none; }
.charts-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; margin: 1.5rem 0; }
.chart-card { background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px; padding: 1rem; display: flex; flex-direction: column; align-items: center; }
.chart-title { font-size: 0.82rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.75rem; text-align: center; }
.chart-card canvas { max-width: 100%; height: auto; }
@media (max-width: 900px) { .charts-row { grid-template-columns: 1fr; } .matrix-body { flex-direction: column; } .matrix-score-panel { position: static; flex: none; width: 100%; display: flex; gap: 0.5rem; flex-wrap: wrap; } .matrix-score-panel .score-chip { width: auto; flex: 1; min-width: 150px; } }
.tab-nav label:hover { color: var(--text-secondary); }
.share-link { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.72rem; color: var(--text-muted); cursor: pointer; padding: 0.2rem 0.5rem; border-radius: 5px; border: 1px solid transparent; transition: all 0.15s; background: none; font-family: inherit; }
.share-link:hover { color: var(--oracle-red); border-color: var(--border); background: var(--surface-2); }
.share-link svg { width: 13px; height: 13px; }
.share-link.copied { color: #10B981; border-color: rgba(16,185,129,0.3); }
.section-header-row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 0.25rem; }
#tab-overview:checked ~ .tab-nav label[for="tab-overview"],
#tab-scenarios:checked ~ .tab-nav label[for="tab-scenarios"],
#tab-matrix:checked ~ .tab-nav label[for="tab-matrix"],
#tab-cost:checked ~ .tab-nav label[for="tab-cost"] { color: var(--oracle-red); border-bottom-color: var(--oracle-red); }
.tab-panel { display: none; animation: fadeIn 0.3s ease; }
@keyframes fadeIn { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
#tab-overview:checked ~ .tab-panels #panel-overview,
#tab-scenarios:checked ~ .tab-panels #panel-scenarios,
#tab-matrix:checked ~ .tab-panels #panel-matrix,
#tab-cost:checked ~ .tab-panels #panel-cost { display: block; }
/* Cost table */
.cost-table { width: 100%; border-collapse: separate; border-spacing: 0; background: var(--surface-1); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; margin-bottom: 1.5rem; }
.cost-table thead th { background: var(--surface-2); padding: 0.75rem 1rem; text-align: right; font-weight: 600; font-size: 0.85rem; color: var(--text-secondary); border-bottom: 1px solid var(--border); }
.cost-table thead th:first-child { text-align: left; border-radius: 12px 0 0 0; }
.cost-table thead th:last-child { border-radius: 0 12px 0 0; }
.cost-table thead th.shared { color: var(--shared-color); }
.cost-table thead th.pool { color: var(--pool-color); }
.cost-table thead th.dedicated { color: var(--dedicated-color); }
.cost-table tbody td { padding: 0.75rem 1rem; vertical-align: middle; border-bottom: 1px solid var(--border); color: var(--text-secondary); text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 0.95rem; }
.cost-table tbody tr:last-child td { border-bottom: none; }
.cost-table tbody tr:hover td { background: var(--surface-2); }
.cost-table tbody td:first-child { text-align: left; font-family: 'DM Sans', sans-serif; font-weight: 600; color: var(--text-primary); }
.cost-table .best-price { color: #10B981; font-weight: 700; }
.cost-table .pct-badge { display: inline-block; font-size: 0.72rem; padding: 0.1rem 0.4rem; border-radius: 4px; margin-left: 0.4rem; font-weight: 600; }
.cost-table .pct-badge.more { background: rgba(239,68,68,0.15); color: #F87171; }
.cost-table .pct-badge.best { background: rgba(16,185,129,0.12); color: #10B981; }
.cost-input-group { display: flex; flex-direction: column; gap: 0.3rem; }
.cost-input-group label { font-size: 0.78rem; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.04em; }
.cost-input-group input { background: var(--surface-3); border: 1px solid var(--border-light); color: var(--text-primary); padding: 0.55rem 0.75rem; border-radius: 8px; font-family: 'JetBrains Mono', monospace; font-size: 0.95rem; width: 100%; transition: border-color 0.2s; }
.cost-input-group input:focus { outline: none; border-color: var(--oracle-red); box-shadow: 0 0 0 2px rgba(199,70,52,0.2); }
.cost-input-hint { font-size: 0.7rem; color: var(--pool-color); font-style: italic; min-height: 1rem; }
.cost-result-cards { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.85rem; }
.cost-result-card { background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px; padding: 1.1rem; position: relative; overflow: hidden; min-width: 0; }
.cost-result-card.winner { border-color: #10B981; box-shadow: 0 0 12px rgba(16,185,129,0.15); }
.cost-result-card .flavor-tag { font-family: 'JetBrains Mono', monospace; font-size: 0.65rem; font-weight: 600; padding: 0.15rem 0.5rem; border-radius: 5px; display: inline-block; margin-bottom: 0.5rem; }
.cost-result-card .flavor-tag.shared { background: var(--shared-bg); color: var(--shared-color); border: 1px solid rgba(59,130,246,0.25); }
.cost-result-card .flavor-tag.spool { background: var(--spool-bg); color: var(--spool-color); border: 1px solid rgba(168,85,247,0.25); }
.cost-result-card .flavor-tag.pool { background: var(--pool-bg); color: var(--pool-color); border: 1px solid rgba(245,158,11,0.25); }
.cost-result-card .flavor-tag.dedicated { background: var(--dedicated-bg); color: var(--dedicated-color); border: 1px solid rgba(16,185,129,0.25); }
.cost-result-card .flavor-tag.dedpool { background: var(--dedpool-bg); color: var(--dedpool-color); border: 1px solid rgba(6,182,212,0.25); }
.cost-result-card .total-price { font-family: 'JetBrains Mono', monospace; font-size: 1.5rem; font-weight: 700; margin-bottom: 0.35rem; }
.cost-result-card .total-price.shared { color: var(--shared-color); }
.cost-result-card .total-price.spool { color: var(--spool-color); }
.cost-result-card .total-price.pool { color: var(--pool-color); }
.cost-result-card .total-price.dedicated { color: var(--dedicated-color); }
.cost-result-card .total-price.dedpool { color: var(--dedpool-color); }
.cost-result-card .pct-vs { font-size: 0.75rem; font-weight: 600; margin-bottom: 0.6rem; }
.cost-result-card .breakdown-line { display: flex; justify-content: space-between; font-size: 0.78rem; color: var(--text-muted); padding: 0.15rem 0; }
.cost-result-card .breakdown-line span:last-child { font-family: 'JetBrains Mono', monospace; color: var(--text-secondary); }
.cost-result-card .winner-label { display: block; text-align: center; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; background: #10B981; color: #fff; padding: 0.25rem 0.6rem; border-radius: 6px; margin-bottom: 0.5rem; }
.cost-result-card .ecpu-note { font-size: 0.72rem; color: var(--pool-color); font-style: italic; margin-top: 0.4rem; padding-top: 0.4rem; border-top: 1px solid var(--border); }
.cost-totals-chip { display: inline-flex; align-items: center; gap: 0.4rem; background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 0.35rem 0.7rem; font-size: 0.78rem; color: var(--text-secondary); }
.cost-totals-chip strong { color: var(--text-primary); font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; }
.cost-totals-chip.highlight strong { color: var(--pool-color); }
#ci_usage_pct { -webkit-appearance: none; appearance: none; width: 100%; height: 6px; background: var(--surface-4); border-radius: 3px; outline: none; cursor: pointer; }
#ci_usage_pct::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%; background: var(--oracle-red); cursor: pointer; border: 2px solid var(--surface-1); box-shadow: 0 0 6px rgba(199,70,52,0.4); }
#ci_usage_pct::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: var(--oracle-red); cursor: pointer; border: 2px solid var(--surface-1); }
@media (max-width: 1200px) { .cost-result-cards { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 900px) { .cost-result-cards { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .cost-result-cards { grid-template-columns: 1fr; } }
/* ── OCI Cost Estimator Style ── */
.oci-header-bar { background: linear-gradient(135deg, #2D6B3F 0%, #1A4528 100%); border-radius: 12px 12px 0 0; padding: 1rem 1.5rem; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; }
.oci-header-left { display: flex; align-items: center; gap: 0.75rem; color: #fff; }
.oci-header-title { font-size: 1.1rem; font-weight: 700; letter-spacing: -0.01em; }
.oci-header-sub { font-size: 0.72rem; color: rgba(255,255,255,0.65); margin-top: 0.15rem; text-transform: uppercase; letter-spacing: 0.05em; }
.oci-header-right { display: flex; align-items: center; gap: 1rem; }
.oci-header-cost-label { font-size: 0.72rem; color: rgba(255,255,255,0.7); text-transform: uppercase; letter-spacing: 0.04em; text-align: right; }
.oci-header-cost-value { font-family: 'JetBrains Mono', monospace; font-size: 1.6rem; font-weight: 700; color: #fff; text-align: right; }
.oci-config-card { background: var(--surface-1); border: 1px solid var(--border); border-top: none; border-radius: 0 0 12px 12px; padding: 1.25rem 1.5rem 1.5rem; margin-bottom: 1.5rem; }
.oci-config-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.75rem; padding-bottom: 1rem; margin-bottom: 1rem; border-bottom: 1px solid var(--border); }
.oci-config-title { display: flex; align-items: center; gap: 0.5rem; font-size: 1rem; font-weight: 600; color: var(--text-primary); }
.oci-config-title svg { color: var(--text-muted); }
.oci-util-badge { display: flex; align-items: center; gap: 0.5rem; background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 0.35rem 0.75rem; }
.oci-util-label { font-size: 0.72rem; color: var(--oracle-red); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; }
.oci-util-value { font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; color: var(--text-primary); font-weight: 600; }
.oci-preset-row { margin-bottom: 1.25rem; }
.oci-field-label { display: block; font-size: 0.75rem; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.35rem; }
.oci-select { background: var(--surface-3); border: 1px solid var(--border-light); color: var(--text-primary); padding: 0.5rem 2.2rem 0.5rem 0.75rem; border-radius: 8px; font-family: 'DM Sans', sans-serif; font-size: 0.88rem; cursor: pointer; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23A8A29E' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 0.7rem center; min-width: 280px; transition: border-color 0.2s; }
.oci-select:focus { outline: none; border-color: var(--oracle-red); }
.oci-fields-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 1.25rem; margin-bottom: 1.25rem; }
.oci-field { display: flex; flex-direction: column; }
.oci-input { background: var(--surface-3); border: 1px solid var(--border-light); color: var(--text-primary); padding: 0.6rem 0.85rem; border-radius: 8px; font-family: 'JetBrains Mono', monospace; font-size: 1rem; width: 100%; transition: border-color 0.2s; }
.oci-input:focus { outline: none; border-color: #2D6B3F; box-shadow: 0 0 0 2px rgba(45,107,63,0.2); }
.oci-field-hint { font-size: 0.7rem; color: var(--pool-color); font-style: italic; margin-top: 0.25rem; min-height: 1rem; }
.oci-slider { -webkit-appearance: none; appearance: none; width: 100%; height: 6px; background: var(--surface-4); border-radius: 3px; outline: none; cursor: pointer; margin-top: 0.35rem; }
.oci-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; border-radius: 50%; background: #2D6B3F; cursor: pointer; border: 2px solid var(--surface-1); box-shadow: 0 0 6px rgba(45,107,63,0.4); }
.oci-slider::-moz-range-thumb { width: 20px; height: 20px; border-radius: 50%; background: #2D6B3F; cursor: pointer; border: 2px solid var(--surface-1); }
.oci-totals-row { display: flex; flex-wrap: wrap; gap: 0.75rem; padding-top: 1rem; border-top: 1px solid var(--border); }
.oci-billing-details { background: var(--surface-1); border: 1px solid var(--border); border-radius: 12px; margin: 1.5rem 0; }
.oci-billing-details summary { padding: 0.85rem 1.25rem; cursor: pointer; font-size: 0.88rem; color: var(--text-secondary); font-weight: 600; }
.oci-billing-details summary:hover { color: var(--text-primary); }
.oci-billing-details[open] summary { border-bottom: 1px solid var(--border); }
.oci-billing-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; padding: 1.25rem; }
.oci-verify-link { text-align: center; font-size: 0.82rem; color: var(--text-muted); margin-top: 0.5rem; }
.oci-verify-link a { color: #2D6B3F; text-decoration: underline; font-weight: 600; }
.oci-verify-link a:hover { color: #3A8C52; }
@media (max-width: 700px) {
.oci-header-bar { padding: 0.85rem 1rem; border-radius: 10px 10px 0 0; flex-direction: column; align-items: flex-start; }
.oci-header-cost-value { font-size: 1.3rem; }
.oci-config-card { padding: 1rem; }
.oci-fields-grid { grid-template-columns: 1fr 1fr; gap: 0.85rem; }
.oci-select { min-width: unset; width: 100%; }
}
@media (max-width: 480px) {
.oci-fields-grid { grid-template-columns: 1fr; }
.oci-billing-grid { grid-template-columns: 1fr; }
}
/* Unified collapsible marker for all details */
details summary { list-style: none; cursor: pointer; }
details summary::-webkit-details-marker { display: none; }
details summary::marker { content: ""; }
details summary::before { content: "\2295"; font-size: 1.1rem; color: var(--oracle-red); margin-right: 0.5rem; font-weight: 400; vertical-align: middle; }
details[open] summary::before { content: "\2296"; }
.feature-matrix-details { background: var(--surface-1); border: 1px solid var(--border); border-radius: 10px; padding: 1rem 1.25rem; margin-bottom: 1.25rem; }
.feature-matrix-details[open] summary { margin-bottom: 0.5rem; }
.cloud-selector-label { font-size: 0.78rem; font-weight: 600; color: var(--text-secondary); margin-right: 0.25rem; }
.cloud-btn { font-size: 0.75rem; padding: 0.3rem 0.7rem; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-1); color: var(--text-secondary); cursor: pointer; transition: all 0.15s; font-weight: 500; }
.cloud-btn:hover { border-color: var(--text-muted); }
.cloud-btn.active { background: var(--oracle-red); color: #fff; border-color: var(--oracle-red); }
.cloud-doc-link { font-size: 0.7rem; text-decoration: none; opacity: 0.5; transition: opacity 0.15s; margin-right: 0.25rem; }
.cloud-doc-link:hover { opacity: 1; }
/* ── Feature Matrix Table ── */
.feature-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; margin-bottom: 1rem; }
.feature-table th { background: var(--surface-2); padding: 0.5rem 0.6rem; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; border-bottom: 2px solid var(--border); text-align: center; position: sticky; top: 0; z-index: 2; }
.feature-table td { padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--border); }
.feat-cat-row td { background: var(--surface-3); font-weight: 700; font-size: 0.78rem; color: var(--text-primary); padding: 0.5rem 0.6rem; letter-spacing: 0.02em; }
.feat-cat-label { border-left: 3px solid var(--oracle-red); }
.feat-name { font-weight: 500; color: var(--text-primary); }
.feat-doc-link { color: var(--oracle-red); text-decoration: none; font-size: 0.7rem; }
.feat-ver { text-align: center; }
.ver-badge { font-family: 'JetBrains Mono', monospace; font-size: 0.62rem; font-weight: 600; padding: 0.1rem 0.35rem; border-radius: 4px; }
.ver-badge.v26 { background: rgba(147,51,234,0.12); color: #9333EA; }
.ver-badge.v19 { background: rgba(37,99,235,0.1); color: #2563EB; }
.ver-badge.vboth { background: rgba(100,116,139,0.1); color: #64748B; }
.feat-avail { text-align: center; font-size: 0.9rem; }
.feat-avail.yes { color: #059669; }
.feat-avail.no { color: #DC2626; opacity: 0.5; }
.avail-yes { font-weight: 700; }
.avail-no { font-weight: 400; }
.feat-req { text-align: center; }
.feat-checkbox { width: 16px; height: 16px; cursor: pointer; accent-color: var(--oracle-red); }
.feat-row.feat-required { background: rgba(199,70,52,0.06); }
.feat-row.feat-required .feat-name { color: var(--oracle-red); font-weight: 600; }
.score-chip.filtered-out { opacity: 0.2; pointer-events: none; }
.ver-rec { margin-left: 0.25rem; }
.ver-rec-tag { font-family: 'JetBrains Mono', monospace; font-size: 0.6rem; font-weight: 600; padding: 0.1rem 0.35rem; border-radius: 4px; vertical-align: middle; }
.ver-rec-tag.v26 { background: rgba(147,51,234,0.12); color: #9333EA; }
.ver-rec-tag.vany { background: rgba(100,116,139,0.1); color: #64748B; }
.ver-rec-tag.excluded { background: rgba(220,38,38,0.08); color: #DC2626; font-style: italic; }
.filter-status-bar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; padding: 0.6rem 1rem; background: rgba(199,70,52,0.06); border: 1px solid rgba(199,70,52,0.15); border-radius: 8px; font-size: 0.82rem; color: var(--text-primary); margin-bottom: 1rem; }
.filter-status-left { display: flex; align-items: center; gap: 0.5rem; }
.filter-status-right { display: flex; align-items: center; gap: 0.3rem; flex-wrap: wrap; }
/* ── Deployment Hierarchy ── */
.hierarchy-container { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; margin-bottom: 2rem; }
.hierarchy-world { border: 1px solid var(--border); border-radius: 12px; overflow: hidden; background: var(--surface-1); }
.hierarchy-world-header { padding: 0.85rem 1.15rem; display: flex; align-items: center; gap: 0.6rem; }
.hierarchy-world-header.serverless-header { background: linear-gradient(135deg, rgba(37,99,235,0.08), rgba(147,51,234,0.06)); border-bottom: 2px solid var(--shared-color); }
.hierarchy-world-header.dedicated-header { background: linear-gradient(135deg, rgba(5,150,105,0.08), rgba(8,145,178,0.06)); border-bottom: 2px solid var(--dedicated-color); }
.hierarchy-world-icon { font-size: 1.2rem; }
.hierarchy-world-title { font-size: 1rem; font-weight: 700; color: var(--text-primary); }
.hierarchy-world-sub { font-size: 0.72rem; color: var(--text-muted); margin-left: auto; }
.hierarchy-children { padding: 0.85rem 1rem; display: flex; flex-direction: column; gap: 0.5rem; }
.hierarchy-child { display: flex; align-items: center; gap: 0.6rem; padding: 0.45rem 0.7rem; border-radius: 8px; background: var(--surface-2); border: 1px solid var(--border); }
.hierarchy-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.hierarchy-child.shared .hierarchy-dot { background: var(--shared-color); }
.hierarchy-child.spool .hierarchy-dot { background: var(--spool-color); }
.hierarchy-child.pool .hierarchy-dot { background: var(--pool-color); }
.hierarchy-child.dedicated .hierarchy-dot { background: var(--dedicated-color); }
.hierarchy-child.dedpool .hierarchy-dot { background: var(--dedpool-color); }
.hierarchy-child-name { font-size: 0.85rem; font-weight: 600; color: var(--text-primary); }
.hierarchy-child-desc { font-size: 0.72rem; color: var(--text-muted); margin-left: auto; }
@media (max-width: 700px) { .hierarchy-container { grid-template-columns: 1fr; } .hierarchy-world-sub { display: none; } }
.section-title { font-size: 1.35rem; font-weight: 700; margin-bottom: 0.25rem; letter-spacing: -0.02em; }
.section-subtitle { color: var(--text-muted); font-size: 0.95rem; margin-bottom: 1.5rem; }
.overview-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
.driver-card { background: var(--surface-1); border: 1px solid var(--border); border-radius: 12px; padding: 1.25rem; transition: border-color 0.2s; }
.driver-card:hover { border-color: var(--border-light); }
.driver-card h3 { font-size: 1.05rem; font-weight: 600; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
.driver-card h3 .icon { width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 8px; font-size: 1rem; flex-shrink: 0; }
.flavor-row { display: grid; grid-template-columns: 140px 1fr; gap: 0.5rem; margin-bottom: 0.6rem; align-items: start; }
.flavor-label { font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; font-weight: 600; padding: 0.25rem 0.5rem; border-radius: 6px; text-align: center; }
.flavor-label.shared { background: var(--shared-bg); color: var(--shared-color); border: 1px solid rgba(59,130,246,0.25); }
.flavor-label.spool { background: var(--spool-bg); color: var(--spool-color); border: 1px solid rgba(168,85,247,0.25); }
.flavor-label.pool { background: var(--pool-bg); color: var(--pool-color); border: 1px solid rgba(245,158,11,0.25); }
.flavor-label.dedicated { background: var(--dedicated-bg); color: var(--dedicated-color); border: 1px solid rgba(16,185,129,0.25); }
.flavor-label.dedpool { background: var(--dedpool-bg); color: var(--dedpool-color); border: 1px solid rgba(6,182,212,0.25); }
.flavor-desc { font-size: 0.92rem; color: var(--text-secondary); line-height: 1.5; }
.flavor-stars { margin-bottom: 2px; }
.score-bar-wrap { display: flex; align-items: center; gap: 0.5rem; }
.score-bar-num { font-family: 'JetBrains Mono', monospace; font-size: 0.78rem; font-weight: 700; color: var(--text-secondary); min-width: 28px; }
.score-bar-track { flex: 1; height: 6px; background: var(--surface-4); border-radius: 3px; min-width: 60px; max-width: 120px; }
.score-bar-fill { height: 100%; border-radius: 3px; transition: width 0.3s ease; }
.score-bar-fill.shared { background: var(--shared-color); }
.score-bar-fill.spool { background: var(--spool-color); }
.score-bar-fill.pool { background: var(--pool-color); }
.score-bar-fill.dedicated { background: var(--dedicated-color); }
.score-bar-fill.dedpool { background: var(--dedpool-color); }
.flavor-row-wrap { display: flex; flex-direction: column; gap: 0.15rem; }
.scenario-table { width: 100%; border-collapse: separate; border-spacing: 0; background: var(--surface-1); border: 1px solid var(--border); border-radius: 12px; margin-bottom: 2rem; font-size: 0.95rem; }
.scenario-table thead th { background: var(--surface-2); padding: 0.75rem 1rem; text-align: left; font-weight: 600; font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-secondary); border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 10; }
.scenario-table thead th:first-child { border-radius: 12px 0 0 0; }
.scenario-table thead th:last-child { border-radius: 0 12px 0 0; }
.scenario-table tbody td { padding: 0.85rem 1rem; vertical-align: top; border-bottom: 1px solid var(--border); color: var(--text-secondary); line-height: 1.55; }
.scenario-table tbody tr:last-child td { border-bottom: none; }
.scenario-table tbody tr:hover td { background: var(--surface-2); }
.scenario-table td:first-child { font-weight: 600; color: var(--text-primary); min-width: 140px; }
.scenario-table .takeaway { font-weight: 500; color: var(--text-primary); font-style: italic; }
.matrix-container { background: var(--surface-1); border: 1px solid var(--border); border-radius: 16px; padding: 1.5rem; margin-bottom: 2rem; }
.matrix-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1.5rem; flex-wrap: wrap; gap: 1rem; }
.matrix-header h2 { font-size: 1.2rem; font-weight: 700; }
.matrix-header p { color: var(--text-muted); font-size: 0.92rem; }
.scenario-selector { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.scenario-selector label { font-size: 0.9rem; color: var(--text-secondary); font-weight: 500; }
.scenario-selector select { background: var(--surface-3); border: 1px solid var(--border-light); color: var(--text-primary); padding: 0.5rem 2rem 0.5rem 0.75rem; border-radius: 8px; font-family: 'DM Sans', sans-serif; font-size: 0.92rem; cursor: pointer; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23A8A29E' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 0.6rem center; }
.scenario-selector select:focus { outline: 1px solid var(--oracle-red); border-color: var(--oracle-red); }
.reset-btn { background: var(--surface-3); border: 1px solid var(--border-light); color: var(--text-secondary); padding: 0.45rem 1rem; border-radius: 8px; font-size: 0.85rem; cursor: pointer; font-family: 'DM Sans', sans-serif; transition: all 0.2s; }
.reset-btn:hover { background: var(--surface-4); color: var(--text-primary); }
.score-summary { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 1.5rem; flex-direction: column; }
/* Decision Matrix split layout — score panel fixed left */
.matrix-body { display: flex; gap: 1.5rem; align-items: flex-start; }
.matrix-score-panel { position: sticky; top: 0.5rem; flex: 0 0 240px; }
.matrix-score-panel .score-chip { min-width: unset; width: 100%; margin-bottom: 0.5rem; }
.matrix-main-content { flex: 1; min-width: 0; max-height: 75vh; overflow-y: auto; overflow-x: auto; padding-right: 0.25rem; }
.matrix-main-content::-webkit-scrollbar { width: 6px; }
.matrix-main-content::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
.matrix-main-content::-webkit-scrollbar-track { background: transparent; }
.score-chip { display: flex; align-items: center; gap: 0.5rem; background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 0.6rem 0.9rem; min-width: 210px; }
.score-chip .label { font-size: 0.8rem; font-weight: 500; color: var(--text-secondary); }
.score-chip .value { font-family: 'JetBrains Mono', monospace; font-size: 1.3rem; font-weight: 700; }
.score-chip.shared .value { color: var(--shared-color); }
.score-chip.spool .value { color: var(--spool-color); }
.score-chip.pool .value { color: var(--pool-color); }
.score-chip.dedicated .value { color: var(--dedicated-color); }
.score-chip.dedpool .value { color: var(--dedpool-color); }
.score-chip .bar-bg { flex: 1; height: 5px; background: var(--surface-4); border-radius: 2px; min-width: 30px; }
.score-chip .bar-fill { height: 100%; border-radius: 2px; transition: width 0.4s ease; }
.score-chip.shared .bar-fill { background: var(--shared-color); }
.score-chip.spool .bar-fill { background: var(--spool-color); }
.score-chip.pool .bar-fill { background: var(--pool-color); }
.score-chip.dedicated .bar-fill { background: var(--dedicated-color); }
.score-chip.dedpool .bar-fill { background: var(--dedpool-color); }
.winner-badge { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; padding: 0.15rem 0.55rem; border-radius: 6px; margin-left: 0.5rem; animation: fadeIn 0.3s; }
.how-to-box { background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 1rem 1.25rem; margin-bottom: 1.5rem; font-size: 0.92rem; color: var(--text-secondary); line-height: 1.6; }
.how-to-box strong { color: var(--text-primary); }
.how-to-box .formula-example { font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; color: var(--text-muted); background: var(--surface-3); padding: 0.5rem 0.75rem; border-radius: 6px; margin-top: 0.6rem; display: block; line-height: 1.7; }
.formula-panel { background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 0.85rem 1rem; }
.formula-panel summary { cursor: pointer; font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); user-select: none; }
.formula-panel summary:hover { color: var(--text-primary); }
.formula-flavor { margin-top: 0.75rem; padding: 0.6rem 0.75rem; background: var(--surface-3); border-radius: 8px; margin-bottom: 0.5rem; }
.formula-flavor-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.4rem; }
.formula-flavor-name { font-size: 0.88rem; font-weight: 600; }
.formula-flavor-name.shared { color: var(--shared-color); }
.formula-flavor-name.spool { color: var(--spool-color); }
.formula-flavor-name.pool { color: var(--pool-color); }
.formula-flavor-name.dedicated { color: var(--dedicated-color); }
.formula-steps { font-family: 'JetBrains Mono', monospace; font-size: 0.78rem; color: var(--text-muted); line-height: 1.8; }
.formula-steps .step-line { display: flex; gap: 0.3rem; }
.formula-steps .step-op { color: var(--text-muted); min-width: 12px; }
.formula-total { font-weight: 700; color: var(--text-primary); border-top: 1px solid var(--border); padding-top: 0.3rem; margin-top: 0.3rem; }
.winner-badge.shared { background: var(--shared-bg); color: var(--shared-color); }
.winner-badge.spool { background: var(--spool-bg); color: var(--spool-color); }
.winner-badge.pool { background: var(--pool-bg); color: var(--pool-color); }
.winner-badge.dedicated { background: var(--dedicated-bg); color: var(--dedicated-color); }
.winner-badge.dedpool { background: var(--dedpool-bg); color: var(--dedpool-color); }
.criteria-grid { display: grid; gap: 0; }
.criteria-row { display: grid; grid-template-columns: 1fr 1.5fr; border-bottom: 1px solid var(--border); transition: background 0.15s; }
.criteria-row:last-child { border-bottom: none; }
.criteria-row:hover { background: var(--surface-2); }
.criteria-left { padding: 1rem; border-right: 1px solid var(--border); }
.criteria-name-row { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.6rem; }
.criteria-name { font-weight: 600; font-size: 1rem; }
.info-trigger { width: 20px; height: 20px; border-radius: 50%; background: var(--surface-4); border: 1px solid var(--border-light); display: inline-flex; align-items: center; justify-content: center; font-size: 0.7rem; font-weight: 700; color: var(--text-muted); cursor: pointer; flex-shrink: 0; transition: all 0.2s; }
.info-trigger:hover { background: var(--oracle-red); color: #fff; border-color: var(--oracle-red); }
.info-trigger.active { background: var(--oracle-red); color: #fff; border-color: var(--oracle-red); }
.info-panel { display: none; grid-column: 1 / -1; background: var(--surface-3); border-bottom: 1px solid var(--border); padding: 1rem 1.25rem; animation: fadeIn 0.2s ease; }
.info-panel.open { display: block; }
.info-panel .info-desc { font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 0.75rem; font-style: italic; line-height: 1.55; }
.info-panel .info-flavor { margin-bottom: 0.5rem; display: grid; grid-template-columns: 120px 1fr; gap: 0.4rem; align-items: start; }
.info-panel .info-flavor:last-child { margin-bottom: 0; }
.info-panel .info-flavor-label { font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; font-weight: 600; padding: 0.2rem 0.45rem; border-radius: 4px; text-align: center; }
.info-panel .info-flavor-label.shared { background: var(--shared-bg); color: var(--shared-color); }
.info-panel .info-flavor-label.spool { background: var(--spool-bg); color: var(--spool-color); }
.info-panel .info-flavor-label.pool { background: var(--pool-bg); color: var(--pool-color); }
.info-panel .info-flavor-label.dedicated { background: var(--dedicated-bg); color: var(--dedicated-color); }
.info-panel .info-flavor-label.dedpool { background: var(--dedpool-bg); color: var(--dedpool-color); }
.info-panel .info-flavor-text { font-size: 0.88rem; color: var(--text-secondary); line-height: 1.5; }
.weight-control { display: flex; align-items: center; gap: 0.5rem; }
.weight-control label { font-size: 0.78rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; min-width: 62px; }
.weight-control input[type="range"] { flex: 1; -webkit-appearance: none; appearance: none; height: 5px; background: var(--surface-4); border-radius: 2px; outline: none; cursor: pointer; }
.weight-control input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; background: var(--oracle-red); cursor: pointer; border: 2px solid var(--surface-1); box-shadow: 0 0 6px rgba(199,70,52,0.4); }
.weight-control input[type="range"]::-moz-range-thumb { width: 16px; height: 16px; border-radius: 50%; background: var(--oracle-red); cursor: pointer; border: 2px solid var(--surface-1); }
.weight-pct { font-family: 'JetBrains Mono', monospace; font-size: 0.9rem; color: var(--oracle-red); min-width: 30px; text-align: right; font-weight: 600; }
.criteria-right { padding: 1rem; }
.scores-row { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.4rem; }
.score-cell { text-align: center; }
.score-cell .score-label { font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 0.3rem; }
.score-cell.shared .score-label { color: var(--shared-color); }
.score-cell.pool .score-label { color: var(--pool-color); }
.score-cell.dedicated .score-label { color: var(--dedicated-color); }
.score-dots { display: flex; gap: 4px; justify-content: center; }
.score-dot { width: 20px; height: 20px; border-radius: 5px; border: 1px solid var(--border); background: var(--surface-3); transition: all 0.2s; cursor: pointer; }
.score-dot.filled.shared { background: var(--shared-color); border-color: var(--shared-color); }
.score-dot.filled.spool { background: var(--spool-color); border-color: var(--spool-color); }
.score-dot.filled.pool { background: var(--pool-color); border-color: var(--pool-color); }
.score-dot.filled.dedicated { background: var(--dedicated-color); border-color: var(--dedicated-color); }
.score-dot.filled.dedpool { background: var(--dedpool-color); border-color: var(--dedpool-color); }
.ref-list { display: flex; flex-direction: column; gap: 0.5rem; }
.ref-item { display: flex; align-items: center; gap: 0.75rem; background: var(--surface-1); border: 1px solid var(--border); border-radius: 10px; padding: 0.9rem 1rem; text-decoration: none; color: var(--text-primary); transition: all 0.2s; }
.ref-item:hover { border-color: var(--oracle-red); background: var(--surface-2); }
.ref-item .ref-icon { width: 34px; height: 34px; background: var(--surface-3); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 1rem; flex-shrink: 0; }
.ref-item .ref-text { flex: 1; }
.ref-item .ref-text strong { display: block; font-size: 0.95rem; font-weight: 600; }
.ref-item .ref-text span { font-size: 0.82rem; color: var(--text-muted); font-family: 'JetBrains Mono', monospace; word-break: break-all; }
.notes-callout { background: var(--surface-2); border: 1px solid var(--border); border-left: 3px solid var(--oracle-red); border-radius: 0 10px 10px 0; padding: 1rem 1.25rem; margin-bottom: 2rem; }
.notes-callout h3 { font-size: 1rem; font-weight: 600; margin-bottom: 0.5rem; }
.notes-callout ul { list-style: none; display: flex; flex-direction: column; gap: 0.4rem; }
.notes-callout li { font-size: 0.92rem; color: var(--text-secondary); padding-left: 1rem; position: relative; line-height: 1.55; }
.notes-callout li::before { content: '\203A'; position: absolute; left: 0; color: var(--oracle-red); font-weight: 700; }
/* Billing description cards */
.billing-card { background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px; padding: 1rem 1.15rem; }
.billing-card h4 { font-size: 0.95rem; font-weight: 700; margin-bottom: 0.5rem; }
.billing-card.shared h4 { color: var(--shared-color); }
.billing-card.spool h4 { color: var(--spool-color); }
.billing-card.pool h4 { color: var(--pool-color); }
.billing-card.dedicated h4 { color: var(--dedicated-color); }
.billing-card.dedpool h4 { color: var(--dedpool-color); }
.billing-card p { font-size: 0.82rem; color: var(--text-secondary); line-height: 1.5; margin-bottom: 0.5rem; }
.doc-link { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.72rem; color: var(--oracle-red); text-decoration: none; font-weight: 500; margin-top: 0.35rem; padding: 0.2rem 0.5rem; background: rgba(199,70,52,0.08); border-radius: 5px; transition: background 0.15s; }
.doc-link:hover { background: rgba(199,70,52,0.18); text-decoration: underline; }
.doc-link svg { width: 12px; height: 12px; flex-shrink: 0; }
.overview-doc-link { display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.68rem; color: var(--oracle-red); text-decoration: none; opacity: 0.8; transition: opacity 0.15s; }
.overview-doc-link:hover { opacity: 1; text-decoration: underline; }
.billing-formula { background: var(--surface-3); border-radius: 8px; padding: 0.6rem 0.75rem; margin: 0.5rem 0; display: flex; flex-direction: column; gap: 0.2rem; }
.billing-formula .billing-line { font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; color: var(--text-secondary); line-height: 1.6; }
.billing-formula .billing-line em { font-style: normal; color: var(--text-primary); font-weight: 600; min-width: 5.5rem; display: inline-block; }
.billing-card .billing-source { font-size: 0.75rem; color: var(--text-muted, #78716C); margin-bottom: 0; font-style: italic; }
.billing-details-toggle summary:hover { color: var(--oracle-red); }
@media (max-width: 900px) {
html { font-size: 17px; }
.overview-grid { grid-template-columns: 1fr; }
.criteria-row { grid-template-columns: 1fr; }
.criteria-left { border-right: none; border-bottom: 1px solid var(--border); }
.score-summary { flex-direction: column; }
.header h1 { font-size: 1.4rem; }
.header p { font-size: 0.95rem; }
.scenario-table { font-size: 0.82rem; display: block; overflow-x: auto; }
.main-content { padding: 0 1rem 2rem; }
.matrix-container { padding: 1rem; }
.matrix-header { flex-direction: column; }
.scenario-selector { width: 100%; }
.scenario-selector select { width: 100%; font-size: 0.85rem; }
.score-chip { min-width: unset; flex: 1; }
.info-panel .info-flavor { grid-template-columns: 1fr; }
.info-panel .info-flavor-label { justify-self: start; }
.cost-table { display: block; overflow-x: auto; }
.tab-nav label { font-size: 0.88rem; }
.section-title { font-size: 1.2rem; }
.driver-card h3 { font-size: 1rem; }
.flavor-desc { font-size: 0.92rem; }
.billing-card p { font-size: 0.85rem; }
.billing-formula .billing-line { font-size: 0.78rem; }
}
@media (max-width: 480px) {
html { font-size: 16px; }
.header { padding: 1.25rem 1rem 1rem; }
.header h1 { font-size: 1.2rem; }
.header p { font-size: 0.85rem; }
.main-content { padding: 0 0.75rem 2rem; }
.tab-nav { padding-top: 0.5rem; }
.tab-nav label { padding: 0.6rem 0.75rem; font-size: 0.82rem; }
/* Section titles */
.section-title { font-size: 1.1rem; }
.section-subtitle { font-size: 0.88rem; }
/* Overview cards */
.driver-card { padding: 1rem; }
.driver-card h3 { font-size: 0.95rem; }
.flavor-row { grid-template-columns: 1fr; gap: 0.25rem; }
.flavor-label { justify-self: start; font-size: 0.7rem; }
.flavor-desc { font-size: 0.85rem; }
.score-bar-track { max-width: 100px; }
/* Scenario table → cards on mobile */
.scenario-table { font-size: 0.82rem; }
.scenario-table thead th { padding: 0.5rem 0.6rem; font-size: 0.75rem; }
.scenario-table tbody td { padding: 0.6rem; }
.scenario-table .takeaway { font-size: 0.82rem; }
/* Matrix */
.matrix-container { padding: 0.75rem; border-radius: 12px; }
.matrix-header h2 { font-size: 1.05rem; }
.how-to-box { font-size: 0.82rem; padding: 0.75rem; }
.how-to-box .formula-example { font-size: 0.75rem; padding: 0.4rem 0.6rem; }
/* Score chips stack */
.score-summary { gap: 0.5rem; }
.score-chip { padding: 0.5rem 0.65rem; }
.score-chip .label { font-size: 0.75rem; }
.score-chip .value { font-size: 1.1rem; }
.winner-badge { font-size: 0.68rem; padding: 0.15rem 0.4rem; }
/* Criteria rows */
.criteria-row { grid-template-columns: 1fr; }
.criteria-left { padding: 0.75rem; border-right: none; border-bottom: 1px solid var(--border); }
.criteria-name { font-size: 0.9rem; }
.weight-control label { font-size: 0.75rem; min-width: 55px; }
.weight-pct { font-size: 0.82rem; }
.criteria-right { padding: 0.75rem; }
.scores-row { grid-template-columns: 1fr; gap: 0.6rem; }
.score-cell { text-align: left; display: flex; align-items: center; gap: 0.5rem; }
.score-cell .score-label { margin-bottom: 0; min-width: 100px; font-size: 0.75rem; }
.score-dots { justify-content: flex-start; }
.score-dot { width: 26px; height: 26px; border-radius: 6px; }
/* Info panel */
.info-panel { padding: 0.75rem; }
.info-panel .info-desc { font-size: 0.82rem; }
.info-panel .info-flavor { grid-template-columns: 1fr; gap: 0.2rem; }
.info-panel .info-flavor-label { justify-self: start; font-size: 0.7rem; }
.info-panel .info-flavor-text { font-size: 0.82rem; }
/* Formula detail */
.formula-panel { padding: 0.65rem; }
.formula-panel summary { font-size: 0.82rem; }
.formula-flavor { padding: 0.5rem; }
.formula-steps { font-size: 0.72rem; line-height: 1.7; }
.formula-steps .step-line { flex-wrap: wrap; }
/* Cost estimator */
.cost-result-card .total-price { font-size: 1.25rem; }
.cost-result-card .breakdown-line { font-size: 0.82rem; }
.cost-result-card .flavor-tag { font-size: 0.72rem; }
.cost-result-card .ecpu-note { font-size: 0.78rem; }
.cost-totals-chip { font-size: 0.82rem; }
.cost-input-hint { font-size: 0.78rem; }
/* References */
.ref-item { padding: 0.65rem 0.75rem; }
.ref-item .ref-text strong { font-size: 0.85rem; }
.ref-item .ref-text span { font-size: 0.72rem; }
/* Notes */
.notes-callout { padding: 0.75rem 1rem; }
.notes-callout li { font-size: 0.82rem; }
.billing-cards { grid-template-columns: 1fr; }
.billing-card h4 { font-size: 0.95rem; }
.billing-card p, .billing-card .billing-source { font-size: 0.82rem; }
.billing-formula .billing-line { font-size: 0.75rem; }
.doc-link { font-size: 0.78rem; }
}
</style>
</head>
<body>
<header class="header">
<div class="header-content">
<div class="header-badge"><span class="dot"></span> Interactive Decision Tool</div>
<h1><span>Oracle</span> Autonomous AI Database — Flavor &amp; Version Advisor</h1>
<p style="font-size:0.84rem;line-height:1.7;color:var(--text-secondary);">
<strong style="color:var(--text-primary);">Serverless</strong> — Standalone · Elastic Pool · Dedicated Elastic Pool<br>
<strong style="color:var(--text-primary);">Dedicated Exadata</strong> — ADB Dedicated · Elastic Pool on Dedicated<br>
<span style="font-size:0.76rem;color:var(--text-muted);">Oracle Database 19c &amp; Oracle AI Database 26ai</span>
</p>
<div style="margin-top:1rem;padding:0.6rem 0.9rem;background:rgba(199,70,52,0.08);border:1px solid rgba(199,70,52,0.2);border-radius:8px;font-size:0.72rem;color:var(--text-secondary);line-height:1.5;">
<strong style="color:var(--oracle-red);">Disclaimer:</strong> This tool is <strong>not an official Oracle document</strong>. It was created to support internal business-case discussions. For official product documentation, pricing, and specifications please refer to the doc links in each flavor card on the <em>When to Choose</em> tab.
</div>
</div>
</header>
<div class="main-content">
<input type="radio" name="main-tab" id="tab-overview" checked>
<input type="radio" name="main-tab" id="tab-scenarios">
<input type="radio" name="main-tab" id="tab-matrix">
<input type="radio" name="main-tab" id="tab-cost">
<nav class="tab-nav">
<label for="tab-overview">When to Choose</label>
<label for="tab-scenarios">Scenarios</label>
<label for="tab-matrix">Decision Matrix</label>
<label for="tab-cost">Cost Estimator</label>
</nav>
<div class="tab-panels">
<!-- ════════════════ TAB 1: OVERVIEW ════════════════ -->
<div class="tab-panel" id="panel-overview">
<div class="notes-callout">
<h3>Assumptions &amp; Notes</h3>
<ul>
<li>Pricing assumptions: ECPUs, BYOL, ATP, list price. No discounts/commits included.</li>
<li>Portability is treated as a single criterion (not split). Within Serverless, joining/leaving an Elastic Pool is click-based; moving to Dedicated Exadata is typically a migration exercise.</li>
<li>Scores are relative (15); importance (0100) can be set independently per criterion to model stakeholder priorities.</li>
<li>For a concrete cost estimate, use the official <a href="https://www.oracle.com/cloud/costestimator.html" target="_blank" style="color:var(--oracle-red);text-decoration:underline;">OCI Cost Estimator</a>.</li>
</ul>
</div>
<div class="section-header-row">
<h2 class="section-title" style="margin-bottom:0">When to Choose Each Option</h2>
<button class="share-link" onclick="copyTabLink('overview')" title="Copy link to this tab">
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1 1 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4 4 0 0 1-.128-1.287z"/><path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243z"/></svg>
<span>Share</span>
</button>
</div>
<p class="section-subtitle">Business driver comparison across the five deployment flavors</p>
<!-- Deployment Hierarchy -->
<div class="hierarchy-container">
<div class="hierarchy-world">
<div class="hierarchy-world-header serverless-header">
<span class="hierarchy-world-icon">☁️</span>
<span class="hierarchy-world-title">Serverless</span>
<span class="hierarchy-world-sub">Oracle-managed shared infrastructure</span>
</div>
<div class="hierarchy-children">
<div class="hierarchy-child shared">
<span class="hierarchy-dot"></span>
<span class="hierarchy-child-name">Serverless</span>
<span class="hierarchy-child-desc">Standalone pay-per-use</span>
</div>
<div class="hierarchy-child spool">
<span class="hierarchy-dot"></span>
<span class="hierarchy-child-name">Elastic Pool</span>
<span class="hierarchy-child-desc">Pooled compute, shared infra</span>
</div>
<div class="hierarchy-child pool">
<span class="hierarchy-dot"></span>
<span class="hierarchy-child-name">Ded. Elastic Pool</span>
<span class="hierarchy-child-desc">Pooled compute+storage, co-located</span>
</div>
</div>
</div>
<div class="hierarchy-world">
<div class="hierarchy-world-header dedicated-header">
<span class="hierarchy-world-icon">🏗️</span>
<span class="hierarchy-world-title">Dedicated</span>
<span class="hierarchy-world-sub">Customer-exclusive Exadata infrastructure</span>
</div>
<div class="hierarchy-children">
<div class="hierarchy-child dedicated">
<span class="hierarchy-dot"></span>
<span class="hierarchy-child-name">ADB Dedicated</span>
<span class="hierarchy-child-desc">Standalone Exadata</span>
</div>
<div class="hierarchy-child dedpool">
<span class="hierarchy-dot"></span>
<span class="hierarchy-child-name">EP (Dedicated)</span>
<span class="hierarchy-child-desc">Elastic Pool on Exadata</span>
</div>
</div>
</div>
</div>
<div class="overview-grid" id="overviewGrid"></div>
</div>
<!-- ════════════════ TAB 2: SCENARIOS ════════════════ -->
<div class="tab-panel" id="panel-scenarios">
<div class="section-header-row">
<h2 class="section-title" style="margin-bottom:0">Scenario Analysis</h2>
<button class="share-link" onclick="copyTabLink('scenarios')" title="Copy link to this tab">
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1 1 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4 4 0 0 1-.128-1.287z"/><path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243z"/></svg>
<span>Share</span>
</button>
</div>
<p class="section-subtitle">When each flavor makes the most business sense</p>
<div id="scenariosContainer"></div>
</div>
<!-- ════════════════ TAB 3: DECISION MATRIX ════════════════ -->
<div class="tab-panel" id="panel-matrix">
<div class="matrix-container">
<div class="matrix-header">
<div>
<div class="section-header-row">
<h2 id="matrixTitle" style="margin-bottom:0">Decision Matrix</h2>
<button class="share-link" onclick="copyTabLink('matrix')" title="Copy link to this tab">
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1 1 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4 4 0 0 1-.128-1.287z"/><path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243z"/></svg>
<span>Share</span>
</button>
</div>
<p id="matrixDesc">Set importance (0100) per criterion to find your best fit</p>
</div>
<div style="display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;">
<div class="scenario-selector">
<label for="matrixSelect">Scenario:</label>
<select id="matrixSelect" onchange="switchMatrix(this.value)">
<optgroup label="By Scale">
<option value="small">Small — 5 DBs, low steady usage</option>
<option value="consolidated">Consolidated — ~25 medium DBs</option>
<option value="saas">High-consolidation SaaS — ~200 tenants / many DBs</option>
<option value="large">Large workloads — ~10 DBs, &gt;100 ECPUs + &gt;100 TB</option>
<option value="ultra">SaaS Ultra Multi-Tenant — ~1,000 DBs</option>
</optgroup>
<optgroup label="By Business Criticality">
<option value="crit_low">🟢 Low Impact — Dev/test, sandbox, PoC</option>
<option value="crit_medium">🟡 Medium Impact — Departmental apps, staging</option>
<option value="crit_high">🔴 High Impact — Mission-critical, ERP, core banking</option>
</optgroup>
</select>
</div>
<button class="reset-btn" onclick="resetMatrix()">↺ Reset</button>
</div>
</div>
<details class="how-to-box">
<summary style="cursor:pointer;font-weight:600;color:var(--text-primary);font-size:0.88rem;">How to use this matrix</summary>
<div style="margin-top:0.6rem;">Each criterion has an <strong>Importance</strong> slider (0100) representing how critical that factor is for your scenario — these are independent, not percentages.
Each flavor receives a <strong>Score</strong> (15) for how well it meets that criterion. You can click the score dots to adjust them.
The weighted result is calculated by normalizing each importance against the total, then multiplying by the score:
<span class="formula-example">Weighted Score = &Sigma; (importance<sub>i</sub> / &Sigma;importances) &times; score<sub>i</sub></span></div>
</details>
<!-- Feature Requirements Filter -->
<details class="feature-matrix-details">
<summary style="cursor:pointer;font-weight:600;color:var(--text-primary);font-size:0.92rem;margin-bottom:0.5rem;">
Feature & Capability Matrix
<span style="font-weight:400;font-size:0.75rem;color:var(--text-muted);margin-left:0.5rem;">Features with differences across flavors or versions — check "Req." to filter</span>
<span style="font-weight:400;font-size:0.68rem;color:var(--text-muted);margin-left:auto;float:right;">Updated: Mar/2026</span>
</summary>
<div id="featureFilterStatus"></div>
<div class="cloud-selector" id="cloudSelector"></div>
<div id="featureMatrixContainer" style="overflow-x:auto;margin-top:0.5rem;"></div>
</details>
<div class="matrix-body">
<div class="matrix-score-panel" id="scorePanel">
<div class="score-summary" id="summary"></div>
</div>
<div class="matrix-main-content">
<div class="criteria-grid" id="grid"></div>
<div class="charts-row" id="chartsRow">
<div class="chart-card">
<div class="chart-title">Flavor Radar — Scores per Criterion</div>
<canvas id="radarChart" width="580" height="520"></canvas>
</div>
<div class="chart-card">
<div class="chart-title">Importance Distribution</div>
<canvas id="pieChart" width="520" height="480"></canvas>
</div>
</div>
<div id="formulaDetail" style="margin-bottom:1.5rem;"></div>
</div>
</div>
</div>
</div>
<!-- ════════════════ TAB: COST ESTIMATOR ════════════════ -->
<div class="tab-panel" id="panel-cost">
<!-- OCI-style green header bar -->
<div class="oci-header-bar">
<div class="oci-header-left">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" style="flex-shrink:0"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="1.5"/><path d="M7 12h10M12 7v10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
<div>
<div class="oci-header-title">Autonomous AI Database — Cost Comparison</div>
<div class="oci-header-sub">BYOL · Transaction Processing · List Price</div>
</div>
</div>
<div class="oci-header-right">
<div class="oci-header-cost-label">Estimated Monthly Cost</div>
<div class="oci-header-cost-value" id="ociHeaderCost">$0</div>
<button class="share-link" onclick="copyTabLink('cost')" title="Copy link to this tab" style="color:#fff;border-color:rgba(255,255,255,0.3);">
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1 1 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4 4 0 0 1-.128-1.287z"/><path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243z"/></svg>
<span>Share</span>
</button>
</div>
</div>
<!-- Configuration Card -->
<div class="oci-config-card">
<div class="oci-config-header">
<div class="oci-config-title">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><rect x="2" y="3" width="16" height="14" rx="2" stroke="currentColor" stroke-width="1.3"/><path d="M6 7h8M6 10h5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
Database Configuration
</div>
<div class="oci-util-badge">
<span class="oci-util-label">Utilization</span>
<span class="oci-util-value">744 hrs/month</span>
</div>
</div>
<!-- Preset selector -->
<div class="oci-preset-row">
<label class="oci-field-label">Load Preset</label>
<select id="costPreset" onchange="loadCostPreset(this.value)" class="oci-select">
<option value="">— Custom —</option>
<option value="small" selected>Small — 5 DBs, 80 ECPUs</option>
<option value="consolidated">Consolidated — 25 DBs, 200 ECPUs</option>
<option value="saas">SaaS — 200 DBs, 400 ECPUs</option>
<option value="large">Large — 10 DBs, 640 ECPUs</option>
<option value="ultra">Ultra Multi-Tenant — 1,000 DBs, 150 real ECPUs</option>
</select>
</div>
<!-- Input fields -->
<div class="oci-fields-grid">
<div class="oci-field">
<label class="oci-field-label"># of Databases</label>
<input type="number" id="ci_dbs" min="1" max="5000" value="5" onchange="calcCosts(true)" oninput="calcCosts(true)" class="oci-input">
</div>
<div class="oci-field">
<label class="oci-field-label">ECPUs per DB</label>
<input type="number" id="ci_ecpu_per_db" min="2" max="5000" value="16" onchange="calcCosts(true)" oninput="calcCosts(true)" class="oci-input">
<span class="oci-field-hint">Min 2 per DB</span>
</div>
<div class="oci-field">
<label class="oci-field-label">Storage per DB (TB)</label>
<input type="number" id="ci_storage_per_db" min="0.02" max="500" value="5" step="0.5" onchange="calcCosts(true)" oninput="calcCosts(true)" class="oci-input">
</div>
<div class="oci-field">
<label class="oci-field-label">Real ECPU Usage (%)</label>
<input type="range" id="ci_usage_pct" min="1" max="100" value="100" oninput="calcCosts(false)" class="oci-slider">
<span class="oci-field-hint" id="usagePctLabel" style="color:var(--pool-color);font-weight:600;">100% — all provisioned ECPUs active</span>
</div>
</div>
<!-- Computed totals -->
<div class="oci-totals-row" id="costTotals"></div>
</div>
<!-- Results cards -->
<div id="costResults"></div>
<!-- Billing model details (collapsible) -->
<details class="oci-billing-details">
<summary>Billing Model Details & Formulas</summary>
<div class="oci-billing-grid">
<div class="billing-card shared">
<h4>ATP Serverless</h4>
<p>Each DB billed independently for <strong>provisioned ECPUs × hours running</strong>. Min 2 ECPUs/DB. Auto-scaling bursts up to 3× base.</p>
<div class="billing-formula">
<span class="billing-line"><em>Compute</em> = Σ ECPUs/DB × $0.0807/hr × 744 h</span>
<span class="billing-line"><em>Storage</em> = GB allocated × $0.1156/mo</span>
<span class="billing-line"><em>Backup</em> = GB used × $0.0244/mo</span>
</div>
<a class="doc-link" href="https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/autonomous-features-billing.html" target="_blank"><svg viewBox="0 0 16 16" fill="currentColor"><path d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z"/><path d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z"/></svg> Billing Docs</a>
</div>
<div class="billing-card spool">
<h4>Elastic Pool (Serverless)</h4>
<p>Compute billed to <strong>pool leader</strong>. Members can have 1 ECPU min. Storage billed per-DB.</p>
<div class="billing-formula">
<span class="billing-line"><em>Compute</em> = Pool shape × tier × $0.0807/hr × 744 h</span>
<span class="billing-line"><em>Tiers</em> peak ≤ 1× → 1×; ≤ 2× → 2×; > 2× → 4×</span>
<span class="billing-line"><em>Shapes</em> 128, 256, 512, 1024, 2048, 4096</span>
</div>
<a class="doc-link" href="https://docs.oracle.com/en-us/iaas/autonomous-database-serverless/doc/elastic-pools-about-billing.html" target="_blank"><svg viewBox="0 0 16 16" fill="currentColor"><path d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z"/><path d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z"/></svg> Billing Docs</a>
</div>
<div class="billing-card pool">
<h4>Dedicated Elastic Pool</h4>
<p>Both <strong>compute + storage billed to leader</strong>. Same tiered billing. Min: 256 ECPUs / 256 TB.</p>
<div class="billing-formula">
<span class="billing-line"><em>Compute</em> = Pool shape × tier × $0.0807/hr × 744 h</span>
<span class="billing-line"><em>Shapes</em> 256, 512, 1024</span>
<span class="billing-line"><em>Storage</em> = Pool total TB × $0.1156/GB/mo</span>
</div>
<a class="doc-link" href="https://docs.oracle.com/en-us/iaas/autonomous-database-serverless/doc/Dedicated-EP-Billing.html" target="_blank"><svg viewBox="0 0 16 16" fill="currentColor"><path d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z"/><path d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z"/></svg> Billing Docs</a>
</div>
<div class="billing-card dedicated">
<h4>ADB Dedicated X11M</h4>
<p>Compute per <strong>provisioned ECPUs</strong> + <strong>Exadata infra</strong> (DB + Storage servers). Backup via Object Storage.</p>
<div class="billing-formula">
<span class="billing-line"><em>Compute</em> = ECPUs × $0.0807/hr × 744 h</span>
<span class="billing-line"><em>DB srvs</em> = max(2, ⌈ECPU÷760⌉) × $2.90/h</span>
<span class="billing-line"><em>ST srvs</em> = max(3, ⌈TB÷80⌉) × $2.90/h</span>
</div>
<a class="doc-link" href="https://docs.oracle.com/en-us/iaas/autonomous-database/doc/billing-autonomous-database-dedicated-exadata-infrastructure.html" target="_blank"><svg viewBox="0 0 16 16" fill="currentColor"><path d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z"/><path d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z"/></svg> Billing Docs</a>
</div>
<div class="billing-card dedpool">
<h4>Elastic Pool (Dedicated)</h4>
<p>Elastic Pool on <strong>Dedicated Exadata</strong>. Pool compute billing + Exadata servers. Storage billed per instance.</p>
<div class="billing-formula">
<span class="billing-line"><em>Compute</em> = Pool shape × tier × $0.0807/hr</span>
<span class="billing-line"><em>Tiers</em> same as Serverless EP (1×/2×/4×)</span>
<span class="billing-line"><em>Infra</em> = DB + ST servers × $2.90/h</span>
</div>
<a class="doc-link" href="https://docs.oracle.com/en/cloud/paas/autonomous-database/dedicated/catiu/index.html" target="_blank"><svg viewBox="0 0 16 16" fill="currentColor"><path d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z"/><path d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z"/></svg> Billing Docs</a>
</div>
</div>
</details>
<div class="oci-verify-link">
Verify estimates with <a href="https://www.oracle.com/cloud/costestimator.html" target="_blank">OCI Cost Estimator ↗</a>
</div>
</div>
<!-- ════════════════ TAB: REFERENCES ════════════════ -->
<script src="adb-data.js"></script>
<script>
Object.keys(matrices).forEach(k => {
matrices[k].origWeights = matrices[k].criteria.map(c => c.weight);
matrices[k].weights = matrices[k].criteria.map(c => c.weight);
});
let currentMatrix = 'A';
function computeScores(m) {
const tw = m.weights.reduce((a,b) => a+b, 0);
if (tw === 0) return [0,0,0,0];
const s = Array(FLAVORS.length).fill(0);
m.criteria.forEach((c,i) => { for(let f=0;f<FLAVORS.length;f++) s[f] += (m.weights[i]/tw)*c.scores[f]; });
return s.map(v => Math.round(v*100)/100);
}
function getWinners(scores) { const mx=Math.max(...scores); return scores.reduce((a,s,i)=>s===mx?[...a,i]:a,[]); }
function renderSummary(m) {
const tw = m.weights.reduce((a,b)=>a+b,0);
const scores = computeScores(m), winners = getWinners(scores);
document.getElementById('summary').innerHTML = scores.map((s,i) => {
const pct = (s/5*100).toFixed(0), win = winners.includes(i);
return `<div class="score-chip ${FLAVORS[i].cls}" data-flavor-idx="${i}"><div style="flex:1">
<div class="label">${FLAVORS[i].name}<span class="ver-rec" id="verRec${i}"></span></div>
<div style="display:flex;align-items:center;gap:0.5rem">
<span class="value">${s.toFixed(2)}</span>
${win ? `<span class="winner-badge ${FLAVORS[i].cls}">\u2605 Best fit</span>` : ''}
</div>
<div class="bar-bg"><div class="bar-fill" style="width:${pct}%"></div></div>
</div></div>`;
}).join('');
// Build formula breakdown
if (tw === 0) { document.getElementById('formulaDetail').innerHTML = ''; return; }
const flavorsHTML = Array.from({length:FLAVORS.length},(_,f)=>f).map(f => {
const lines = m.criteria.map((c,i) => {
const norm = (m.weights[i]/tw);
const contrib = norm * c.scores[f];
return `<div class="step-line"><span class="step-op">+</span><span>(${m.weights[i]}/${tw}) \u00d7 ${c.scores[f]} = ${contrib.toFixed(3)}</span><span style="color:var(--text-muted);margin-left:0.5rem;font-style:italic">${c.name}</span></div>`;
}).join('');
return `<div class="formula-flavor">
<div class="formula-flavor-header"><span class="formula-flavor-name ${FLAVORS[f].cls}">${FLAVORS[f].name}</span></div>
<div class="formula-steps">
${lines}
<div class="step-line formula-total"><span class="step-op">=</span><span>${scores[f].toFixed(2)}</span></div>
</div>
</div>`;
}).join('');
document.getElementById('formulaDetail').innerHTML = `
<details class="formula-panel">
<summary>Show calculation details</summary>
${flavorsHTML}
</details>`;
// Render charts
renderRadar(m, scores);
renderPie(m);
// Re-apply feature filters (checkboxes survive in separate DOM)
if (typeof applyFeatureFilters === 'function') applyFeatureFilters();
}
// ═══════════════════════════════════════════════════════════════════════════
// RADAR CHART — Flavor scores per criterion (like spider/web chart)
// ═══════════════════════════════════════════════════════════════════════════
function renderRadar(m, scores) {
const canvas = document.getElementById('radarChart');
if (!canvas) return;
const dpr = window.devicePixelRatio || 1;
const W = 580, H = 520;
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, W, H);
const cx = W / 2, cy = H / 2 - 8;
const maxR = Math.min(W, H) * 0.27; // smaller polygon = more label room
const n = m.criteria.length;
if (n < 3) return;
const angleStep = (2 * Math.PI) / n;
const startAngle = -Math.PI / 2;
const flavorColors = ['#3B82F6','#A855F7','#F59E0B','#10B981','#06B6D4'];
const flavorAlphas = ['#3B82F619','#A855F719','#F59E0B19','#10B98119','#06B6D419'];
// Grid rings (15)
for (let ring = 1; ring <= 5; ring++) {
const r = (ring / 5) * maxR;
ctx.beginPath();
for (let i = 0; i <= n; i++) {
const a = startAngle + i * angleStep;
const x = cx + r * Math.cos(a), y = cy + r * Math.sin(a);
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
}
ctx.closePath();
ctx.strokeStyle = ring === 5 ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.06)';
ctx.lineWidth = ring === 5 ? 1.2 : 0.7;
ctx.stroke();
// Ring number
if (ring % 2 === 0 || ring === 5) {
ctx.fillStyle = 'rgba(255,255,255,0.18)';
ctx.font = '400 9px "JetBrains Mono", monospace';
ctx.textAlign = 'left';
ctx.textBaseline = 'bottom';
ctx.fillText(ring, cx + 3, cy - r - 2);
}
}
// Axis lines
for (let i = 0; i < n; i++) {
const a = startAngle + i * angleStep;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.lineTo(cx + maxR * Math.cos(a), cy + maxR * Math.sin(a));
ctx.strokeStyle = 'rgba(255,255,255,0.07)';
ctx.lineWidth = 0.7;
ctx.stroke();
}
// Flavor polygons
for (let f = 0; f < FLAVORS.length; f++) {
ctx.beginPath();
for (let i = 0; i <= n; i++) {
const idx = i % n;
const a = startAngle + idx * angleStep;
const val = m.criteria[idx].scores[f] / 5;
const x = cx + val * maxR * Math.cos(a), y = cy + val * maxR * Math.sin(a);
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fillStyle = flavorAlphas[f];
ctx.fill();
ctx.strokeStyle = flavorColors[f];
ctx.lineWidth = 2.2;
ctx.stroke();
// Dots
for (let i = 0; i < n; i++) {
const a = startAngle + i * angleStep;
const val = m.criteria[i].scores[f] / 5;
const x = cx + val * maxR * Math.cos(a), y = cy + val * maxR * Math.sin(a);
ctx.beginPath();
ctx.arc(x, y, 3.5, 0, 2 * Math.PI);
ctx.fillStyle = flavorColors[f];
ctx.fill();
}
}
// Labels — generous spacing, multi-line wrap, never clipped
const labelFont = '500 12.5px "DM Sans", sans-serif';
ctx.fillStyle = '#57534E';
for (let i = 0; i < n; i++) {
const a = startAngle + i * angleStep;
const cosA = Math.cos(a), sinA = Math.sin(a);
const labelR = maxR + 30;
let lx = cx + labelR * cosA;
let ly = cy + labelR * sinA;
// Alignment
if (cosA < -0.15) ctx.textAlign = 'right';
else if (cosA > 0.15) ctx.textAlign = 'left';
else ctx.textAlign = 'center';
if (sinA < -0.6) ctx.textBaseline = 'bottom';
else if (sinA > 0.6) ctx.textBaseline = 'top';
else ctx.textBaseline = 'middle';
ctx.font = labelFont;
const name = m.criteria[i].name;
// Word wrap into lines ≤ 14 chars
const words = name.split(/[\s-]+/);
const rawParts = [];
// Preserve hyphens
let pos = 0;
words.forEach(w => {
const idx = name.indexOf(w, pos);
const sep = idx > 0 && name[idx - 1] === '-' ? '-' : '';
rawParts.push(sep + w);
pos = idx + w.length;
});
const lines = [];
let cur = '';
rawParts.forEach(w => {
const test = cur ? cur + ' ' + w : w;
if (test.length > 14 && cur) { lines.push(cur.trim()); cur = w; }
else { cur = test; }
});
if (cur) lines.push(cur.trim());
const lineH = 15;
const totalH = lines.length * lineH;
let baseY = ly;
if (ctx.textBaseline === 'middle') baseY = ly - (totalH - lineH) / 2;
else if (ctx.textBaseline === 'bottom') baseY = ly - totalH + lineH;
lines.forEach((line, li) => {
ctx.fillStyle = '#57534E';
ctx.fillText(line, lx, baseY + li * lineH);
});
}
// Legend — centered at bottom
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
const legY = H - 16;
const legItemW = 128;
const legTotalW = 4 * legItemW;
const legStartX = (W - legTotalW) / 2;
for (let f = 0; f < FLAVORS.length; f++) {
const lx = legStartX + f * legItemW;
ctx.fillStyle = flavorColors[f];
ctx.fillRect(lx, legY - 6, 14, 14);
ctx.strokeStyle = 'rgba(0,0,0,0.3)';
ctx.lineWidth = 1;
ctx.strokeRect(lx, legY - 6, 14, 14);
ctx.fillStyle = '#57534E';
ctx.font = '500 11.5px "DM Sans", sans-serif';
ctx.fillText(FLAVORS[f].short, lx + 19, legY + 1);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// PIE / DONUT CHART — Importance weight distribution
// ═══════════════════════════════════════════════════════════════════════════
function renderPie(m) {
const canvas = document.getElementById('pieChart');
if (!canvas) return;
const dpr = window.devicePixelRatio || 1;
const W = 520, H = 480;
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, W, H);
const cx = W / 2, cy = H / 2 - 5;
const outerR = Math.min(W, H) * 0.30;
const innerR = outerR * 0.52;
const total = m.weights.reduce((a, b) => a + b, 0);
if (total === 0) return;
// Color palette — muted blues/teals matching dark theme
const palette = [
'#3B82F6','#10B981','#F59E0B','#A855F7','#EC4899',
'#06B6D4','#F87171','#84CC16','#D4A85C','#818CF8'
];
// Sort by weight descending for better visual
const sorted = m.criteria.map((c, i) => ({ name: c.name, weight: m.weights[i], idx: i }))
.sort((a, b) => b.weight - a.weight);
let angle = -Math.PI / 2;
const slices = [];
sorted.forEach((item, si) => {
const sliceAngle = (item.weight / total) * 2 * Math.PI;
const midAngle = angle + sliceAngle / 2;
const color = palette[si % palette.length];
// Draw slice
ctx.beginPath();
ctx.arc(cx, cy, outerR, angle, angle + sliceAngle);
ctx.arc(cx, cy, innerR, angle + sliceAngle, angle, true);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = 'rgba(15,14,13,0.6)';
ctx.lineWidth = 2;
ctx.stroke();
slices.push({ name: item.name, weight: item.weight, pct: ((item.weight / total) * 100).toFixed(0), midAngle, color });
angle += sliceAngle;
});
// Labels — outside with leader lines, anti-collision
const labelFont = '500 11.5px "DM Sans", sans-serif';
const pctFont = '700 11.5px "JetBrains Mono", monospace';
ctx.font = labelFont;
const labelR = outerR + 16;
const lineR = outerR + 5;
const elbowR = labelR + 18;
slices.forEach(s => {
if (parseFloat(s.pct) < 3) return; // skip tiny slices
const cosM = Math.cos(s.midAngle), sinM = Math.sin(s.midAngle);
const isLeft = cosM < 0;
// Leader line: radial → elbow → horizontal
const startX = cx + lineR * cosM, startY = cy + lineR * sinM;
const elbowX = cx + elbowR * cosM, elbowY = cy + elbowR * sinM;
const endX = elbowX + (isLeft ? -20 : 20);
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(elbowX, elbowY);
ctx.lineTo(endX, elbowY);
ctx.strokeStyle = 'rgba(255,255,255,0.2)';
ctx.lineWidth = 0.9;
ctx.stroke();
// Dot at start
ctx.beginPath();
ctx.arc(startX, startY, 2, 0, 2 * Math.PI);
ctx.fillStyle = s.color;
ctx.fill();
// Text
const tx = endX + (isLeft ? -5 : 5);
ctx.textAlign = isLeft ? 'right' : 'left';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#1C1917';
ctx.font = labelFont;
ctx.fillText(s.name + ',', tx, elbowY - 1);
ctx.fillStyle = s.color;
ctx.font = pctFont;
// Measure name width to place pct inline
ctx.font = labelFont;
const nameW = ctx.measureText(s.name + ', ').width;
ctx.font = pctFont;
if (isLeft) {
ctx.textAlign = 'right';
ctx.fillText(' ' + s.pct + '%', tx - nameW, elbowY - 1);
} else {
ctx.textAlign = 'left';
ctx.fillText(' ' + s.pct + '%', tx + nameW, elbowY - 1);
}
});
// Center text
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#1C1917';
ctx.font = '700 24px "JetBrains Mono", monospace';
ctx.fillText(total, cx, cy - 7);
ctx.fillStyle = '#A8A29E';
ctx.font = '500 12px "DM Sans", sans-serif';
ctx.fillText('Total Weight', cx, cy + 14);
}
function buildInfoTrigger(name, idx) {
const info = INFO[name];
if (!info) return '';
return `<span class="info-trigger" onclick="toggleInfo(${idx})" id="infoBtn-${idx}">i</span>`;
}
function buildInfoPanel(name, idx) {
const info = INFO[name];
if (!info) return '';
const flavorHTML = info.flavors.map((t,f) => `<div class="info-flavor">
<span class="info-flavor-label ${FLAVORS[f].cls}">${FLAVORS[f].short}</span>
<span class="info-flavor-text">${t}</span>
</div>`).join('');
return `<div class="info-panel" id="infoPanel-${idx}">
<div class="info-desc">${info.desc}</div>
${flavorHTML}
</div>`;
}
function toggleInfo(idx) {
const panel = document.getElementById('infoPanel-' + idx);
const btn = document.getElementById('infoBtn-' + idx);
const isOpen = panel.classList.contains('open');
// Close all others
document.querySelectorAll('.info-panel.open').forEach(p => p.classList.remove('open'));
document.querySelectorAll('.info-trigger.active').forEach(b => b.classList.remove('active'));
if (!isOpen) {
panel.classList.add('open');
btn.classList.add('active');
}
}
function renderGrid(m) {
document.getElementById('grid').innerHTML = m.criteria.map((c,i) => {
const scoresHTML = c.scores.map((s,f) => {
const dots = Array.from({length:5},(_,d) =>
`<div class="score-dot ${d<s?'filled '+FLAVORS[f].cls:''}" data-c="${i}" data-f="${f}" data-d="${d+1}" onclick="setScore(${i},${f},${d+1})"></div>`
).join('');
return `<div class="score-cell ${FLAVORS[f].cls}"><div class="score-label">${FLAVORS[f].short}</div><div class="score-dots">${dots}</div></div>`;
}).join('');
return `<div class="criteria-row">
<div class="criteria-left">
<div class="criteria-name-row"><span class="criteria-name">${c.name}</span>${buildInfoTrigger(c.name, i)}</div>
<div class="weight-control">
<label>Importance</label>
<input type="range" min="0" max="100" value="${m.weights[i]}" oninput="updateWeight(${i},this.value)">
<span class="weight-pct" id="wpct-${i}">${m.weights[i]}</span>
</div>
</div>
<div class="criteria-right"><div class="scores-row">${scoresHTML}</div></div>
</div>${buildInfoPanel(c.name, i)}`;
}).join('');
}
function updateWeight(idx,val) {
const m = matrices[currentMatrix]; m.weights[idx]=parseInt(val);
document.getElementById(`wpct-${idx}`).textContent=val; renderSummary(m);
}
function setScore(ci,fi,ns) {
const m = matrices[currentMatrix]; m.criteria[ci].scores[fi]=ns;
document.querySelectorAll(`.score-dot[data-c="${ci}"][data-f="${fi}"]`).forEach(d => {
d.className = parseInt(d.dataset.d)<=ns ? `score-dot filled ${FLAVORS[fi].cls}` : 'score-dot';
});
renderSummary(m);
}
function switchMatrix(key) {
currentMatrix=key; const m=matrices[key];
document.getElementById('matrixTitle').textContent=m.title;
document.getElementById('matrixDesc').textContent=m.desc+' \u2022 Set importance (0\u2013100) per criterion to find your best fit';
renderGrid(m); renderSummary(m);
}
function resetMatrix() {
const m=matrices[currentMatrix];
m.origWeights.forEach((w,i)=>{m.weights[i]=w;});
m.origScores.forEach((s,i)=>{m.criteria[i].scores=[...s];});
renderGrid(m); renderSummary(m);
}
switchMatrix('small');
function renderStars(n, flavorCls) {
const pct = (n / 5 * 100).toFixed(0);
return `<div class="score-bar-wrap"><span class="score-bar-num">${n}/5</span><div class="score-bar-track"><div class="score-bar-fill ${flavorCls || ''}" style="width:${pct}%"></div></div></div>`;
}
function renderOverview() {
const flavorLabels = FLAVORS.map(fl => fl.name);
const flavorCls = FLAVORS.map(fl => fl.cls);
const flavorDocKeys = ["shared","spool","pool","dedicated"];
document.getElementById('overviewGrid').innerHTML = OVERVIEW_CARDS.map(card => {
const rows = Array.from({length:FLAVORS.length},(_,f)=>f).map(f => `
<div class="flavor-row">
<span class="flavor-label ${flavorCls[f]}">${flavorLabels[f]}</span>
<div class="flavor-row-wrap">
<div class="flavor-stars">${renderStars(card.stars[f], flavorCls[f])}</div>
<span class="flavor-desc">${card.descs[f]}</span>
<a class="overview-doc-link" href="${DOC_LINKS[flavorDocKeys[f]]}" target="_blank" title="Oracle Docs">\uD83D\uDD17</a>
</div>
</div>`).join('');
return `<div class="driver-card">
<h3><span class="icon" style="background:${card.iconBg};color:${card.iconColor};">${card.icon}</span> ${card.title}</h3>
${rows}
</div>`;
}).join('');
}
renderOverview();
function loadCostPreset(key) {
if (!key || !COST_PRESETS[key]) return;
const p = COST_PRESETS[key];
document.getElementById('ci_dbs').value = p.dbs;
document.getElementById('ci_ecpu_per_db').value = p.ecpuPerDb;
document.getElementById('ci_storage_per_db').value = p.storPerDb;
document.getElementById('ci_usage_pct').value = p.usagePct;
calcCosts(false);
}
function calcCosts(updateBackupDefault) {
const P = PRICING;
const dbs = Math.max(1, parseInt(document.getElementById('ci_dbs').value) || 1);
const ecpuPerDb = Math.max(2, parseInt(document.getElementById('ci_ecpu_per_db').value) || 2);
const storPerDb = Math.max(0.02, parseFloat(document.getElementById('ci_storage_per_db').value) || 0.02);
const usagePct = Math.max(1, Math.min(100, parseInt(document.getElementById('ci_usage_pct').value) || 100));
// Computed totals
const provEcpu = dbs * ecpuPerDb;
const storageTB = dbs * storPerDb;
const backupTB = Math.round(storageTB / 3 * 10) / 10; // 1/3 of storage
const realEcpu = Math.max(1, Math.round(provEcpu * usagePct / 100));
// Usage % label
const pctLabel = document.getElementById('usagePctLabel');
if (usagePct < 100) {
pctLabel.innerHTML = `${usagePct}% \u2014 <strong>${realEcpu.toLocaleString()}</strong> real ECPUs of ${provEcpu.toLocaleString()} provisioned`;
pctLabel.style.color = 'var(--dedicated-color)';
} else {
pctLabel.textContent = `100% \u2014 all ${provEcpu.toLocaleString()} provisioned ECPUs active`;
pctLabel.style.color = 'var(--pool-color)';
}
// Totals chips
document.getElementById('costTotals').innerHTML = [
`<span class="cost-totals-chip">Total ECPUs: <strong>${provEcpu.toLocaleString()}</strong></span>`,
usagePct < 100 ? `<span class="cost-totals-chip highlight">Real Usage: <strong>${realEcpu.toLocaleString()} ECPUs</strong> (${usagePct}%)</span>` : '',
`<span class="cost-totals-chip">Storage: <strong>${storageTB.toLocaleString()} TB</strong></span>`,
`<span class="cost-totals-chip">Backup: <strong>${backupTB.toLocaleString()} TB</strong> (\u2153 storage)</span>`,
].filter(Boolean).join('');
// ── SERVERLESS ──
const sharedCompute = provEcpu * P.hrsMonth * P.ecpuHr;
const sharedStorage = storageTB * 1024 * P.storageGbMo;
const sharedBackup = backupTB * 1024 * P.backupGbMo;
const sharedTotal = sharedCompute + sharedStorage + sharedBackup;
// ── ELASTIC POOL (Serverless) ──
// Pool shapes: 128, 256, 512, 1024, 2048, 4096 ECPUs
// Pool capacity = 4× pool shape (max provisionable ECPUs in pool)
// Billing tiers per hour based on aggregate peak usage:
// usage ≤ 1× shape → billed 1× shape
// usage > 1× and ≤ 2× shape → billed 2× shape
// usage > 2× and ≤ 4× shape → billed 4× shape
// Inside pool: min 1 ECPU per DB (vs 2 outside)
// Storage billed per-DB individually (same as Serverless).
const POOL_SHAPES = [128, 256, 512, 1024, 2048, 4096];
// Total ECPUs that need to fit in pool capacity (4× shape)
// Inside pool each DB can be 1 ECPU min, but user specified ecpuPerDb
const totalPoolEcpus = dbs * Math.max(1, ecpuPerDb);
// Find smallest shape whose capacity (4×) can hold all provisioned ECPUs
let spoolShape = POOL_SHAPES.find(s => s * 4 >= totalPoolEcpus) || 4096;
// If even 4096×4=16384 is not enough, need multiple pools
const spoolPoolCount = spoolShape * 4 >= totalPoolEcpus ? 1 : Math.ceil(totalPoolEcpus / (4096 * 4));
if (spoolPoolCount > 1) spoolShape = 4096;
// Determine billing tier: what gets billed per hour based on real usage
const spoolRealPerPool = Math.ceil(realEcpu / spoolPoolCount);
let spoolBilledEcpus;
if (spoolRealPerPool <= spoolShape) spoolBilledEcpus = spoolShape; // 1× tier
else if (spoolRealPerPool <= spoolShape * 2) spoolBilledEcpus = spoolShape * 2; // 2× tier
else spoolBilledEcpus = spoolShape * 4; // 4× tier
spoolBilledEcpus *= spoolPoolCount;
const spoolTierLabel = spoolRealPerPool <= spoolShape ? '1×' :
spoolRealPerPool <= spoolShape * 2 ? '2×' : '4×';
const spoolCompute = spoolBilledEcpus * P.hrsMonth * P.ecpuHr;
const spoolStorage = storageTB * 1024 * P.storageGbMo;
const spoolBackup = backupTB * 1024 * P.backupGbMo;
const spoolTotal = spoolCompute + spoolStorage + spoolBackup;
// ── DEDICATED ELASTIC POOL ──
// Shapes: 256 ECPUs min (with 256 TB storage)
// Same tiered billing as Serverless pool, plus storage consolidated to leader
const DED_POOL_SHAPES = [256, 512, 1024];
let poolShape = DED_POOL_SHAPES.find(s => s * 4 >= totalPoolEcpus) || 1024;
const poolPoolCount = poolShape * 4 >= totalPoolEcpus ? 1 : Math.ceil(totalPoolEcpus / (1024 * 4));
if (poolPoolCount > 1) poolShape = 1024;
const poolRealPerPool = Math.ceil(realEcpu / poolPoolCount);
let poolBilledEcpus;
if (poolRealPerPool <= poolShape) poolBilledEcpus = poolShape;
else if (poolRealPerPool <= poolShape * 2) poolBilledEcpus = poolShape * 2;
else poolBilledEcpus = poolShape * 4;
poolBilledEcpus *= poolPoolCount;
const poolTierLabel = poolRealPerPool <= poolShape ? '1×' :
poolRealPerPool <= poolShape * 2 ? '2×' : '4×';
const poolStorTB = Math.max(P.poolMinStorTB, storageTB);
const poolCompute = poolBilledEcpus * P.hrsMonth * P.ecpuHr;
const poolStorage = poolStorTB * 1024 * P.storageGbMo;
const poolBackup = backupTB * 1024 * P.backupGbMo;
const poolTotal = poolCompute + poolStorage + poolBackup;
// ── DEDICATED X11M ──
const dedEcpu = provEcpu;
const dedCompute = dedEcpu * P.hrsMonth * P.ecpuHr;
const dbServers = Math.max(P.minDbServers, Math.ceil(dedEcpu / P.dbSrvEcpuCap));
const stServers = Math.max(P.minStServers, Math.ceil(storageTB / P.stSrvStorCap));
const dedInfra = (dbServers * P.x11mDbSrvHr + stServers * P.x11mStSrvHr) * P.hrsMonth;
const dedBackup = Math.max(0, backupTB * 1024 - 10) * P.objStorageGbMo;
const dedTotal = dedCompute + dedInfra + dedBackup;
// ── DEDICATED ELASTIC POOL (Exadata) ──
// Same Exadata infra as ADB Dedicated + pool billing (same shapes/tiers as Serverless EP)
// Pool shapes: 128, 256, 512, 1024, 2048, 4096
let dedEpShape = POOL_SHAPES.find(s => s * 4 >= totalPoolEcpus) || 4096;
const dedEpPoolCount = dedEpShape * 4 >= totalPoolEcpus ? 1 : Math.ceil(totalPoolEcpus / (4096 * 4));
if (dedEpPoolCount > 1) dedEpShape = 4096;
const dedEpRealPerPool = Math.ceil(realEcpu / dedEpPoolCount);
let dedEpBilledEcpus;
if (dedEpRealPerPool <= dedEpShape) dedEpBilledEcpus = dedEpShape;
else if (dedEpRealPerPool <= dedEpShape * 2) dedEpBilledEcpus = dedEpShape * 2;
else dedEpBilledEcpus = dedEpShape * 4;
dedEpBilledEcpus *= dedEpPoolCount;
const dedEpTierLabel = dedEpRealPerPool <= dedEpShape ? '1×' :
dedEpRealPerPool <= dedEpShape * 2 ? '2×' : '4×';
const dedEpCompute = dedEpBilledEcpus * P.hrsMonth * P.ecpuHr;
// Infra: same Exadata servers as Dedicated, but sized for pool capacity
const dedEpDbServers = Math.max(P.minDbServers, Math.ceil((dedEpShape * 4 * dedEpPoolCount) / P.dbSrvEcpuCap));
const dedEpStServers = Math.max(P.minStServers, Math.ceil(storageTB / P.stSrvStorCap));
const dedEpInfra = (dedEpDbServers * P.x11mDbSrvHr + dedEpStServers * P.x11mStSrvHr) * P.hrsMonth;
const dedEpBackup = Math.max(0, backupTB * 1024 - 10) * P.objStorageGbMo;
const dedEpTotal = dedEpCompute + dedEpInfra + dedEpBackup;
// Find winner
const totals = [sharedTotal, spoolTotal, poolTotal, dedTotal, dedEpTotal];
const minTotal = Math.min(...totals);
// Update OCI-style green header with lowest cost
const hdrEl = document.getElementById('ociHeaderCost');
if (hdrEl) hdrEl.textContent = '$' + Math.round(minTotal).toLocaleString('en-US');
const spoolSavingVsProv = `Shape ${spoolShape.toLocaleString()} ECPUs (cap ${(spoolShape*4).toLocaleString()})${spoolPoolCount > 1 ? ' × '+spoolPoolCount+' pools' : ''} — billed ${spoolTierLabel} = ${spoolBilledEcpus.toLocaleString()} ECPUs`;
const poolSavingVsProv = `Shape ${poolShape.toLocaleString()} ECPUs (cap ${(poolShape*4).toLocaleString()})${poolPoolCount > 1 ? ' × '+poolPoolCount+' pools' : ''} — billed ${poolTierLabel} = ${poolBilledEcpus.toLocaleString()} ECPUs`;
const dedEpSavingVsProv = `Shape ${dedEpShape.toLocaleString()} ECPUs (cap ${(dedEpShape*4).toLocaleString()})${dedEpPoolCount > 1 ? ' × '+dedEpPoolCount+' pools' : ''} — billed ${dedEpTierLabel} = ${dedEpBilledEcpus.toLocaleString()} ECPUs`;
const flavors = [
{ name: 'ATP Serverless', cls: 'shared', total: sharedTotal, compute: sharedCompute, storage: sharedStorage, backup: sharedBackup, infra: 0,
ecpuLabel: `${provEcpu.toLocaleString()} prov. ECPUs`,
ecpuNote: null },
{ name: 'Elastic Pool', cls: 'spool', total: spoolTotal, compute: spoolCompute, storage: spoolStorage, backup: spoolBackup, infra: 0,
ecpuLabel: `Compute (${spoolBilledEcpus.toLocaleString()} billed ECPUs)`,
ecpuNote: spoolSavingVsProv },
{ name: 'Ded. Elastic Pool', cls: 'pool', total: poolTotal, compute: poolCompute, storage: poolStorage, backup: poolBackup, infra: 0,
ecpuLabel: `Compute (${poolBilledEcpus.toLocaleString()} billed ECPUs)`,
ecpuNote: poolSavingVsProv },
{ name: 'ADB Dedicated X11M', cls: 'dedicated', total: dedTotal, compute: dedCompute, storage: 0, backup: dedBackup, infra: dedInfra,
ecpuLabel: `${dedEcpu.toLocaleString()} prov. ECPUs`,
ecpuNote: `${dbServers} DB + ${stServers} Storage servers (X11M)` },
{ name: 'EP (Dedicated)', cls: 'dedpool', total: dedEpTotal, compute: dedEpCompute, storage: 0, backup: dedEpBackup, infra: dedEpInfra,
ecpuLabel: `Compute (${dedEpBilledEcpus.toLocaleString()} billed ECPUs)`,
ecpuNote: dedEpSavingVsProv + ` | ${dedEpDbServers} DB + ${dedEpStServers} ST servers` },
];
// Render result cards
const fmt = v => '$' + Math.round(v).toLocaleString('en-US');
document.getElementById('costResults').innerHTML = '<div class="cost-result-cards">' + flavors.map(f => {
const isWinner = Math.abs(f.total - minTotal) < 1;
const pct = isWinner ? '' : `<div class="pct-vs" style="color:#F87171">+${Math.round(((f.total - minTotal) / minTotal) * 100)}% vs lowest</div>`;
const winLabel = isWinner ? '<span class="winner-label">\u2714 Lowest</span>' : '';
const winCls = isWinner ? ' winner' : '';
const infraLine = f.infra > 0 ? `<div class="breakdown-line"><span>Exadata infra</span><span>${fmt(f.infra)}</span></div>` : '';
const ecpuNote = f.ecpuNote ? `<div class="ecpu-note">${f.ecpuNote}</div>` : '';
return `<div class="cost-result-card${winCls}">
${winLabel}
<span class="flavor-tag ${f.cls}">${f.name}</span>
<div class="total-price ${f.cls}">${fmt(f.total)}<span style="font-size:0.65rem;font-weight:400;color:var(--text-muted)">/mo</span></div>
${pct}
<div class="breakdown-line"><span>Compute (${f.ecpuLabel})</span><span>${fmt(f.compute)}</span></div>
${infraLine}
<div class="breakdown-line"><span>Storage</span><span>${f.storage > 0 ? fmt(f.storage) : '\u2014 (incl. in infra)'}</span></div>
<div class="breakdown-line"><span>Backup</span><span>${fmt(f.backup)}</span></div>
${ecpuNote}
</div>`;
}).join('') + '</div>';
}
// ═══════════════════════════════════════════════════════════════════════════
// SCENARIO TABLES — rendered from SCENARIOS data
// ═══════════════════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════════════════
// FEATURE MATRIX — interactive table + requirement filters
// ═══════════════════════════════════════════════════════════════════════════
function renderFeatureMatrix() {
if (typeof FEATURES === 'undefined') return;
// Render cloud platform selector
if (typeof CLOUD_PLATFORMS !== 'undefined') {
const cs = document.getElementById('cloudSelector');
if (cs) {
cs.innerHTML = '<span class="cloud-selector-label">Cloud platform:</span>' +
CLOUD_PLATFORMS.map((cp, i) =>
`<button class="cloud-btn${i === 0 ? ' active' : ''}" data-cidx="${i}" onclick="toggleCloud(this)">${cp.icon} ${cp.name}</button>` +
(cp.doc ? `<a href="${cp.doc}" target="_blank" class="cloud-doc-link" title="Regional availability for Oracle Database@${cp.name}">🔗</a>` : '')
).join('');
}
}
const container = document.getElementById('featureMatrixContainer');
if (!container) return;
const categories = [...new Set(FEATURES.map(f => f.category))];
const flavorNames = FLAVORS.map(fl => fl.short);
const flavorCls = FLAVORS.map(fl => fl.cls);
const versionBadge = v => v === '26ai' ? '<span class="ver-badge v26">26ai</span>' : v === '19c' ? '<span class="ver-badge v19">19c</span>' : v === 'both' ? '<span class="ver-badge vboth">19c</span> <span class="ver-badge vboth">26ai</span>' : '';
const availIcon = ok => ok ? '<span class="avail-yes">✓</span>' : '<span class="avail-no">✗</span>';
let html = '<table class="feature-table"><thead><tr>';
html += '<th style="text-align:left;min-width:200px">Feature / Capability</th>';
html += '<th style="width:60px">Version</th>';
flavorNames.forEach((n, i) => {
html += `<th style="color:var(--${flavorCls[i]}-color);min-width:70px">${n}</th>`;
});
html += '<th style="width:40px;text-align:center" title="Check to require this feature">Req.</th>';
html += '</tr></thead><tbody>';
categories.forEach(cat => {
html += `<tr class="feat-cat-row"><td colspan="${FLAVORS.length + 3}" class="feat-cat-label">${cat}</td></tr>`;
FEATURES.filter(f => f.category === cat).forEach((feat, idx) => {
const fIdx = FEATURES.indexOf(feat);
html += `<tr class="feat-row" data-fidx="${fIdx}">`;
html += `<td class="feat-name">${feat.name}${feat.doc ? ` <a href="${feat.doc}" target="_blank" class="feat-doc-link">↗</a>` : ''}</td>`;
html += `<td class="feat-ver">${versionBadge(feat.version)}</td>`;
feat.avail.forEach((ok, fi) => {
html += `<td class="feat-avail ${ok ? 'yes' : 'no'}">${availIcon(ok)}</td>`;
});
html += `<td class="feat-req"><input type="checkbox" class="feat-checkbox" data-fidx="${fIdx}" onchange="applyFeatureFilters()"></td>`;
html += '</tr>';
});
});
html += '</tbody></table>';
container.innerHTML = html;
}
function toggleCloud(btn) {
btn.classList.toggle('active');
applyFeatureFilters();
}
function applyFeatureFilters() {
if (typeof FEATURES === 'undefined') return;
const checkboxes = document.querySelectorAll('.feat-checkbox:checked');
const required = Array.from(checkboxes).map(cb => parseInt(cb.dataset.fidx));
// Determine cloud platform constraints
const activeCloudBtns = document.querySelectorAll('.cloud-btn.active');
const selectedClouds = Array.from(activeCloudBtns).map(b => parseInt(b.dataset.cidx));
const hasCloudFilter = typeof CLOUD_PLATFORMS !== 'undefined' && selectedClouds.length > 0 && selectedClouds.length < CLOUD_PLATFORMS.length;
// Determine which flavors satisfy ALL required features
let flavorOk = FLAVORS.map((_, fi) => {
return required.every(fIdx => FEATURES[fIdx].avail[fi]);
});
// Apply cloud platform filter
if (hasCloudFilter) {
flavorOk = flavorOk.map((ok, fi) => {
if (!ok) return false;
return selectedClouds.every(ci => CLOUD_PLATFORMS[ci].avail[fi]);
});
}
// Determine version requirement
const needs26ai = required.some(fIdx => FEATURES[fIdx].version === '26ai');
// Update version recommendation on each score chip
FLAVORS.forEach((_, i) => {
const el = document.getElementById('verRec' + i);
if (!el) return;
if (required.length === 0 && !hasCloudFilter) {
el.innerHTML = '';
} else if (!flavorOk[i]) {
el.innerHTML = ' <span class="ver-rec-tag excluded">not compatible</span>';
} else if (needs26ai) {
el.innerHTML = ' <span class="ver-rec-tag v26">26ai</span>';
} else {
el.innerHTML = ' <span class="ver-rec-tag vany">19c / 26ai</span>';
}
});
// Update filter status display
const statusEl = document.getElementById('featureFilterStatus');
if (statusEl) {
const hasAnyFilter = required.length > 0 || hasCloudFilter;
if (!hasAnyFilter) {
statusEl.innerHTML = '';
} else {
const okFlavors = FLAVORS.filter((_, i) => flavorOk[i]).map(fl => `<span class="flavor-label ${fl.cls}" style="font-size:0.65rem;padding:0.1rem 0.4rem;">${fl.short}</span>`);
const blockedFlavors = FLAVORS.filter((_, i) => !flavorOk[i]).map(fl => `<span class="flavor-label ${fl.cls}" style="font-size:0.65rem;padding:0.1rem 0.4rem;opacity:0.3;text-decoration:line-through;">${fl.short}</span>`);
const parts = [];
if (required.length > 0) parts.push(`<strong>${required.length}</strong> feature${required.length > 1 ? 's' : ''}`);
if (hasCloudFilter) parts.push(selectedClouds.map(ci => CLOUD_PLATFORMS[ci].name).join(' + '));
const verLabel = needs26ai ? ' · <span class="ver-badge v26">26ai required</span>' : required.length > 0 ? ' · <span class="ver-badge vboth">19c / 26ai</span>' : '';
statusEl.innerHTML = `
<div class="filter-status-bar">
<div class="filter-status-left">
${parts.join(' · ')}${verLabel}
</div>
<div class="filter-status-right">
${okFlavors.join(' ')} ${blockedFlavors.join(' ')}
</div>
</div>`;
}
}
// Apply visual dimming to the Decision Matrix score chips
const hasAnyFilter = required.length > 0 || hasCloudFilter;
document.querySelectorAll('.score-chip').forEach(chip => {
const fIdx = parseInt(chip.dataset.flavorIdx);
if (!isNaN(fIdx)) {
if (hasAnyFilter && !flavorOk[fIdx]) {
chip.classList.add('filtered-out');
} else {
chip.classList.remove('filtered-out');
}
}
});
// Dim feature rows that are checked
document.querySelectorAll('.feat-row').forEach(row => {
const fIdx = parseInt(row.dataset.fidx);
if (required.includes(fIdx)) {
row.classList.add('feat-required');
} else {
row.classList.remove('feat-required');
}
});
// Dim cost cards
document.querySelectorAll('.cost-result-card').forEach(card => {
const cls = card.querySelector('.flavor-tag');
if (cls) {
const flavorClass = FLAVORS.map(fl => fl.cls).find(c => cls.classList.contains(c));
const fi = FLAVORS.findIndex(fl => fl.cls === flavorClass);
if (fi >= 0 && hasAnyFilter && !flavorOk[fi]) {
card.style.opacity = '0.35';
card.style.pointerEvents = 'none';
} else {
card.style.opacity = '';
card.style.pointerEvents = '';
}
}
});
}
renderFeatureMatrix();
function renderScenarios() {
const flavorColors = ['var(--shared-color)','var(--spool-color)','var(--pool-color)','var(--dedicated-color)','var(--dedpool-color)'];
const flavorNames = FLAVORS.map(f => f.short);
document.getElementById('scenariosContainer').innerHTML = SCENARIOS.map((group, gi) => {
const heading = gi > 0 ? `<h3 style="font-size:1.05rem;margin:2rem 0 0.75rem;color:var(--text-primary);">${group.title}</h3>` : '';
const rows = group.rows.map(r => `<tr>
<td>${r.label}<br><span style="font-weight:400;color:var(--text-muted);font-size:0.7rem">${r.sub}</span></td>
${r.flavors.map(f => `<td>${f}</td>`).join('')}
<td class="takeaway">${r.takeaway}</td>
</tr>`).join('');
return `${heading}
<table class="scenario-table">
<thead><tr>
<th>${group.firstCol}</th>
${flavorNames.map((n,i) => `<th style="color:${flavorColors[i]}">${n}</th>`).join('')}
<th>Takeaway</th>
</tr></thead>
<tbody>${rows}</tbody>
</table>`;
}).join('');
}
renderScenarios();
// ═══════════════════════════════════════════════════════════════════════════
// TAB DEEP LINKING — #overview, #scenarios, #matrix, #cost
// ═══════════════════════════════════════════════════════════════════════════
// Auto-hide tab nav on page scroll down, reveal on scroll up
(function(){
var last=0, nav=document.querySelector('.tab-nav');
if(!nav) return;
window.addEventListener('scroll',function(){
var y=window.scrollY;
if(y>200 && y>last+5) nav.classList.add('nav-hidden');
else if(y<last-5) nav.classList.remove('nav-hidden');
last=y;
},{passive:true});
})();
const TAB_IDS = ['overview','scenarios','matrix','cost'];
function activateTab(tabName) {
if (!TAB_IDS.includes(tabName)) return;
const radio = document.getElementById('tab-' + tabName);
if (radio) radio.checked = true;
}
function copyTabLink(tabName) {
const url = window.location.href.split('#')[0] + '#' + tabName;
navigator.clipboard.writeText(url).then(() => {
// Find the clicked button by the tab name
const btns = document.querySelectorAll('.share-link');
btns.forEach(btn => {
if (btn.getAttribute('onclick') && btn.getAttribute('onclick').includes(tabName)) {
btn.classList.add('copied');
btn.querySelector('span').textContent = 'Copied!';
setTimeout(() => {
btn.classList.remove('copied');
btn.querySelector('span').textContent = 'Share';
}, 2000);
}
});
}).catch(() => {
// Fallback: select the URL in a prompt
prompt('Copy this link:', url);
});
// Also update the hash without scrolling
try { history.replaceState(null, '', '#' + tabName); } catch(e) { /* sandboxed iframe */ }
}
// On page load: read hash and activate tab
function initTabFromHash() {
try {
const hash = window.location.hash.replace('#','');
if (hash && TAB_IDS.includes(hash)) {
activateTab(hash);
}
} catch(e) { /* sandboxed */ }
}
// Update hash when tab is clicked via label
document.querySelectorAll('.tab-nav label').forEach(label => {
label.addEventListener('click', () => {
const forId = label.getAttribute('for'); // e.g. "tab-scenarios"
const tabName = forId.replace('tab-','');
try { history.replaceState(null, '', '#' + tabName); } catch(e) {}
});
});
// Listen for back/forward
window.addEventListener('hashchange', () => initTabFromHash());
// Initialize
initTabFromHash();
loadCostPreset('small');
</script>
</body>
</html>