Files
2026-10-13 18:59:49 +01:00

283 lines
16 KiB
PHP

<?php
/**
* Style lab (DEV ONLY): the public site's components on one page, with sliders
* for the visual effects added to style.css, so values can be tuned by eye and
* then copied back into the CSS. Nothing here is saved; reload resets it.
* Refuses to run when APP_ENV is prod.
*/
require __DIR__ . '/config/db.php';
require __DIR__ . '/config/functions.php';
$env = strtolower((string) (function_exists('env') ? env('APP_ENV', 'prod') : 'prod'));
if ($env === 'prod' || $env === 'production') { http_response_code(404); exit; }
$gyms = function_exists('db_gyms_published') ? db_gyms_published($pdo) : [];
$img_team = base_url('uploads/team/foto/1.jpg');
$img_gal = [base_url('site/assets/img/home.jpg'), base_url('site/assets/img/crossfit.jpg'), base_url('site/assets/img/footer-bg-gym.jpg')];
?><!doctype html>
<html lang="pt">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title>Laboratório de estilo — FitMe</title>
<?= link_tag('assets/vendor/bootstrap5/css/bootstrap.min.css') ?>
<?= link_tag('site/assets/fonts/fontawesome/css/all.css') ?>
<?= link_tag('site/assets/css/style.css') ?>
<style id="lab-overrides"></style>
<style>
body { padding-bottom: 40px; }
.lab-panel { position: fixed; top: 12px; right: 12px; z-index: 2000; width: 300px; max-height: calc(100vh - 24px); overflow: auto;
background: rgba(11,11,13,.94); color: #fff; padding: 14px 16px; border-radius: 8px; box-shadow: 0 8px 30px rgba(0,0,0,.5); font-size: .85rem; }
.lab-panel h2 { font-size: 1rem; margin: 0 0 10px; color: var(--fitme-accent, #C2D500); filter: none; }
.lab-panel label { display: flex; justify-content: space-between; gap: 8px; margin: 10px 0 2px; }
.lab-panel output { color: var(--fitme-accent, #C2D500); font-variant-numeric: tabular-nums; }
.lab-panel input[type=range] { width: 100%; accent-color: var(--fitme-accent, #C2D500); }
.lab-panel input[type=color] { width: 100%; height: 34px; border: 0; background: none; padding: 0; }
.lab-panel button.reset { margin-top: 12px; width: 100%; border: 1px solid #555; background: none; color: #fff; padding: 6px; border-radius: 4px; }
.lab-panel .hint { color: #aaa; font-size: .75rem; margin-top: 10px; }
.lab-sec { padding: 56px 0; }
.lab-sec.dark { background: var(--fitme-dark-2); color: #fff; }
.lab-sec.dark h1, .lab-sec.dark h2 { color: #fff; }
.lab-row { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; margin: 18px 0; }
.lab-label { font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; opacity: .6; margin: 26px 0 6px; }
.lab-gal { display: flex; gap: 12px; }
.lab-gal .gal-tile { width: 220px; height: 150px; }
.lab-card-wrap { max-width: 270px; }
@media (max-width: 991px) { .lab-panel { position: static; width: auto; max-height: none; margin: 12px; } }
</style>
</head>
<body>
<?php include __DIR__ . '/includes/env_ribbon.php'; ?>
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<filter id="pt-noise" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB">
<feTurbulence id="lab-turb" type="fractalNoise" baseFrequency=".9" numOctaves="2" seed="4" result="n"/>
<feColorMatrix id="lab-sat" in="n" type="saturate" values="0" result="g"/>
<feGaussianBlur id="lab-blur" in="g" stdDeviation="0" result="gb"/>
<feComponentTransfer in="gb" result="grain">
<feFuncR id="lab-fr" type="linear" slope=".15" intercept=".86"/>
<feFuncG id="lab-fg" type="linear" slope=".15" intercept=".86"/>
<feFuncB id="lab-fb" type="linear" slope=".15" intercept=".86"/>
<feFuncA type="linear" slope="0" intercept="1"/>
</feComponentTransfer>
<feBlend id="lab-blend" in="SourceGraphic" in2="grain" mode="multiply" result="mix"/>
<feComposite in="mix" in2="SourceGraphic" operator="in"/>
</filter>
</svg>
<aside class="lab-panel" aria-label="Controlos">
<h2>Laboratório de estilo</h2>
<label for="c-color">Cor da marca <output id="o-color">#C2D500</output></label>
<input type="color" id="c-color" value="#c2d500">
<label for="c-btn">Opacidade do grão — botões <output id="o-btn"></output></label>
<input type="range" id="c-btn" min="0" max="50" value="16">
<label for="c-head">Opacidade do grão — títulos h1/h2 <output id="o-head"></output></label>
<input type="range" id="c-head" min="0" max="100" value="25">
<label for="c-sat">Saturação das fotos <output id="o-sat"></output></label>
<input type="range" id="c-sat" min="40" max="100" value="90">
<label for="c-glow">Brilho dos links (hover) <output id="o-glow"></output></label>
<input type="range" id="c-glow" min="0" max="100" value="60">
<label for="c-spin">Volta da aresta rotativa <output id="o-spin"></output></label>
<input type="range" id="c-spin" min="10" max="100" value="32">
<label for="c-ring">Espessura da aresta (px) <output id="o-ring"></output></label>
<input type="range" id="c-ring" min="1" max="6" value="2">
<h2 style="margin-top:18px">Textura do grão</h2>
<label for="c-type">Tipo</label>
<select id="c-type" class="form-select form-select-sm"><option value="fractalNoise">Fino (fractalNoise)</option><option value="turbulence">Manchado (turbulence)</option></select>
<label for="c-freq">Tamanho do grão (frequência) <output id="o-freq"></output></label>
<input type="range" id="c-freq" min="5" max="200" value="90">
<label for="c-oct">Detalhe (oitavas) <output id="o-oct"></output></label>
<input type="range" id="c-oct" min="1" max="5" value="2">
<label for="c-seed">Semente <output id="o-seed"></output></label>
<input type="range" id="c-seed" min="0" max="50" value="4">
<label for="c-contrast">Contraste do grão <output id="o-contrast"></output></label>
<input type="range" id="c-contrast" min="20" max="250" value="100">
<label for="c-soft">Suavidade (desfoque) <output id="o-soft"></output></label>
<input type="range" id="c-soft" min="0" max="20" value="0">
<label for="c-tile">Mosaico dos botões (px) <output id="o-tile"></output></label>
<input type="range" id="c-tile" min="40" max="400" value="140">
<label for="c-bbm">Mistura nos botões</label>
<select id="c-bbm" class="form-select form-select-sm"><option>normal</option><option>multiply</option><option>overlay</option><option>soft-light</option><option>hard-light</option><option>screen</option><option>darken</option><option>lighten</option></select>
<label for="c-hbm">Mistura nos títulos</label>
<select id="c-hbm" class="form-select form-select-sm"><option>multiply</option><option>overlay</option><option>soft-light</option><option>screen</option><option>darken</option><option>lighten</option></select>
<label style="justify-content:flex-start;align-items:center"><input type="checkbox" id="c-mono" checked> Só cinzento (sem cor no grão)</label>
<label for="c-css">CSS gerado (copiar)</label>
<textarea id="c-css" class="form-control form-control-sm" rows="9" readonly style="font-family:monospace;font-size:.7rem"></textarea>
<button type="button" class="reset" id="c-reset">Repor valores</button>
<p class="hint">Passa o rato pelos botões, links, fotos, moldura e cartão para ver os efeitos. Os valores não são guardados.</p>
</aside>
<section class="lab-sec dark">
<div class="container">
<h1>Ginásio, Box, Aulas <span style="color:var(--fitme-accent)">&amp; Acompanhamento</span></h1>
<div class="lab-row">
<a href="#" class="site-btn">Inscreve-te já!</a>
<a href="#" class="site-btn sb-ghost">Ghost</a>
<a href="#" class="site-btn sb-dark">Dark</a>
<a href="#" class="site-btn sb-line" style="color:#fff;border-color:#fff">Line</a>
<button type="button" class="hero-text-link">Marca uma aula experimental</button>
</div>
<p>Um <a href="#">link no texto</a> sobre fundo escuro.</p>
</div>
</section>
<section class="lab-sec">
<div class="container">
<div class="section-title text-center deco">
<span class="section-eyebrow">Secção</span>
<h2>Título de secção (h2)</h2>
<p class="section-subtitle">Subtítulo de exemplo para ver o grão no h2 sobre fundo claro.</p>
</div>
<div class="lab-label">Botões e links</div>
<div class="lab-row">
<a href="#" class="site-btn">Botão principal</a>
<a href="#" class="nav-link crossfit-nav-link" style="display:inline-block;margin:0 !important">CrossFit</a>
<a href="#" class="gym-switch-btn">FitMe Mira</a>
<?php foreach (array_slice($gyms, 0, 2) as $g): ?><a href="#" class="gym-switch-btn" style="<?= htmlspecialchars(gym_brand_vars($g)) ?>"><?= htmlspecialchars($g['name']) ?></a><?php endforeach; ?>
</div>
<p>Texto corrido com um <a href="#">link normal</a> e outro <a href="#">link mais comprido para testar</a>.</p>
<div class="lab-label">Galeria (fotos)</div>
<div class="lab-gal">
<?php foreach ($img_gal as $i => $src): ?>
<div class="gal-tile"><a href="#" class="gal-link"><img src="<?= htmlspecialchars($src) ?>" alt="" draggable="false"></a></div>
<?php endforeach; ?>
</div>
<div class="lab-label">Equipa (moldura)</div>
<div class="trainer-item" style="display:inline-block">
<div class="trainer-pic"><img src="<?= htmlspecialchars($img_team) ?>" alt=""></div>
<h3 style="color:inherit">Nome do membro</h3>
</div>
<div class="lab-label">Modalidades (ícone)</div>
<div class="row" style="max-width:520px">
<div class="col-6"><div class="icon-box-item"><div class="ib-icon"><i class="fas fa-dumbbell"></i></div><h3>Musculação</h3></div></div>
<div class="col-6"><div class="icon-box-item"><div class="ib-icon"><i class="fas fa-bicycle"></i></div><h3>Cycling</h3></div></div>
</div>
<div class="lab-label">Mensalidades (cartão)</div>
<div class="lab-card-wrap">
<div class="price-card"><h3>Plano exemplo</h3><p>Passa o rato por cima.</p><a href="#" class="site-btn">Inscrever</a></div>
</div>
</div>
</section>
<script>
(function () {
var root = document.documentElement, ov = document.getElementById('lab-overrides');
var $ = function (id) { return document.getElementById(id); };
var DEF = { color: '#c2d500', btn: 16, head: 25, sat: 90, glow: 60, spin: 32, ring: 2,
type: 'fractalNoise', freq: 90, oct: 2, seed: 4, contrast: 100, soft: 0, tile: 140, bbm: 'normal', hbm: 'multiply', mono: true };
var st = Object.assign({}, DEF);
function noiseUrl() {
var f = (st.freq / 100).toFixed(2), op = (st.btn / 100).toFixed(3), c = st.contrast / 100;
var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='" + st.tile + "' height='" + st.tile + "'>" +
"<filter id='n' x='0' y='0' width='100%' height='100%'>" +
"<feTurbulence type='" + st.type + "' baseFrequency='" + f + "' numOctaves='" + st.oct + "' seed='" + st.seed + "' stitchTiles='stitch'/>" +
(st.mono ? "<feColorMatrix type='saturate' values='0'/>" : "") +
(st.soft > 0 ? "<feGaussianBlur stdDeviation='" + (st.soft / 10).toFixed(1) + "'/>" : "") +
(c !== 1 ? "<feComponentTransfer><feFuncR type='linear' slope='" + c + "' intercept='" + ((1 - c) / 2).toFixed(3) + "'/><feFuncG type='linear' slope='" + c + "' intercept='" + ((1 - c) / 2).toFixed(3) + "'/><feFuncB type='linear' slope='" + c + "' intercept='" + ((1 - c) / 2).toFixed(3) + "'/></feComponentTransfer>" : "") +
"</filter><rect width='100%' height='100%' filter='url(#n)' opacity='" + op + "'/></svg>";
return 'url("data:image/svg+xml,' + svg.replace(/</g, '%3C').replace(/>/g, '%3E').replace(/#/g, '%23').replace(/%(?![0-9A-F]{2})/g, '%25') + '")';
}
function brandVars(hex) {
var r = parseInt(hex.slice(1, 3), 16), g = parseInt(hex.slice(3, 5), 16), b = parseInt(hex.slice(5, 7), 16);
var sh = function (k) { return '#' + [r, g, b].map(function (v) { return ('0' + Math.round(v * k).toString(16)).slice(-2); }).join('').toUpperCase(); };
var lum = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
return { '--fitme-accent': hex.toUpperCase(), '--fitme-accent-rgb': r + ', ' + g + ', ' + b, '--fitme-accent-dark': sh(.86),
'--fitme-accent-ink': sh(lum > .5 ? .5 : .85), '--fitme-on-accent': lum > .5 ? '#0B0B0D' : '#FFFFFF' };
}
function apply() {
var v = brandVars(st.color);
Object.keys(v).forEach(function (k) { root.style.setProperty(k, v[k]); });
var url = noiseUrl();
root.style.setProperty('--noise', url);
// headings: the SVG filter
var s = st.head / 100, c = st.contrast / 100;
var slope = (s * .6 * c).toFixed(3), icpt = (1 - s * .6 * c / 2 - s * .6 * c / 2).toFixed(3);
['lab-fr', 'lab-fg', 'lab-fb'].forEach(function (id) { $(id).setAttribute('slope', slope); $(id).setAttribute('intercept', icpt); });
$('lab-turb').setAttribute('type', st.type);
$('lab-turb').setAttribute('baseFrequency', (st.freq / 100).toFixed(2));
$('lab-turb').setAttribute('numOctaves', st.oct);
$('lab-turb').setAttribute('seed', st.seed);
$('lab-sat').setAttribute('values', st.mono ? '0' : '1');
$('lab-blur').setAttribute('stdDeviation', (st.soft / 10).toFixed(1));
$('lab-blend').setAttribute('mode', st.hbm);
var glow = st.glow / 100, spin = (st.spin / 10).toFixed(1) + 's';
ov.textContent =
'.site-btn,.gym-switch-btn,.crossfit-nav-link { background-size: ' + st.tile + 'px ' + st.tile + 'px !important; background-blend-mode: ' + st.bbm + ' !important; }' +
'.gal-link img, .trainer-item .trainer-pic img { filter: saturate(' + (st.sat / 100) + '); }' +
'.gal-link:hover img, .trainer-item .trainer-pic:hover img { filter: saturate(1) brightness(1.05); }' +
'a:where(:not(.site-btn,.gym-switch-btn,.gal-link,.nav-link)):is(:hover,:focus-visible) { text-shadow: 0 0 10px rgba(var(--fitme-accent-rgb),' + (.6 * glow * 1.67).toFixed(2) + '), 0 0 22px rgba(var(--fitme-accent-rgb),' + (.3 * glow * 1.67).toFixed(2) + ') !important; }' +
'.site-btn::before,.site-btn::after,.gym-switch-btn::before,.gym-switch-btn::after,.price-card::before,.price-card::after,.gal-link::after,.icon-box-item .ib-icon::after,.trainer-item .trainer-pic:hover { animation-duration: ' + spin + ' !important; }' +
'.site-btn,.gym-switch-btn { --ring: ' + st.ring + 'px; }';
$('o-color').textContent = st.color.toUpperCase();
$('o-btn').textContent = st.btn + '%';
$('o-head').textContent = st.head + '%';
$('o-sat').textContent = st.sat + '%';
$('o-glow').textContent = st.glow + '%';
$('o-spin').textContent = spin;
$('o-ring').textContent = st.ring;
$('o-freq').textContent = (st.freq / 100).toFixed(2);
$('o-oct').textContent = st.oct;
$('o-seed').textContent = st.seed;
$('o-contrast').textContent = st.contrast + '%';
$('o-soft').textContent = (st.soft / 10).toFixed(1);
$('o-tile').textContent = st.tile;
$('c-css').value =
':root {\n --noise: ' + url + ';\n}\n' +
'.site-btn, .gym-switch-btn, .crossfit-nav-link {\n background-image: var(--noise) !important;\n background-size: ' + st.tile + 'px ' + st.tile + 'px !important;' +
(st.bbm !== 'normal' ? '\n background-blend-mode: ' + st.bbm + ' !important;' : '') + '\n}\n' +
'/* títulos: <filter id="pt-noise"> */\n' +
'feTurbulence type="' + st.type + '" baseFrequency="' + (st.freq / 100).toFixed(2) + '" numOctaves="' + st.oct + '" seed="' + st.seed + '"\n' +
'feColorMatrix saturate ' + (st.mono ? 0 : 1) + ' · feGaussianBlur ' + (st.soft / 10).toFixed(1) + '\n' +
'feFunc* slope="' + slope + '" intercept="' + icpt + '" · feBlend mode="' + st.hbm + '"';
}
var map = { 'c-color': 'color', 'c-btn': 'btn', 'c-head': 'head', 'c-sat': 'sat', 'c-glow': 'glow', 'c-spin': 'spin', 'c-ring': 'ring',
'c-type': 'type', 'c-freq': 'freq', 'c-oct': 'oct', 'c-seed': 'seed', 'c-contrast': 'contrast', 'c-soft': 'soft', 'c-tile': 'tile',
'c-bbm': 'bbm', 'c-hbm': 'hbm', 'c-mono': 'mono' };
var textual = { 'c-color': 1, 'c-type': 1, 'c-bbm': 1, 'c-hbm': 1 };
Object.keys(map).forEach(function (id) {
$(id).addEventListener('input', function () {
st[map[id]] = id === 'c-mono' ? this.checked : (textual[id] ? this.value : +this.value);
apply();
});
});
$('c-reset').addEventListener('click', function () {
st = Object.assign({}, DEF);
Object.keys(map).forEach(function (id) { if (id === 'c-mono') $(id).checked = DEF.mono; else $(id).value = DEF[map[id]]; });
apply();
});
apply();
})();
</script>
</body>
</html>