rsrc/extra/etc/cs2/crosshair.html

1107 lines
30 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">
<title>crosshair gen</title>
<style>
:root{
--black:#000;
--white:#fff;
--line:#262626;
--muted:#888;
--muted-strong:#aaa;
--control:#101010;
--control-hover:#181818;
--bright:rgba(255,255,255,.84);
--soft:rgba(255,255,255,.65);
}
*{box-sizing:border-box}
html,body{min-height:100%;background:#000}
body{
margin:0;
padding:10px;
background:#000;
color:var(--bright);
font:13px/1.3 "SFMono-Regular",Consolas,"Liberation Mono",monospace
}
.app{
width:min(100%,1080px);
min-height:calc(100vh - 20px);
margin:auto;
display:grid;
grid-template-columns:272px minmax(0,1fr);
gap:8px
}
.panel{background:#000;border:0px solid var(--line);border-radius:0}
.sidebar{
padding:7px;
display:flex;
flex-direction:column;
gap:4px;
max-height:calc(100vh - 20px);
overflow:auto;
scrollbar-width:thin;
scrollbar-color:#555 #000
}
.field{padding:5px 6px;background:#000}
.label{
display:flex;
align-items:center;
justify-content:space-between;
gap:6px;
margin:0 0 4px;
color:var(--muted);
font-size:10px;
letter-spacing:.07em;
text-transform:lowercase
}
.value{color:var(--bright);letter-spacing:0}
select,input[type=number],textarea,button{font:inherit}
select,input[type=number]{
width:100%;
height:29px;
padding:0 7px;
color:var(--bright);
background:var(--control);
border:0;
border-radius:0;
outline:none;
font-size:11px
}
select:focus,input[type=number]:focus,textarea:focus,.map-button:focus{
box-shadow:inset 0 0 0 1px rgba(255,255,255,.55)
}
.range-row{
display:grid;
grid-template-columns:minmax(0,1fr) 60px;
gap:7px;
align-items:center
}
input[type=range]{
appearance:none;
width:100%;
height:2px;
margin:0;
background:#484848;
border-radius:999px;
outline:none;
cursor:pointer
}
input[type=range]::-webkit-slider-thumb{
appearance:none;
width:12px;
height:12px;
background:rgba(255,255,255,.82);
border:0;
border-radius:50%
}
input[type=range]::-moz-range-thumb{
width:12px;
height:12px;
background:rgba(255,255,255,.82);
border:0;
border-radius:50%
}
.number-control{
height:28px;
display:grid;
grid-template-columns:19px minmax(0,1fr) 19px;
background:var(--control);
border-radius:999px;
overflow:hidden
}
.number-control input{
min-width:0;
height:28px;
padding:0;
text-align:center;
color:var(--bright);
background:transparent;
border:0!important;
border-radius:0!important;
appearance:textfield;
box-shadow:none!important;
font-size:11px
}
.number-control input::-webkit-inner-spin-button,
.number-control input::-webkit-outer-spin-button{appearance:none;margin:0}
.step{
padding:0;
border:0;
border-radius:0;
background:transparent;
color:var(--soft);
cursor:pointer;
font-size:15px;
line-height:1
}
.step:hover{color:#000;background:rgba(255,255,255,.82)}
.toggle-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px}
.toggle{
min-height:29px;
padding:5px 6px;
display:flex;
align-items:center;
justify-content:space-between;
gap:5px;
background:var(--control);
color:var(--soft);
cursor:pointer;
font-size:10px;
border:0;
border-radius:0;
text-transform:lowercase
}
.toggle:hover{color:var(--bright);background:var(--control-hover)}
.toggle input{
appearance:none;
width:24px;
height:14px;
margin:0;
position:relative;
border:0;
border-radius:999px;
background:#3a3a3a;
cursor:pointer
}
.toggle input:after{
content:"";
position:absolute;
left:2px;
top:2px;
width:10px;
height:10px;
border-radius:50%;
background:#888;
transition:left .12s ease,background .12s ease
}
.toggle input:checked{background:#666}
.toggle input:checked:after{left:12px;background:rgba(255,255,255,.88)}
.workspace{
display:grid;
grid-template-rows:minmax(430px,1fr) 126px;
gap:8px;
min-height:calc(100vh - 20px)
}
.preview{
position:relative;
overflow:hidden;
isolation:isolate;
background:#000;
cursor:crosshair;
user-select:none
}
.preview.grid-on{
background-image:
linear-gradient(90deg,#151515 1px,transparent 1px),
linear-gradient(#151515 1px,transparent 1px);
background-size:48px 48px
}
.map-image{
position:absolute;
inset:0;
width:100%;
height:100%;
z-index:-2;
object-fit:cover;
opacity:.76;
filter:saturate(.82) contrast(.92);
pointer-events:none
}
.map-fallback{position:absolute;inset:0;z-index:-3;background:#000;pointer-events:none}
.preview:before,.preview:after{
content:"";
position:absolute;
z-index:1;
pointer-events:none;
background:rgba(255,255,255,.18)
}
.preview:before{left:0;right:0;top:50%;height:1px}
.preview:after{top:0;bottom:0;left:50%;width:1px}
.map-tag{
position:absolute;
left:8px;
top:8px;
z-index:4;
padding:4px 6px;
background:#000;
color:rgba(255,255,255,.7);
font-size:10px;
letter-spacing:.1em;
text-transform:uppercase;
pointer-events:none
}
.viewer-tools{position:absolute;right:8px;top:8px;z-index:5;display:flex;gap:5px}
.tool-button{height:25px;padding:0 8px;font-size:10px}
.tool-button.active{background:rgba(255,255,255,.84);color:#000}
.crosshair{position:absolute;left:50%;top:50%;width:0;height:0;z-index:3;pointer-events:none}
.part,.dot{position:absolute;display:block;background:#fff;opacity:1;box-shadow:none}
.part{border-radius:0}
.dot{border-radius:50%;transform:translate(-50%,-50%)}
.output{position:relative;padding:7px}
textarea{
display:block;
width:100%;
height:100%;
min-height:108px;
resize:none;
outline:none;
color:var(--bright);
background:#000;
border:0;
border-radius:0;
padding:8px 9px 32px;
font:10px/1.4 "SFMono-Regular",Consolas,"Liberation Mono",monospace
}
.actions{position:absolute;right:13px;bottom:13px;display:flex;gap:5px}
button{
height:24px;
padding:0 8px;
color:var(--soft);
background:var(--control);
border:0;
border-radius:0;
cursor:pointer;
font-size:10px
}
button:hover{color:#000;background:rgba(255,255,255,.84)}
.status{position:absolute;left:15px;bottom:17px;color:#777;font-size:10px}
.map-picker{position:relative}
.map-button{
width:100%;
height:30px;
padding:2px;
display:flex;
align-items:center;
gap:7px;
color:var(--bright);
background:var(--control);
border:0;
border-radius:0;
outline:none;
cursor:pointer;
text-align:left;
font-size:11px
}
.map-button:after{content:"+";margin-left:auto;padding:0 6px;color:#777;font-size:15px}
.map-thumb{width:38px;height:26px;flex:0 0 38px;object-fit:cover;background:#000}
.map-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.map-list{
display:none;
position:absolute;
left:0;
right:0;
top:calc(100% + 4px);
z-index:20;
max-height:260px;
overflow:auto;
padding:3px;
background:var(--control);
border:0;
border-radius:0
}
.map-picker.open .map-list{display:block}
.map-picker.open .map-button:after{content:"−"}
.map-option{
width:100%;
padding:5px;
display:flex;
align-items:center;
gap:8px;
border:0;
border-radius:0;
background:var(--control);
color:var(--soft);
text-align:left;
cursor:pointer;
font-size:11px
}
.map-option:hover,.map-option.active{color:var(--bright);background:#292929}
.map-option .map-thumb{width:43px;height:27px;flex-basis:43px}
.color-inline{display:grid;grid-template-columns:minmax(0,1fr) 29px;gap:5px;align-items:center}
input[type=color]{
appearance:none;
width:29px;
height:29px;
padding:3px;
background:#000;
border:0;
border-radius:0;
cursor:pointer
}
input[type=color]::-webkit-color-swatch-wrapper{padding:0}
input[type=color]::-webkit-color-swatch{border:0;border-radius:0}
input[type=color]::-moz-color-swatch{border:0;border-radius:0}
.hex-readout{
min-height:14px;
margin-top:4px;
color:#777;
font-size:10px;
letter-spacing:.04em;
text-transform:uppercase
}
.hex-readout[hidden]{display:none}
@media(max-width:720px){
body{padding:7px}
.app{grid-template-columns:1fr;min-height:0;max-width:620px}
.sidebar{max-height:none;overflow:visible}
.workspace{min-height:auto;grid-template-rows:410px 124px}
}
@media(max-width:400px){
.toggle-grid{grid-template-columns:1fr}
.workspace{grid-template-rows:350px 124px}
.viewer-tools{top:auto;bottom:8px}
}
</style>
</head>
<body>
<div class="app">
<aside class="panel sidebar">
<div class="field">
<div class="map-picker" id="mapPicker">
<button class="map-button" id="mapButton" type="button" aria-expanded="false">
<img class="map-thumb" id="selectedMapThumb" alt="">
<span class="map-name" id="selectedMapName">None / Black</span>
</button>
<div class="map-list" id="mapList"></div>
</div>
</div>
<div class="field">
<select id="style" aria-label="Crosshair style">
<option value="4">Static</option>
</select>
</div>
<div class="field">
<div class="color-inline">
<select id="color" aria-label="Crosshair color preset">
<option value="5">Custom RGB</option>
<option value="0">Red</option>
<option value="1">Green</option>
<option value="2">Yellow</option>
<option value="3">Blue</option>
<option value="4">Cyan</option>
</select>
<input id="hexColorPicker" type="color" value="#00FFFF" aria-label="Custom crosshair color picker">
</div>
<div class="hex-readout" id="hexReadout">#00FFFF</div>
</div>
<div class="field">
<div class="label">cl_crosshairsize <span class="value" id="sizeV"></span></div>
<div class="range-row">
<input id="size" type="range" min=".5" max="10" step=".1" value="2.5">
<div class="number-control">
<button class="step" data-step="size" data-dir="-1" type="button">−</button>
<input id="sizeN" type="number" min=".5" max="10" step=".1" value="2.5">
<button class="step" data-step="size" data-dir="1" type="button">+</button>
</div>
</div>
</div>
<div class="field">
<div class="label">cl_crosshairthickness <span class="value" id="thicknessV"></span></div>
<div class="range-row">
<input id="thickness" type="range" min=".1" max="6" step=".1" value=".5">
<div class="number-control">
<button class="step" data-step="thickness" data-dir="-1" type="button">−</button>
<input id="thicknessN" type="number" min=".1" max="6" step=".1" value=".5">
<button class="step" data-step="thickness" data-dir="1" type="button">+</button>
</div>
</div>
</div>
<div class="field">
<div class="label">cl_crosshairgap <span class="value" id="gapV"></span></div>
<div class="range-row">
<input id="gap" type="range" min="-10" max="10" step=".1" value="-3">
<div class="number-control">
<button class="step" data-step="gap" data-dir="-1" type="button">−</button>
<input id="gapN" type="number" min="-10" max="10" step=".1" value="-3">
<button class="step" data-step="gap" data-dir="1" type="button">+</button>
</div>
</div>
</div>
<div class="field">
<div class="label">cl_crosshair_outlinethickness <span class="value" id="outlineTV"></span></div>
<div class="range-row">
<input id="outlineT" type="range" min=".1" max="3" step=".1" value="1">
<div class="number-control">
<button class="step" data-step="outlineT" data-dir="-1" type="button">−</button>
<input id="outlineTN" type="number" min=".1" max="3" step=".1" value="1">
<button class="step" data-step="outlineT" data-dir="1" type="button">+</button>
</div>
</div>
</div>
<div class="field">
<div class="label">cl_crosshaircolor_r <span class="value" id="rV"></span></div>
<div class="range-row">
<input id="r" type="range" min="0" max="255" step="1" value="0">
<div class="number-control">
<button class="step" data-step="r" data-dir="-1" type="button">−</button>
<input id="rN" type="number" min="0" max="255" step="1" value="0">
<button class="step" data-step="r" data-dir="1" type="button">+</button>
</div>
</div>
</div>
<div class="field">
<div class="label">cl_crosshaircolor_g <span class="value" id="gV"></span></div>
<div class="range-row">
<input id="g" type="range" min="0" max="255" step="1" value="255">
<div class="number-control">
<button class="step" data-step="g" data-dir="-1" type="button">−</button>
<input id="gN" type="number" min="0" max="255" step="1" value="255">
<button class="step" data-step="g" data-dir="1" type="button">+</button>
</div>
</div>
</div>
<div class="field">
<div class="label">cl_crosshaircolor_b <span class="value" id="bV"></span></div>
<div class="range-row">
<input id="b" type="range" min="0" max="255" step="1" value="255">
<div class="number-control">
<button class="step" data-step="b" data-dir="-1" type="button">−</button>
<input id="bN" type="number" min="0" max="255" step="1" value="255">
<button class="step" data-step="b" data-dir="1" type="button">+</button>
</div>
</div>
</div>
<div class="field">
<div class="label">cl_crosshairalpha <span class="value" id="alphaV"></span></div>
<div class="range-row">
<input id="alpha" type="range" min="0" max="255" step="1" value="255">
<div class="number-control">
<button class="step" data-step="alpha" data-dir="-1" type="button">−</button>
<input id="alphaN" type="number" min="0" max="255" step="1" value="255">
<button class="step" data-step="alpha" data-dir="1" type="button">+</button>
</div>
</div>
</div>
<div class="field">
<div class="label" style="opacity:0.52;">cl_crosshair_sniper_width <span class="value" style="opacity:0.5;" id="sniperWidthV"></span></div>
<div class="range-row">
<input id="sniperWidth" type="range" min="0" max="10" step=".1" value="1" disabled>
<div class="number-control" style="opacity:0.5;">
<button class="step" data-step="sniperWidth" data-dir="-1" type="button">−</button>
<input id="sniperWidthN" type="number" min="0" max="10" step=".1" value="1">
<button class="step" data-step="sniperWidth" data-dir="1" type="button">+</button>
</div>
</div>
</div>
<div class="field">
<div class="toggle-grid">
<label class="toggle">center dot <input id="dot" type="checkbox"></label>
<label class="toggle">outline <input id="outline" type="checkbox"></label>
<label class="toggle">t style <input id="t" type="checkbox"></label>
<label class="toggle">weapon gap <input id="deployed" type="checkbox"></label>
<label class="toggle">use alpha <input id="useAlpha" type="checkbox" checked></label>
<label class="toggle">follow recoil <input id="recoil" type="checkbox"></label>
<label class="toggle" style="background:none;max-width:none;width:100%;">
<a style="text-decoration:none;font-size:11px;color:#f7f7f7;background:transparent;" href="https://s1ne.com/cs2pattern">spray patterns</a></label>
</div>
</div>
</aside>
<main class="workspace">
<section class="panel preview grid-on" id="preview" aria-label="Crosshair preview">
<div class="map-fallback" id="mapFallback"></div>
<img class="map-image" id="mapImage" alt="">
<span class="map-tag" id="mapTag">No map selected</span>
<div class="viewer-tools">
<button class="tool-button active" id="gridToggle" type="button" aria-pressed="true" style="opacity:0;">Grid: On</button>
</div>
<div class="crosshair" id="crosshair">
<i class="part" id="top"></i>
<i class="part" id="bottom"></i>
<i class="part" id="left"></i>
<i class="part" id="right"></i>
<i class="dot" id="dotEl"></i>
</div>
</section>
<section class="panel output">
<textarea id="output" readonly spellcheck="false"></textarea>
<span class="status" id="status">Autosaved</span>
<div class="actions">
<button id="randomize" type="button">Random</button>
<button id="copy" type="button">Copy</button>
<button id="reset" type="button">Reset</button>
</div>
</section>
</main>
</div>
<script>
const $ = id => document.getElementById(id);
const STORAGE_KEY = "cs2-classic-static-crosshair-v2";
const ranges = [
"size",
"thickness",
"gap",
"outlineT",
"r",
"g",
"b",
"alpha",
"sniperWidth"
];
const checks = [
"dot",
"outline",
"t",
"deployed",
"useAlpha",
"recoil"
];
const colorMap = {
0: [255, 0, 0],
1: [0, 255, 0],
2: [255, 255, 0],
3: [0, 128, 255],
4: [0, 255, 255]
};
const maps = [
{id:"none",name:"None / Black",palette:["#000","#000","#000"],img:""},
{id:"mirage",name:"Mirage",palette:["#d5a55d","#866344","#32251c"],img:"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/Cs2_mirage.jpg/640px-Cs2_mirage.jpg"},
{id:"dust2",name:"Dust II",palette:["#d4bb86","#907346","#423523"],img:"https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Dust_II_A_site.jpg/640px-Dust_II_A_site.jpg"},
{id:"inferno",name:"Inferno",palette:["#cd9863","#7d5036","#35241c"],img:"https://upload.wikimedia.org/wikipedia/commons/thumb/4/45/Inferno_CS2.jpg/640px-CS2_Inferno.jpg"},
{id:"nuke",name:"Nuke",palette:["#c5ced0","#6e7c83","#252e33"],img:"https://upload.wikimedia.org/wikipedia/commons/thumb/5/51/CS2_Nuke.jpg/640px-CS2_Nuke.jpg"},
{id:"ancient",name:"Ancient",palette:["#82975f","#4e633d","#1d2a1e"],img:"https://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/CS2_Ancient.jpg/640px-CS2_Ancient.jpg"},
{id:"anubis",name:"Anubis",palette:["#cfaa68","#886d45","#352d22"],img:"https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/CS2_Anubis.jpg/640px-CS2_Anubis.jpg"},
{id:"overpass",name:"Overpass",palette:["#91a078","#5b6652","#29312a"],img:"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/CS2_Overpass.jpg/640px-CS2_Overpass.jpg"},
{id:"vertigo",name:"Vertigo",palette:["#b9c0c3","#73797b","#2c3032"],img:"https://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/CS2_Vertigo.jpg/640px-CS2_Vertigo.jpg"},
{id:"train",name:"Train",palette:["#849599","#4f5d60","#242e30"],img:"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8a/CS2_Train.jpg/640px-CS2_Train.jpg"}
];
let activeMap = "none";
let gridEnabled = true;
function clamp(value, min, max) {
const number = Number(value);
return Number.isFinite(number) ? Math.min(max, Math.max(min, number)) : min;
}
function formatValue(id, value) {
return ["size", "thickness", "gap", "outlineT", "sniperWidth"].includes(id)
? Number(value).toFixed(1).replace(/\.0$/, "")
: String(Math.round(value));
}
function randomBetween(min, max, step = 1) {
const count = Math.round((max - min) / step);
return Number((min + Math.floor(Math.random() * (count + 1)) * step).toFixed(4));
}
function setStatus(text) {
$("status").textContent = text;
clearTimeout(setStatus.timer);
setStatus.timer = setTimeout(() => {
$("status").textContent = "Saved automatically";
}, 1400);
}
function rgbToHex(red, green, blue) {
return "#" + [red, green, blue]
.map(value => clamp(value, 0, 255).toString(16).padStart(2, "0"))
.join("")
.toUpperCase();
}
function hexToRgb(hex) {
const normalized = hex.trim().replace(/^#/, "");
if (!/^[0-9A-Fa-f]{6}$/.test(normalized)) {
return null;
}
return [
parseInt(normalized.slice(0, 2), 16),
parseInt(normalized.slice(2, 4), 16),
parseInt(normalized.slice(4, 6), 16)
];
}
function isCustomColor() {
return $("color").value === "5";
}
function syncColorUi() {
const hex = rgbToHex($("r").value, $("g").value, $("b").value);
$("hexColorPicker").value = hex;
$("hexReadout").textContent = hex;
$("hexReadout").hidden = !isCustomColor();
}
function applyHexColor(hex, save = true) {
const rgb = hexToRgb(hex);
if (!rgb) return false;
rgb.forEach((value, index) => {
const id = ["r", "g", "b"][index];
$(id).value = value;
$(id + "N").value = value;
});
$("color").value = "5";
syncColorUi();
update(save);
return true;
}
function mapBackground(map) {
const [a, b, c] = map.palette;
return `
linear-gradient(90deg,rgba(255,255,255,.04) 1px,transparent 1px) 50% 50%/48px 48px,
linear-gradient(rgba(255,255,255,.04) 1px,transparent 1px) 50% 50%/48px 48px,
linear-gradient(135deg,${a},${b} 58%,${c})
`;
}
function makeThumb(map) {
const image = document.createElement("img");
image.className = "map-thumb";
image.alt = "";
image.loading = "lazy";
image.style.background = map.id === "none"
? "#000"
: `linear-gradient(135deg,${map.palette.join(",")})`;
if (map.img) image.src = map.img;
image.onerror = () => image.removeAttribute("src");
return image;
}
function cacheMapImages() {
maps.forEach(map => {
if (!map.img) return;
const image = new Image();
image.src = map.img;
});
}
function renderMapList() {
$("mapList").innerHTML = "";
maps.forEach(map => {
const option = document.createElement("button");
option.type = "button";
option.className = "map-option";
option.dataset.map = map.id;
option.append(makeThumb(map));
const name = document.createElement("span");
name.textContent = map.name;
option.append(name);
option.onclick = () => selectMap(map.id);
$("mapList").append(option);
});
}
function closeMapPicker() {
$("mapPicker").classList.remove("open");
$("mapButton").setAttribute("aria-expanded", "false");
}
function selectMap(id, save = true) {
const map = maps.find(item => item.id === id) || maps[0];
activeMap = map.id;
$("mapFallback").style.background = map.id === "none"
? "#000"
: mapBackground(map);
$("mapTag").textContent = map.name;
$("selectedMapName").textContent = map.name;
const image = $("mapImage");
if (map.img) {
image.src = map.img;
image.style.opacity = ".76";
image.onerror = () => image.style.opacity = "0";
} else {
image.removeAttribute("src");
image.style.opacity = "0";
}
const thumb = $("selectedMapThumb");
thumb.style.background = map.id === "none"
? "#000"
: `linear-gradient(135deg,${map.palette.join(",")})`;
if (map.img) {
thumb.src = map.img;
thumb.onerror = () => thumb.removeAttribute("src");
} else {
thumb.removeAttribute("src");
}
document.querySelectorAll(".map-option").forEach(option => {
option.classList.toggle("active", option.dataset.map === map.id);
});
closeMapPicker();
if (save) saveState();
}
function setGrid(enabled, save = true) {
gridEnabled = !!enabled;
$("preview").classList.toggle("grid-on", gridEnabled);
$("gridToggle").classList.toggle("active", gridEnabled);
$("gridToggle").textContent = `Grid: ${gridEnabled ? "On" : "Off"}`;
$("gridToggle").setAttribute("aria-pressed", String(gridEnabled));
if (save) saveState();
}
function saveState() {
const state = {
map: activeMap,
grid: gridEnabled,
color: $("color").value
};
ranges.forEach(id => state[id] = $(id).value);
checks.forEach(id => state[id] = $(id).checked);
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
} catch {}
}
function loadState() {
try {
const state = JSON.parse(localStorage.getItem(STORAGE_KEY));
if (!state) return;
if (state.color != null) $("color").value = state.color;
ranges.forEach(id => {
if (state[id] == null) return;
const range = $(id);
const number = $(id + "N");
const value = clamp(state[id], Number(range.min), Number(range.max));
range.value = value;
number.value = value;
});
checks.forEach(id => {
if (typeof state[id] === "boolean") {
$(id).checked = state[id];
}
});
if (maps.some(map => map.id === state.map)) {
activeMap = state.map;
}
if (typeof state.grid === "boolean") {
gridEnabled = state.grid;
}
} catch {}
}
function syncRange(id, fromNumber = false) {
const range = $(id);
const number = $(id + "N");
const source = fromNumber ? number : range;
const value = clamp(source.value, Number(range.min), Number(range.max));
range.value = value;
number.value = value;
if (["r", "g", "b"].includes(id)) {
$("color").value = "5";
syncColorUi();
}
update();
}
function stepRange(id, direction) {
const range = $(id);
const step = Number(range.step) || 1;
const value = clamp(
Number(range.value) + step * direction,
Number(range.min),
Number(range.max)
);
range.value = value;
$(id + "N").value = Number(value.toFixed(4));
if (["r", "g", "b"].includes(id)) {
$("color").value = "5";
syncColorUi();
}
update();
}
function getCrosshairColor() {
const preset = colorMap[$("color").value];
const [red, green, blue] = preset || [
Number($("r").value),
Number($("g").value),
Number($("b").value)
];
return `rgb(${red},${green},${blue})`;
}
function update(shouldSave = true) {
ranges.forEach(id => {
$(id + "V").textContent = formatValue(id, $(id).value);
});
const size = Number($("size").value) * 8;
const thickness = Math.max(1, Number($("thickness").value) * 3);
const gap = Number($("gap").value) * 3;
const rgb = getCrosshairColor();
const alpha = $("useAlpha").checked
? Number($("alpha").value) / 255
: 1;
const outlineEnabled = $("outline").checked;
const outlineSize = Number($("outlineT").value) * 2;
const line = (element, width, height, left, top, hidden = false) => {
element.style.cssText = `
display:${hidden ? "none" : "block"};
width:${width}px;
height:${height}px;
left:${left}px;
top:${top}px;
background:${rgb};
opacity:${alpha};
${outlineEnabled ? `border:${outlineSize}px solid #000;` : ""}
`;
};
const edge = gap + thickness / 2;
line($("top"), thickness, size, -thickness / 2, -edge - size, $("t").checked);
line($("bottom"), thickness, size, -thickness / 2, edge);
line($("left"), size, thickness, -edge - size, -thickness / 2);
line($("right"), size, thickness, edge, -thickness / 2);
$("dotEl").style.cssText = `
display:${$("dot").checked ? "block" : "none"};
width:${thickness * 2}px;
height:${thickness * 2}px;
left:0;
top:0;
background:${rgb};
opacity:${alpha};
${outlineEnabled ? `border:${outlineSize}px solid #000;` : ""}
`;
$("output").value = [
"cl_crosshairstyle 4",
`cl_crosshairsize ${$("size").value}`,
`cl_crosshairthickness ${$("thickness").value}`,
`cl_crosshairgap ${$("gap").value}`,
`cl_crosshairdot ${$("dot").checked ? 1 : 0}`,
`cl_crosshair_drawoutline ${outlineEnabled ? 1 : 0}`,
`cl_crosshair_outlinethickness ${$("outlineT").value}`,
`cl_crosshair_t ${$("t").checked ? 1 : 0}`,
`cl_crosshairusealpha ${$("useAlpha").checked ? 1 : 0}`,
`cl_crosshairalpha ${$("alpha").value}`,
`cl_crosshaircolor ${$("color").value}`,
`cl_crosshaircolor_r ${$("r").value}`,
`cl_crosshaircolor_g ${$("g").value}`,
`cl_crosshaircolor_b ${$("b").value}`,
`cl_crosshairgap_useweaponvalue ${$("deployed").checked ? 1 : 0}`,
`cl_crosshair_recoil ${$("recoil").checked ? 1 : 0}`,
`cl_crosshair_sniper_width ${$("sniperWidth").value}`
].join("; ");
if (shouldSave) saveState();
}
function randomize() {
const compactPalette = [
[0,255,255],
[0,255,0],
[255,255,0],
[255,110,0],
[255,90,180],
[170,130,255],
[255,255,255]
];
const rgb = compactPalette[Math.floor(Math.random() * compactPalette.length)];
$("size").value = $("sizeN").value = randomBetween(1.5, 5.5, .1);
$("thickness").value = $("thicknessN").value = randomBetween(.5, 2, .1);
$("gap").value = $("gapN").value = randomBetween(-5, 2, .1);
$("outlineT").value = $("outlineTN").value = randomBetween(.5, 1.5, .1);
$("alpha").value = $("alphaN").value = randomBetween(180, 255, 1);
$("sniperWidth").value = $("sniperWidthN").value = randomBetween(1, 3, .1);
["r", "g", "b"].forEach((id, index) => {
$(id).value = $(id + "N").value = rgb[index];
});
$("color").value = "5";
$("dot").checked = Math.random() > .7;
$("outline").checked = Math.random() > .45;
$("t").checked = Math.random() > .82;
$("deployed").checked = false;
$("useAlpha").checked = true;
$("recoil").checked = Math.random() > .5;
syncColorUi();
update();
setStatus("Randomized classic static config");
}
loadState();
renderMapList();
selectMap(activeMap, false);
setGrid(gridEnabled, false);
cacheMapImages();
syncColorUi();
ranges.forEach(id => {
$(id).oninput = () => syncRange(id);
$(id + "N").oninput = () => syncRange(id, true);
});
document.querySelectorAll(".step").forEach(button => {
button.onclick = () => {
stepRange(button.dataset.step, Number(button.dataset.dir));
};
});
["dot", "outline", "t", "deployed", "useAlpha", "recoil"].forEach(id => {
$(id).oninput = update;
});
$("color").onchange = () => {
const preset = colorMap[$("color").value];
if (preset) {
preset.forEach((value, index) => {
const id = ["r", "g", "b"][index];
$(id).value = value;
$(id + "N").value = value;
});
}
syncColorUi();
update();
};
$("hexColorPicker").oninput = () => {
applyHexColor($("hexColorPicker").value);
};
$("mapButton").onclick = () => {
const open = $("mapPicker").classList.toggle("open");
$("mapButton").setAttribute("aria-expanded", String(open));
};
document.addEventListener("click", event => {
if (!$("mapPicker").contains(event.target)) {
closeMapPicker();
}
});
document.addEventListener("keydown", event => {
if (event.key === "Escape") {
closeMapPicker();
}
});
$("gridToggle").onclick = () => {
setGrid(!gridEnabled);
};
$("randomize").onclick = randomize;
$("copy").onclick = async () => {
try {
await navigator.clipboard.writeText($("output").value);
} catch {
$("output").focus();
$("output").select();
document.execCommand("copy");
}
$("copy").textContent = "Copied";
setStatus("Commands copied");
setTimeout(() => {
$("copy").textContent = "Copy";
}, 1100);
};
$("reset").onclick = () => {
try {
localStorage.removeItem(STORAGE_KEY);
} catch {}
document.querySelectorAll("input[type=range],input[type=number]").forEach(element => {
element.value = element.defaultValue;
});
document.querySelectorAll("input[type=checkbox]").forEach(element => {
element.checked = element.defaultChecked;
});
$("style").value = "4";
$("color").value = "5";
activeMap = "none";
selectMap("none", false);
setGrid(true, false);
syncColorUi();
update();
setStatus("Reset complete");
};
update();
</script>
</body>
</html>