stable
This commit is contained in:
parent
860471f805
commit
c523bf4380
8 changed files with 76 additions and 26 deletions
7
app.js
7
app.js
|
|
@ -16,6 +16,7 @@ const modes = [
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
seedText: "114514",
|
seedText: "114514",
|
||||||
|
generationType: "auto",
|
||||||
mode: "all",
|
mode: "all",
|
||||||
showFeatures: true,
|
showFeatures: true,
|
||||||
showLabels: true,
|
showLabels: true,
|
||||||
|
|
@ -26,6 +27,7 @@ const state = {
|
||||||
const canvas = document.getElementById("mapCanvas");
|
const canvas = document.getElementById("mapCanvas");
|
||||||
const canvasShell = document.querySelector(".canvas-shell");
|
const canvasShell = document.querySelector(".canvas-shell");
|
||||||
const seedInput = document.getElementById("seed");
|
const seedInput = document.getElementById("seed");
|
||||||
|
const generationTypeInput = document.getElementById("generationType");
|
||||||
const randomSeedButton = document.getElementById("randomSeed");
|
const randomSeedButton = document.getElementById("randomSeed");
|
||||||
const showFeaturesInput = document.getElementById("showFeatures");
|
const showFeaturesInput = document.getElementById("showFeatures");
|
||||||
const showLabelsInput = document.getElementById("showLabels");
|
const showLabelsInput = document.getElementById("showLabels");
|
||||||
|
|
@ -336,10 +338,11 @@ function renderModeButtons() {
|
||||||
|
|
||||||
async function regenerate() {
|
async function regenerate() {
|
||||||
state.seedText = seedInput.value;
|
state.seedText = seedInput.value;
|
||||||
|
state.generationType = generationTypeInput?.value || "auto";
|
||||||
setProgressVisible(true, "Preparing generation...");
|
setProgressVisible(true, "Preparing generation...");
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
try {
|
try {
|
||||||
state.map = await generateMapAsync(parseSeed(state.seedText), { onProgress: updateGenerationProgress });
|
state.map = await generateMapAsync(parseSeed(state.seedText), { onProgress: updateGenerationProgress, terrainType: state.generationType });
|
||||||
state.hoverEntities = buildHoverEntities(state.map);
|
state.hoverEntities = buildHoverEntities(state.map);
|
||||||
renderStats(state.map);
|
renderStats(state.map);
|
||||||
redraw();
|
redraw();
|
||||||
|
|
@ -369,6 +372,8 @@ function init() {
|
||||||
if (event.key === "Enter") regenerate();
|
if (event.key === "Enter") regenerate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
generationTypeInput?.addEventListener("change", regenerate);
|
||||||
|
|
||||||
randomSeedButton.addEventListener("click", () => {
|
randomSeedButton.addEventListener("click", () => {
|
||||||
seedInput.value = String(Math.floor(Math.random() * 9999999));
|
seedInput.value = String(Math.floor(Math.random() * 9999999));
|
||||||
regenerate();
|
regenerate();
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,15 @@
|
||||||
<section class="card">
|
<section class="card">
|
||||||
<label class="label" for="seed">Seed</label>
|
<label class="label" for="seed">Seed</label>
|
||||||
<input id="seed" class="input" value="114514" />
|
<input id="seed" class="input" value="114514" />
|
||||||
|
<label class="label inline-label" for="generationType">Generation Type</label>
|
||||||
|
<select id="generationType" class="input">
|
||||||
|
<option value="auto">Auto</option>
|
||||||
|
<option value="tohoku_spine">Tohoku spine</option>
|
||||||
|
<option value="chubu_mountain">Chubu mountain</option>
|
||||||
|
<option value="setouchi_inland_sea">Setouchi inland sea</option>
|
||||||
|
<option value="kanto_alluvial">Kanto alluvial plain</option>
|
||||||
|
<option value="mixed_archipelago">Mixed archipelago</option>
|
||||||
|
</select>
|
||||||
<button id="randomSeed" type="button" class="primary-button">Generate Random Seed</button>
|
<button id="randomSeed" type="button" class="primary-button">Generate Random Seed</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
39
mapOutput.js
39
mapOutput.js
|
|
@ -96,7 +96,7 @@ function assignMunicipalityPopulations(adminCenters, adminId, fields, settlement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function promotePrefectureCapitalPopulations(prefectureRegionId, sea, modernCities, markets, adminCenters, fields, seed, minPopulation = 200000) {
|
function promotePrefectureCapitalPopulations(prefectureRegionId, sea, modernCities, markets, adminCenters, fields, seed, minPopulation = 200000, focusedPrefectureMask = null) {
|
||||||
if (!prefectureRegionId) return 0;
|
if (!prefectureRegionId) return 0;
|
||||||
const prefIds = new Set();
|
const prefIds = new Set();
|
||||||
for (let i = 0; i < prefectureRegionId.length; i++) {
|
for (let i = 0; i < prefectureRegionId.length; i++) {
|
||||||
|
|
@ -135,6 +135,24 @@ function promotePrefectureCapitalPopulations(prefectureRegionId, sea, modernCiti
|
||||||
if (!p || !inside(p.x, p.y)) return -1;
|
if (!p || !inside(p.x, p.y)) return -1;
|
||||||
return prefectureRegionId[indexOf(p.x, p.y)] ?? -1;
|
return prefectureRegionId[indexOf(p.x, p.y)] ?? -1;
|
||||||
}
|
}
|
||||||
|
const focusedPrefCounts = new Map();
|
||||||
|
if (focusedPrefectureMask) {
|
||||||
|
for (let i = 0; i < focusedPrefectureMask.length; i++) {
|
||||||
|
if (!focusedPrefectureMask[i] || sea[i]) continue;
|
||||||
|
const prefId = prefectureRegionId[i] ?? -1;
|
||||||
|
if (prefId >= 0) focusedPrefCounts.set(prefId, (focusedPrefCounts.get(prefId) || 0) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const focusedPrefId = focusedPrefCounts.size
|
||||||
|
? [...focusedPrefCounts.entries()].sort((a, b) => b[1] - a[1])[0][0]
|
||||||
|
: 0;
|
||||||
|
for (const city of modernCities || []) {
|
||||||
|
if (city.isPrefecturalCapital) {
|
||||||
|
city.isPrefecturalCapital = false;
|
||||||
|
city.rank = city.isRegionalCapital ? "Regional Capital" : city.population >= 200000 ? "Regional City" : "Local City";
|
||||||
|
city.kind = city.rank;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const prefId of [...prefIds].sort((a, b) => a - b)) {
|
for (const prefId of [...prefIds].sort((a, b) => a - b)) {
|
||||||
const cities = (modernCities || []).filter((p) => prefAt(p) === prefId);
|
const cities = (modernCities || []).filter((p) => prefAt(p) === prefId);
|
||||||
let target = cities.slice().sort((a, b) =>
|
let target = cities.slice().sort((a, b) =>
|
||||||
|
|
@ -175,10 +193,10 @@ function promotePrefectureCapitalPopulations(prefectureRegionId, sea, modernCiti
|
||||||
target.population = promotedPopulation;
|
target.population = promotedPopulation;
|
||||||
promoted++;
|
promoted++;
|
||||||
}
|
}
|
||||||
target.isPrefecturalCapital = true;
|
|
||||||
target.isRegionalCapital = true;
|
target.isRegionalCapital = true;
|
||||||
target.rank = "Prefectural Capital";
|
target.isPrefecturalCapital = prefId === focusedPrefId;
|
||||||
target.kind = "Prefectural Capital";
|
target.rank = target.isPrefecturalCapital ? "Prefectural Capital" : "Regional Capital";
|
||||||
|
target.kind = target.rank;
|
||||||
target.labelPriorityBase = Math.max(target.labelPriorityBase || 0, 1150);
|
target.labelPriorityBase = Math.max(target.labelPriorityBase || 0, 1150);
|
||||||
target.urbanRadius = clamp(6.2 + Math.sqrt(target.population) / 80, 9, 30);
|
target.urbanRadius = clamp(6.2 + Math.sqrt(target.population) / 80, 9, 30);
|
||||||
target.coreRadius = clamp(2.0 + Math.sqrt(target.population) / 390, 2.4, 6.4);
|
target.coreRadius = clamp(2.0 + Math.sqrt(target.population) / 390, 2.4, 6.4);
|
||||||
|
|
@ -480,6 +498,17 @@ export function finishMapOutput({
|
||||||
center.canonicalSettlementId = best.id;
|
center.canonicalSettlementId = best.id;
|
||||||
center.canonicalSettlementName = best.name;
|
center.canonicalSettlementName = best.name;
|
||||||
center.municipalityRootName = best.name;
|
center.municipalityRootName = best.name;
|
||||||
|
const bestAdmin = adminId?.[indexOf(best.x, best.y)];
|
||||||
|
const canSnapOffice = inside(best.x, best.y) && !sea[indexOf(best.x, best.y)] && (
|
||||||
|
centerAdmin == null || centerAdmin < 0 || bestAdmin == null || bestAdmin < 0 || bestAdmin === centerAdmin
|
||||||
|
);
|
||||||
|
if (canSnapOffice) {
|
||||||
|
center.generatedOfficeX = center.generatedOfficeX ?? center.x;
|
||||||
|
center.generatedOfficeY = center.generatedOfficeY ?? center.y;
|
||||||
|
center.x = best.x;
|
||||||
|
center.y = best.y;
|
||||||
|
center.officeSnappedToSettlement = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
center.municipalityRootName = center.generatedMunicipalityName;
|
center.municipalityRootName = center.generatedMunicipalityName;
|
||||||
}
|
}
|
||||||
|
|
@ -513,7 +542,7 @@ export function finishMapOutput({
|
||||||
center.municipalityName = candidate;
|
center.municipalityName = candidate;
|
||||||
usedAdminNames.add(center.name);
|
usedAdminNames.add(center.name);
|
||||||
}
|
}
|
||||||
const promotedPrefectureCapitals = promotePrefectureCapitalPopulations(prefectureRegionId, sea, modernCities, markets, adminCenters, nameFields, seed, 220000);
|
const promotedPrefectureCapitals = promotePrefectureCapitalPopulations(prefectureRegionId, sea, modernCities, markets, adminCenters, nameFields, seed, 220000, prefectureMask);
|
||||||
assignMunicipalityPopulations(adminCenters, adminId, nameFields, [
|
assignMunicipalityPopulations(adminCenters, adminId, nameFields, [
|
||||||
...modernCities,
|
...modernCities,
|
||||||
...markets,
|
...markets,
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export function generateMap(seedInput = 114514, options = {}) {
|
||||||
const generationTimings = [];
|
const generationTimings = [];
|
||||||
const stage = (key, label, fn) => timedStage(generationTimings, options, key, label, fn);
|
const stage = (key, label, fn) => timedStage(generationTimings, options, key, label, fn);
|
||||||
|
|
||||||
const terrain = stage("terrain", "Terrain, rivers, and natural compartments", () => generateTerrainAndRivers(seed));
|
const terrain = stage("terrain", "Terrain, rivers, and natural compartments", () => generateTerrainAndRivers(seed, options));
|
||||||
const {
|
const {
|
||||||
elevation,
|
elevation,
|
||||||
slope,
|
slope,
|
||||||
|
|
@ -134,7 +134,7 @@ export async function generateMapAsync(seedInput = 114514, options = {}) {
|
||||||
const generationTimings = [];
|
const generationTimings = [];
|
||||||
const stage = (key, label, fn) => timedStageAsync(generationTimings, options, key, label, fn);
|
const stage = (key, label, fn) => timedStageAsync(generationTimings, options, key, label, fn);
|
||||||
|
|
||||||
const terrain = await stage("terrain", "Terrain, rivers, and natural compartments", () => generateTerrainAndRivers(seed));
|
const terrain = await stage("terrain", "Terrain, rivers, and natural compartments", () => generateTerrainAndRivers(seed, options));
|
||||||
const {
|
const {
|
||||||
elevation,
|
elevation,
|
||||||
slope,
|
slope,
|
||||||
|
|
|
||||||
|
|
@ -168,13 +168,13 @@ const TERRAIN_TYPES = [
|
||||||
mountainOffsetRange: [0.47, 0.53],
|
mountainOffsetRange: [0.47, 0.53],
|
||||||
baseHeightRange: [0.56, 0.82],
|
baseHeightRange: [0.56, 0.82],
|
||||||
primaryLengthRange: [0.76, 0.96],
|
primaryLengthRange: [0.76, 0.96],
|
||||||
primaryWidthRange: [0.13, 0.22],
|
primaryWidthRange: [0.18, 0.30],
|
||||||
systemCountRange: [12, 16],
|
systemCountRange: [14, 18],
|
||||||
beltCountRange: [3, 4],
|
beltCountRange: [4, 5],
|
||||||
angleSpread: 0.14,
|
angleSpread: 0.18,
|
||||||
crossSpread: 0.38,
|
crossSpread: 0.58,
|
||||||
lengthScale: 1.22,
|
lengthScale: 1.22,
|
||||||
widthScale: 0.92,
|
widthScale: 1.16,
|
||||||
heightScale: 0.86,
|
heightScale: 0.86,
|
||||||
coastStrength: 0.90,
|
coastStrength: 0.90,
|
||||||
plainBiasRange: [0.16, 0.34],
|
plainBiasRange: [0.16, 0.34],
|
||||||
|
|
@ -283,7 +283,12 @@ const TERRAIN_TYPES = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function pickTerrainType(seed) {
|
function pickTerrainType(seed, requestedType = "auto") {
|
||||||
|
if (requestedType && requestedType !== "auto") {
|
||||||
|
const normalizedType = requestedType === "touhoku_spine" ? "tohoku_spine" : requestedType;
|
||||||
|
const selected = TERRAIN_TYPES.find((type) => type.id === normalizedType);
|
||||||
|
if (selected) return selected;
|
||||||
|
}
|
||||||
// Terrain type selection is intentionally uniform. Individual terrain
|
// Terrain type selection is intentionally uniform. Individual terrain
|
||||||
// templates still contain their own parameter ranges, but there is no
|
// templates still contain their own parameter ranges, but there is no
|
||||||
// terrain-type appearance weighting.
|
// terrain-type appearance weighting.
|
||||||
|
|
@ -299,8 +304,8 @@ function rangeInt(seed, salt, [lo, hi]) {
|
||||||
return Math.round(lo + rand(seed, salt) * (hi - lo));
|
return Math.round(lo + rand(seed, salt) * (hi - lo));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildTerrainTemplate(seed) {
|
export function buildTerrainTemplate(seed, options = {}) {
|
||||||
const terrainType = pickTerrainType(seed);
|
const terrainType = pickTerrainType(seed, options.terrainType || options.generationType || "auto");
|
||||||
const mountainMode = terrainType.mountainMode === "mixed"
|
const mountainMode = terrainType.mountainMode === "mixed"
|
||||||
? (rand(seed, 12) < 0.42 ? "range" : rand(seed, 13) < 0.72 ? "mixed" : "massif")
|
? (rand(seed, 12) < 0.42 ? "range" : rand(seed, 13) < 0.72 ? "mixed" : "massif")
|
||||||
: terrainType.mountainMode;
|
: terrainType.mountainMode;
|
||||||
|
|
@ -1132,7 +1137,7 @@ function enforceLandGradient(elevation, sea, seaLevel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateTerrainAndRivers(seed) {
|
export function generateTerrainAndRivers(seed, options = {}) {
|
||||||
const fields = createMapFields();
|
const fields = createMapFields();
|
||||||
fields.visibleRavineField = new Float32Array(SIZE);
|
fields.visibleRavineField = new Float32Array(SIZE);
|
||||||
fields.surfaceTextureField = new Float32Array(SIZE);
|
fields.surfaceTextureField = new Float32Array(SIZE);
|
||||||
|
|
@ -1145,7 +1150,7 @@ export function generateTerrainAndRivers(seed) {
|
||||||
passSuitability,
|
passSuitability,
|
||||||
} = fields;
|
} = fields;
|
||||||
|
|
||||||
const terrainTemplate = buildTerrainTemplate(seed);
|
const terrainTemplate = buildTerrainTemplate(seed, options);
|
||||||
const systems = buildMountainSystems(terrainTemplate, seed);
|
const systems = buildMountainSystems(terrainTemplate, seed);
|
||||||
const allRidges = systems.flatMap((system, id) => buildScratchRidges(system, seed, id));
|
const allRidges = systems.flatMap((system, id) => buildScratchRidges(system, seed, id));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1367,11 +1367,12 @@ export function buildDensityFlowRoadTransportSystem(ctx) {
|
||||||
}
|
}
|
||||||
return best ? { target: best, d: bestD } : null;
|
return best ? { target: best, d: bestD } : null;
|
||||||
}
|
}
|
||||||
function stitchEndpoints(paths, mode, targets, maxAdds, radius) {
|
function stitchEndpoints(paths, mode, targets, maxAdds, radius, probability = 1) {
|
||||||
let added = 0;
|
let added = 0;
|
||||||
const endpoints = endpointListWithIds(paths);
|
const endpoints = endpointListWithIds(paths);
|
||||||
for (const ep of endpoints) {
|
for (const ep of endpoints) {
|
||||||
if (added >= maxAdds) break;
|
if (added >= maxAdds) break;
|
||||||
|
if (probability < 1 && hash2(ep.x, ep.y, seed + 18331 + added * 17) > probability) continue;
|
||||||
const near = nearestWithin(ep, targets, radius, true);
|
const near = nearestWithin(ep, targets, radius, true);
|
||||||
if (near && addShortConnector(paths, ep, near.target, mode)) added++;
|
if (near && addShortConnector(paths, ep, near.target, mode)) added++;
|
||||||
}
|
}
|
||||||
|
|
@ -1380,7 +1381,7 @@ export function buildDensityFlowRoadTransportSystem(ctx) {
|
||||||
const expresswayCells = sampledCells(expressways, 1);
|
const expresswayCells = sampledCells(expressways, 1);
|
||||||
const nationalCells = sampledCells([...nationalRoads, ...externalRoads], 1);
|
const nationalCells = sampledCells([...nationalRoads, ...externalRoads], 1);
|
||||||
const localCells = sampledCells(minorRoads, 1);
|
const localCells = sampledCells(minorRoads, 1);
|
||||||
debug.expressway += stitchEndpoints(expressways, "expressway", expresswayCells, 8, 34.0);
|
debug.expressway += stitchEndpoints(expressways, "expressway", expresswayCells, 14, 46.0, 0.62);
|
||||||
debug.national += stitchEndpoints(nationalRoads, "national", nationalCells, 48, 14.0);
|
debug.national += stitchEndpoints(nationalRoads, "national", nationalCells, 48, 14.0);
|
||||||
debug.localToNational += stitchEndpoints(minorRoads, "local", nationalCells, 240, 20.0);
|
debug.localToNational += stitchEndpoints(minorRoads, "local", nationalCells, 240, 20.0);
|
||||||
debug.local += stitchEndpoints(minorRoads, "local", localCells, 260, 16.0);
|
debug.local += stitchEndpoints(minorRoads, "local", localCells, 260, 16.0);
|
||||||
|
|
|
||||||
|
|
@ -1018,8 +1018,8 @@ export function drawMap(canvas, map, options) {
|
||||||
for (const path of map.externalRailways) drawLandPath(ctx, map, path, "rgba(255, 255, 255, 0.78)", 3.6, false, 3);
|
for (const path of map.externalRailways) drawLandPath(ctx, map, path, "rgba(255, 255, 255, 0.78)", 3.6, false, 3);
|
||||||
}
|
}
|
||||||
if (showRoads) {
|
if (showRoads) {
|
||||||
for (const path of map.expressways) drawLandPath(ctx, map, path, "rgba(105, 125, 105, 0.78)", 4.6);
|
for (const path of map.expressways) drawPath(ctx, path, "rgba(105, 125, 105, 0.78)", 4.6);
|
||||||
for (const path of map.externalExpressways) drawLandPath(ctx, map, path, "rgba(105, 125, 105, 0.78)", 4.6);
|
for (const path of map.externalExpressways) drawPath(ctx, path, "rgba(105, 125, 105, 0.78)", 4.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Transport fills. Expressways remain above railways; railways sit above ordinary roads.
|
// 5. Transport fills. Expressways remain above railways; railways sit above ordinary roads.
|
||||||
|
|
@ -1037,8 +1037,8 @@ export function drawMap(canvas, map, options) {
|
||||||
for (const path of map.externalRailways) drawLandRailway(ctx, map, path, "rgba(95, 95, 95, 1)", 1.55, 5.0, 7.0);
|
for (const path of map.externalRailways) drawLandRailway(ctx, map, path, "rgba(95, 95, 95, 1)", 1.55, 5.0, 7.0);
|
||||||
}
|
}
|
||||||
if (showRoads) {
|
if (showRoads) {
|
||||||
for (const path of map.expressways) drawLandPath(ctx, map, path, "rgba(135, 160, 135, 0.88)", 2.4);
|
for (const path of map.expressways) drawPath(ctx, path, "rgba(135, 160, 135, 0.88)", 2.4);
|
||||||
for (const path of map.externalExpressways) drawLandPath(ctx, map, path, "rgba(135, 160, 135, 0.88)", 2.4);
|
for (const path of map.externalExpressways) drawPath(ctx, path, "rgba(135, 160, 135, 0.88)", 2.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Icons & Labels
|
// 6. Icons & Labels
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
*{box-sizing:border-box}
|
*{box-sizing:border-box}
|
||||||
body{margin:0;background:#f0f2f5;color:#2c2c2c;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
body{margin:0;background:#f0f2f5;color:#2c2c2c;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||||
button,input{font:inherit}
|
button,input,select{font:inherit}
|
||||||
code{background:#e8e8e8;border-radius:4px;padding:1px 4px}
|
code{background:#e8e8e8;border-radius:4px;padding:1px 4px}
|
||||||
.app{min-height:100vh;padding:16px}
|
.app{min-height:100vh;padding:16px}
|
||||||
.layout{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:16px;max-width:1360px;margin:0 auto}
|
.layout{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:16px;max-width:1360px;margin:0 auto}
|
||||||
|
|
@ -13,6 +13,7 @@ code{background:#e8e8e8;border-radius:4px;padding:1px 4px}
|
||||||
.sidebar{display:flex;flex-direction:column;gap:12px}
|
.sidebar{display:flex;flex-direction:column;gap:12px}
|
||||||
.card{padding:14px}
|
.card{padding:14px}
|
||||||
.label,.card-title{display:block;margin-bottom:12px;color:#1a1a1a;font-size:14px;font-weight:600}
|
.label,.card-title{display:block;margin-bottom:12px;color:#1a1a1a;font-size:14px;font-weight:600}
|
||||||
|
.inline-label{margin-top:12px}
|
||||||
.input{width:100%;border:1px solid rgba(0,0,0,0.15);background:#fff;color:#2c2c2c;border-radius:8px;padding:10px 12px;outline:none;transition:border-color 0.2s}
|
.input{width:100%;border:1px solid rgba(0,0,0,0.15);background:#fff;color:#2c2c2c;border-radius:8px;padding:10px 12px;outline:none;transition:border-color 0.2s}
|
||||||
.input:focus{border-color:#1a73e8;box-shadow:0 0 0 3px rgba(26,115,232,0.15)}
|
.input:focus{border-color:#1a73e8;box-shadow:0 0 0 3px rgba(26,115,232,0.15)}
|
||||||
.primary-button,.mode-button{border:0;border-radius:8px;padding:10px 12px;cursor:pointer;font-weight:600;transition:background 0.2s}
|
.primary-button,.mode-button{border:0;border-radius:8px;padding:10px 12px;cursor:pointer;font-weight:600;transition:background 0.2s}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue