first build

main
Matthew Huntington 1 month ago
parent e6784be6c2
commit 7c3131522d

@ -0,0 +1,534 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PF2e Perception &amp; Stealth</title>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<style>
:root {
--bg: #161210;
--surface: #211a13;
--surface2: #2e2318;
--border: #7a5c18;
--text: #e8d5b0;
--text-muted: #9a7e56;
--accent: #c89030;
--c-observed: #193a14;
--c-observed-hi: #5ec454;
--c-observed-border:#3a7c32;
--c-hidden: #3e2e06;
--c-hidden-hi: #f0c040;
--c-hidden-border: #a07a10;
--c-undetected: #3e1a06;
--c-undetected-hi: #f07830;
--c-undetected-border:#a04010;
--c-unnoticed: #300c0c;
--c-unnoticed-hi: #f04040;
--c-unnoticed-border:#8a1010;
--c-concealed: #0e2234;
--c-concealed-hi: #50a8e8;
--c-concealed-border:#2060a0;
--c-invisible: #180e30;
--c-invisible-hi: #9070e0;
--c-invisible-border:#5030b0;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: Georgia, 'Times New Roman', serif;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ── Header ── */
header {
background: var(--surface);
border-bottom: 2px solid var(--border);
padding: 14px 24px;
display: flex;
align-items: baseline;
gap: 16px;
flex-shrink: 0;
}
header h1 {
font-size: 1.5rem;
color: var(--accent);
letter-spacing: 0.06em;
}
header p {
color: var(--text-muted);
font-size: 0.85rem;
font-style: italic;
}
/* ── Body layout ── */
.layout {
display: flex;
flex: 1;
overflow: hidden;
}
/* ── Sidebar ── */
aside {
width: 270px;
min-width: 270px;
background: var(--surface);
border-right: 1px solid var(--border);
overflow-y: auto;
padding: 16px 14px;
}
aside h2 {
color: var(--accent);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.12em;
margin-bottom: 12px;
}
.ccard {
border-radius: 5px;
padding: 10px 12px;
margin-bottom: 9px;
border: 1px solid;
}
.ccard h3 {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.07em;
margin-bottom: 5px;
}
.ccard p {
font-size: 0.76rem;
line-height: 1.55;
color: var(--text);
}
.ccard .tag {
display: inline-block;
margin-top: 6px;
font-size: 0.72rem;
font-weight: bold;
padding: 2px 7px;
border-radius: 3px;
}
.ccard.observed { background: var(--c-observed); border-color: var(--c-observed-border); }
.ccard.observed h3{ color: var(--c-observed-hi); }
.ccard.observed .tag { background: #0d2a0a; color: var(--c-observed-hi); }
.ccard.hidden { background: var(--c-hidden); border-color: var(--c-hidden-border); }
.ccard.hidden h3 { color: var(--c-hidden-hi); }
.ccard.hidden .tag{ background: #2a1e04; color: var(--c-hidden-hi); }
.ccard.undetected { background: var(--c-undetected); border-color: var(--c-undetected-border); }
.ccard.undetected h3 { color: var(--c-undetected-hi); }
.ccard.undetected .tag{ background: #2a1004; color: var(--c-undetected-hi); }
.ccard.unnoticed { background: var(--c-unnoticed); border-color: var(--c-unnoticed-border); }
.ccard.unnoticed h3 { color: var(--c-unnoticed-hi); }
.ccard.unnoticed .tag{ background: #200808; color: var(--c-unnoticed-hi); }
.ccard.concealed { background: var(--c-concealed); border-color: var(--c-concealed-border); }
.ccard.concealed h3 { color: var(--c-concealed-hi); }
.ccard.concealed .tag{ background: #081422; color: var(--c-concealed-hi); }
.ccard.invisible { background: var(--c-invisible); border-color: var(--c-invisible-border); }
.ccard.invisible h3 { color: var(--c-invisible-hi); }
.ccard.invisible .tag{ background: #100820; color: var(--c-invisible-hi); }
.cover-box {
margin-top: 14px;
padding: 10px 12px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 5px;
font-size: 0.76rem;
line-height: 1.7;
}
.cover-box h3 {
color: var(--accent);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 6px;
}
.cover-box strong { color: var(--accent); }
/* ── Main content ── */
main {
flex: 1;
overflow: auto;
padding: 20px 24px;
}
/* ── Tabs ── */
.tab-bar {
display: flex;
gap: 6px;
margin-bottom: 0;
}
.tab-btn {
padding: 8px 22px;
background: var(--surface2);
border: 1px solid var(--border);
border-bottom-color: var(--border);
color: var(--text-muted);
cursor: pointer;
font-family: inherit;
font-size: 0.9rem;
border-radius: 6px 6px 0 0;
transition: color 0.15s, background 0.15s;
}
.tab-btn.active {
background: var(--surface);
color: var(--accent);
border-color: var(--accent);
border-bottom-color: var(--surface);
position: relative;
z-index: 1;
}
.tab-btn:hover:not(.active) { color: var(--text); }
.tab-panel {
display: none;
background: var(--surface);
border: 1px solid var(--accent);
border-radius: 0 6px 6px 6px;
padding: 24px;
}
.tab-panel.active { display: block; }
.tab-panel h2 {
color: var(--accent);
font-size: 1.1rem;
margin-bottom: 6px;
}
.tab-panel .subtitle {
color: var(--text-muted);
font-size: 0.82rem;
font-style: italic;
margin-bottom: 22px;
}
/* ── Mermaid container ── */
.diagram-wrap {
overflow-x: auto;
padding: 10px 0;
}
.diagram-wrap .mermaid {
display: flex;
justify-content: center;
min-width: 600px;
}
/* legend strip */
.legend {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 18px;
font-size: 0.77rem;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
}
.legend-dot {
width: 13px;
height: 13px;
border-radius: 50%;
border: 1px solid;
flex-shrink: 0;
}
.note-box {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 5px;
padding: 12px 16px;
margin-top: 20px;
font-size: 0.8rem;
line-height: 1.7;
color: var(--text);
}
.note-box h3 {
color: var(--accent);
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 8px;
}
.note-box ul {
padding-left: 16px;
}
.note-box li { margin-bottom: 4px; }
.note-box strong { color: var(--accent); }
</style>
</head>
<body>
<header>
<h1>PF2e Perception &amp; Stealth</h1>
<p>Flow of Control Reference &mdash; Player Core</p>
</header>
<div class="layout">
<!-- ── Sidebar ── -->
<aside>
<h2>Detection Conditions</h2>
<div class="ccard observed">
<h3>Observed</h3>
<p>You perceive the creature clearly with a precise sense. You can target it normally.</p>
<span class="tag">No flat check to target</span>
</div>
<div class="ccard concealed">
<h3>Concealed</h3>
<p>In mist, dim light, or similar. Still <em>observed</em>, but harder to affect. Does not change the main detection category.</p>
<span class="tag">DC 5 flat check to affect</span>
</div>
<div class="ccard hidden">
<h3>Hidden</h3>
<p>You know the creature&rsquo;s space but can&rsquo;t see it. You are off-guard to it. The creature cannot take non-unobtrusive actions without being observed.</p>
<span class="tag">DC 11 flat check to affect</span>
</div>
<div class="ccard undetected">
<h3>Undetected</h3>
<p>You don&rsquo;t know which space it&rsquo;s in. You are off-guard. You may know it is nearby (undetected) or have no clue at all (unnoticed).</p>
<span class="tag">Pick a square &mdash; GM rolls secretly</span>
</div>
<div class="ccard unnoticed">
<h3>Unnoticed</h3>
<p>You have no idea the creature even exists nearby. Certain abilities only work on totally unaware targets.</p>
<span class="tag">Cannot target at all</span>
</div>
<div class="ccard invisible">
<h3>Invisible</h3>
<p>Automatically <em>undetected</em> against creatures relying on sight alone. Precise senses other than vision still work. Seek can reveal location, making it hidden.</p>
<span class="tag">Undetected to sight-only perceivers</span>
</div>
<div class="cover-box">
<h3>Cover Bonuses (Stealth)</h3>
<strong>Standard Cover:</strong> +2 to Stealth<br>
<strong>Greater Cover:</strong> +4 to Stealth
</div>
</aside>
<!-- ── Main ── -->
<main>
<div class="tab-bar">
<button class="tab-btn active" onclick="showTab('stealth', this)">Stealth Actions</button>
<button class="tab-btn" onclick="showTab('detection', this)">Detection &amp; Targeting</button>
</div>
<!-- Stealth Tab -->
<div id="stealth" class="tab-panel active">
<h2>Stealth Actions Flow</h2>
<p class="subtitle">From the <strong>stealther&rsquo;s</strong> perspective &mdash; how detection states change based on actions you take</p>
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:var(--c-observed);border-color:var(--c-observed-hi)"></div> Observed</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--c-hidden);border-color:var(--c-hidden-hi)"></div> Hidden</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--c-undetected);border-color:var(--c-undetected-hi)"></div> Undetected</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--c-unnoticed);border-color:var(--c-unnoticed-hi)"></div> Unnoticed</div>
<div class="legend-item"><div class="legend-dot" style="background:#1a2840;border-color:#4080b0"></div> Action / Roll</div>
</div>
<div class="diagram-wrap">
<div class="mermaid" id="stealth-diagram">
%%{init: {'theme':'dark','themeVariables':{'background':'#161210','primaryColor':'#193a14','primaryTextColor':'#e8d5b0','primaryBorderColor':'#3a7c32','lineColor':'#9a7e56','secondaryColor':'#3e2e06','tertiaryColor':'#1a2840','edgeLabelBackground':'#211a13','clusterBkg':'#211a13'}}}%%
flowchart LR
classDef obs fill:#193a14,stroke:#3a7c32,color:#b8e8b0,rx:30
classDef hid fill:#3e2e06,stroke:#a07a10,color:#f0e060
classDef und fill:#3e1a06,stroke:#a04010,color:#f0a868
classDef unn fill:#300c0c,stroke:#8a1010,color:#f07070
classDef act fill:#1a2840,stroke:#4080b0,color:#90c0e8
classDef roll fill:#201828,stroke:#6040a0,color:#c0a0e8
OBS(["OBSERVED"]):::obs
HID(["HIDDEN"]):::hid
UND(["UNDETECTED"]):::und
UNN(["UNNOTICED"]):::unn
HIDE["HIDE
— needs cover or concealment —
secret Stealth roll vs Perception DC
Standard cover +2 · Greater cover +4"]:::act
SNEAK["SNEAK
— cover at START and END —
move ≤ half Speed
secret Stealth roll vs Perception DC"]:::act
OBS --> HIDE
HIDE -->|"✓ Success"| HID
HIDE -->|"✗ Failure"| OBS
HID --> SNEAK
HID -->|"Non-stealthy action
or Strike"| OBS
HID -->|"STEP
with cover / concealment"| HID
HID -->|"Unobtrusive action
GM may require Stealth"| HID
SNEAK -->|"✓ Success"| UND
SNEAK -->|"✗ Failure"| HID
SNEAK -->|"✗✗ Crit Failure
(not invisible)"| OBS
UND -->|"SNEAK ✓ Success"| UND
UND -->|"HIDE ✓ Success"| HID
UND -->|"Speak or
make loud noise"| OBS
UNN -.->|"may also be"| UND
UNN -->|"Speak, loud noise
or non-stealthy action"| OBS
</div>
</div>
<div class="note-box">
<h3>Key Rules</h3>
<ul>
<li><strong>HIDE</strong> requires that you have cover or concealment from the target. You can&rsquo;t hide in the open.</li>
<li><strong>SNEAK</strong> requires cover or concealment at <em>both the start and end</em> of your movement. You move up to half your Speed.</li>
<li>Taking any non-<em>unobtrusive</em> action while hidden immediately makes you <strong>Observed</strong>.</li>
<li>A <strong>Strike</strong> from a hidden or undetected position makes you <strong>Observed</strong> after the attack (target now knows your location).</li>
<li>An <strong>Invisible</strong> creature that critically fails a Sneak roll becomes <strong>Hidden</strong> rather than Observed, since it cannot be seen.</li>
<li><strong>AVOID NOTICE</strong> (Exploration): Roll Stealth to start an encounter Hidden or Undetected rather than Observed.</li>
</ul>
</div>
</div>
<!-- Detection Tab -->
<div id="detection" class="tab-panel">
<h2>Detection &amp; Targeting Flow</h2>
<p class="subtitle">From the <strong>detector&rsquo;s</strong> perspective &mdash; how to determine a creature&rsquo;s condition and what you can do about it</p>
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:var(--c-observed);border-color:var(--c-observed-hi)"></div> Observed</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--c-hidden);border-color:var(--c-hidden-hi)"></div> Hidden</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--c-undetected);border-color:var(--c-undetected-hi)"></div> Undetected</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--c-unnoticed);border-color:var(--c-unnoticed-hi)"></div> Unnoticed</div>
<div class="legend-item"><div class="legend-dot" style="background:#1a2840;border-color:#4080b0"></div> Action / Roll</div>
<div class="legend-item"><div class="legend-dot" style="background:#201828;border-color:#6040a0"></div> Decision</div>
</div>
<div class="diagram-wrap">
<div class="mermaid" id="detection-diagram">
%%{init: {'theme':'dark','themeVariables':{'background':'#161210','primaryColor':'#211a13','primaryTextColor':'#e8d5b0','primaryBorderColor':'#7a5c18','lineColor':'#9a7e56','secondaryColor':'#2e2318','tertiaryColor':'#1a2840','edgeLabelBackground':'#211a13'}}}%%
flowchart TD
classDef obs fill:#193a14,stroke:#3a7c32,color:#b8e8b0
classDef hid fill:#3e2e06,stroke:#a07a10,color:#f0e060
classDef und fill:#3e1a06,stroke:#a04010,color:#f0a868
classDef unn fill:#300c0c,stroke:#8a1010,color:#f07070
classDef act fill:#1a2840,stroke:#4080b0,color:#90c0e8
classDef dec fill:#201828,stroke:#6040a0,color:#d0c0f0
classDef start fill:#2a1f14,stroke:#7a5c18,color:#e8d5b0
START(["Start: What can you perceive?"]):::start
Q1{"Can your precise sense\ndetect the creature?"}:::dec
Q2{"Do you know it\nis nearby?"}:::dec
Q3{"Do you know\nwhich space?"}:::dec
OBS(["OBSERVED
— target normally —
off-guard? No
(if also concealed: DC 5 flat check)"]):::obs
HID(["HIDDEN
— you know the space —
off-guard to you
DC 11 flat check before roll
Fail = actions wasted, still off-guard"]):::hid
UND(["UNDETECTED
— don't know the space —
off-guard to you
Pick a square to attack
GM secretly rolls flat check + attack
Won't reveal why you missed"]):::und
UNN(["UNNOTICED
— no idea it exists —
Cannot target at all"]):::unn
SEEK["SEEK action
Secret Perception roll
vs creature's Stealth DC"]:::act
START --> Q1
Q1 -->|"YES — precise sense works"| OBS
Q1 -->|"NO"| Q2
Q2 -->|"NO"| UNN
Q2 -->|"YES"| Q3
Q3 -->|"YES — know the space"| HID
Q3 -->|"NO — don't know the space"| UND
HID -->|"Use SEEK"| SEEK
UND -->|"Use SEEK"| SEEK
SEEK -->|"Critical Success"| OBS
SEEK -->|"Success
(precise sense + creature visible)"| OBS
SEEK -->|"Success
(imprecise sense or creature invisible)"| HID
SEEK -->|"Failure — creature
stays at current state"| HID
</div>
</div>
<div class="note-box">
<h3>Targeting Summary</h3>
<ul>
<li><strong>Observed:</strong> Attack normally. If also <em>concealed</em>, attempt a DC&nbsp;5 flat check before rolling — fail means no effect.</li>
<li><strong>Hidden:</strong> Attempt a DC&nbsp;11 flat check before rolling. On a fail, your actions (and spell slots, resources) are wasted. You remain off-guard to the creature regardless.</li>
<li><strong>Undetected:</strong> Declare a square to attack. The GM secretly rolls both the flat check and your attack roll. The GM will not say whether you missed due to the flat check, the attack roll, or choosing the wrong square.</li>
<li><strong>Unnoticed:</strong> You cannot target the creature directly. Area effects still work normally against undetected creatures.</li>
<li><strong>Invisible creature</strong> starts as <em>Hidden</em> if you were already observing it when it turned invisible (you last know where it was). It can then Sneak to become Undetected.</li>
<li><strong>Off-guard</strong> applies against you when attacking a creature that is hidden or undetected relative to you — it gets the &minus;2 AC penalty.</li>
</ul>
</div>
</div>
</main>
</div>
<script>
mermaid.initialize({
startOnLoad: true,
theme: 'dark',
flowchart: { curve: 'basis', padding: 20, useMaxWidth: false },
securityLevel: 'loose',
});
function showTab(name, btn) {
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById(name).classList.add('active');
btn.classList.add('active');
}
</script>
</body>
</html>
Loading…
Cancel
Save