index.html
por Felipe Augusto Barcelos
—
última modificação
07/05/2026 14h52
index.html
—
HTML,
11 KB (12265 bytes)
Conteúdo do arquivo
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Carta de Serviço 2026</title>
<style>
:root {
color-scheme: light;
--bg: #eef2f6;
--paper: #ffffff;
--ink: #172033;
--muted: #647086;
--line: #cfd7e3;
--accent: #0b6f6a;
--accent-strong: #07524f;
--shadow: 0 18px 45px rgba(23, 32, 51, .22);
font-family: Arial, Helvetica, sans-serif;
}
* { box-sizing: border-box; }
html,
body {
width: 100%;
min-height: 100%;
margin: 0;
background: var(--bg);
color: var(--ink);
}
body {
overflow: hidden;
}
.app {
display: grid;
grid-template-rows: auto 1fr;
min-height: 100vh;
}
.toolbar {
display: flex;
align-items: center;
gap: 8px;
min-height: 58px;
padding: 10px 12px;
border-bottom: 1px solid var(--line);
background: rgba(255, 255, 255, .96);
box-shadow: 0 1px 0 rgba(23, 32, 51, .04);
z-index: 5;
}
.title {
min-width: 155px;
margin-right: auto;
line-height: 1.15;
}
.title strong {
display: block;
font-size: 15px;
font-weight: 700;
}
.title span {
display: block;
color: var(--muted);
font-size: 12px;
margin-top: 2px;
}
button,
a.button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 38px;
border: 1px solid var(--line);
border-radius: 6px;
background: #fff;
color: var(--ink);
font: inherit;
line-height: 1;
text-decoration: none;
cursor: pointer;
transition: background .16s ease, border-color .16s ease, transform .16s ease;
}
button:hover,
a.button:hover {
background: #f7fafc;
border-color: #aeb9c8;
}
button:active,
a.button:active {
transform: translateY(1px);
}
button:disabled {
cursor: not-allowed;
opacity: .45;
}
.counter {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 86px;
height: 38px;
padding: 0 10px;
border: 1px solid var(--line);
border-radius: 6px;
background: #fff;
color: var(--muted);
font-size: 13px;
white-space: nowrap;
}
.stage {
position: relative;
display: grid;
place-items: center;
min-height: 0;
padding: 18px;
overflow: auto;
background:
linear-gradient(90deg, rgba(11, 111, 106, .08), transparent 28%, transparent 72%, rgba(11, 111, 106, .08)),
var(--bg);
}
.book {
position: relative;
width: min(92vw, 980px);
aspect-ratio: 1.414 / 1;
perspective: 1800px;
}
.spread {
position: absolute;
inset: 0;
display: grid;
grid-template-columns: 1fr 1fr;
filter: drop-shadow(var(--shadow));
transform-origin: center center;
transition: transform .2s ease;
}
.sheet {
position: relative;
overflow: hidden;
background: var(--paper);
}
.sheet.left {
border-radius: 6px 0 0 6px;
box-shadow: inset -10px 0 16px rgba(23, 32, 51, .10);
}
.sheet.right {
border-radius: 0 6px 6px 0;
box-shadow: inset 10px 0 16px rgba(23, 32, 51, .08);
}
.sheet.single {
grid-column: 1 / -1;
justify-self: center;
width: 50%;
border-radius: 6px;
box-shadow: none;
}
.page {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
background: #fff;
user-select: none;
-webkit-user-drag: none;
}
.turning .right {
animation: turnPage .46s ease;
transform-origin: left center;
}
.turning-back .left {
animation: turnBack .46s ease;
transform-origin: right center;
}
@keyframes turnPage {
0% { transform: rotateY(0); filter: brightness(1); }
50% { transform: rotateY(-52deg); filter: brightness(.92); }
100% { transform: rotateY(0); filter: brightness(1); }
}
@keyframes turnBack {
0% { transform: rotateY(0); filter: brightness(1); }
50% { transform: rotateY(52deg); filter: brightness(.92); }
100% { transform: rotateY(0); filter: brightness(1); }
}
.edge {
position: absolute;
top: 0;
bottom: 0;
width: 22%;
border: 0;
background: transparent;
z-index: 3;
}
.edge.prev { left: 0; }
.edge.next { right: 0; }
.edge:focus-visible {
outline: 3px solid rgba(11, 111, 106, .45);
outline-offset: 3px;
}
@media (max-width: 760px) {
body { overflow: auto; }
.app { min-height: 100svh; }
.toolbar {
flex-wrap: wrap;
align-content: center;
}
.title {
flex: 1 1 100%;
min-width: 0;
margin-right: 0;
}
.stage {
padding: 12px;
align-items: start;
}
.book {
width: min(94vw, 520px);
aspect-ratio: .707 / 1;
}
.spread {
grid-template-columns: 1fr;
}
.sheet,
.sheet.single {
grid-column: auto;
width: 100%;
border-radius: 6px;
box-shadow: none;
}
.sheet.left { display: none; }
}
</style>
</head>
<body>
<main class="app">
<nav class="toolbar" aria-label="Controles do flipbook">
<div class="title">
<strong>Carta de Serviço 2026</strong>
<span>Câmara Municipal</span>
</div>
<button id="first" type="button" title="Primeira página" aria-label="Primeira página">|<</button>
<button id="prev" type="button" title="Página anterior" aria-label="Página anterior"><</button>
<span class="counter" id="counter">1 / 16</span>
<button id="next" type="button" title="Próxima página" aria-label="Próxima página">></button>
<button id="last" type="button" title="Última página" aria-label="Última página">>|</button>
<button id="zoomOut" type="button" title="Diminuir zoom" aria-label="Diminuir zoom">-</button>
<button id="zoomIn" type="button" title="Aumentar zoom" aria-label="Aumentar zoom">+</button>
<button id="fullscreen" type="button" title="Tela cheia" aria-label="Tela cheia">[]</button>
<a class="button" href="carta-de-servico-2026.pdf" target="_blank" rel="noopener" title="Abrir PDF" aria-label="Abrir PDF">PDF</a>
</nav>
<section class="stage" id="stage">
<div class="book" id="book">
<div class="spread" id="spread"></div>
<button class="edge prev" id="edgePrev" type="button" aria-label="Página anterior"></button>
<button class="edge next" id="edgeNext" type="button" aria-label="Próxima página"></button>
</div>
</section>
</main>
<script>
const pages = [{"src":"pages/page-01.png","alt":"Carta de Serviço 2026 - página 1"},{"src":"pages/page-02.png","alt":"Carta de Serviço 2026 - página 2"},{"src":"pages/page-03.png","alt":"Carta de Serviço 2026 - página 3"},{"src":"pages/page-04.png","alt":"Carta de Serviço 2026 - página 4"},{"src":"pages/page-05.png","alt":"Carta de Serviço 2026 - página 5"},{"src":"pages/page-06.png","alt":"Carta de Serviço 2026 - página 6"},{"src":"pages/page-07.png","alt":"Carta de Serviço 2026 - página 7"},{"src":"pages/page-08.png","alt":"Carta de Serviço 2026 - página 8"},{"src":"pages/page-09.png","alt":"Carta de Serviço 2026 - página 9"},{"src":"pages/page-10.png","alt":"Carta de Serviço 2026 - página 10"},{"src":"pages/page-11.png","alt":"Carta de Serviço 2026 - página 11"},{"src":"pages/page-12.png","alt":"Carta de Serviço 2026 - página 12"},{"src":"pages/page-13.png","alt":"Carta de Serviço 2026 - página 13"},{"src":"pages/page-14.png","alt":"Carta de Serviço 2026 - página 14"},{"src":"pages/page-15.png","alt":"Carta de Serviço 2026 - página 15"},{"src":"pages/page-16.png","alt":"Carta de Serviço 2026 - página 16"}];
const spread = document.getElementById("spread");
const book = document.getElementById("book");
const counter = document.getElementById("counter");
const stage = document.getElementById("stage");
const controls = {
first: document.getElementById("first"),
prev: document.getElementById("prev"),
next: document.getElementById("next"),
last: document.getElementById("last"),
edgePrev: document.getElementById("edgePrev"),
edgeNext: document.getElementById("edgeNext"),
zoomOut: document.getElementById("zoomOut"),
zoomIn: document.getElementById("zoomIn"),
fullscreen: document.getElementById("fullscreen"),
};
let index = 0;
let zoom = 1;
function isMobileLayout() {
return window.matchMedia("(max-width: 760px)").matches;
}
function pageNode(page, side) {
const sheet = document.createElement("div");
sheet.className = "sheet " + side;
const img = document.createElement("img");
img.className = "page";
img.src = page.src;
img.alt = page.alt;
img.loading = "eager";
sheet.appendChild(img);
return sheet;
}
function render(direction = 0) {
spread.classList.remove("turning", "turning-back");
void spread.offsetWidth;
if (direction > 0) spread.classList.add("turning");
if (direction < 0) spread.classList.add("turning-back");
spread.innerHTML = "";
const mobile = isMobileLayout();
if (mobile || index === 0 || index === pages.length - 1) {
spread.appendChild(pageNode(pages[index], "single right"));
} else {
const leftIndex = index % 2 === 0 ? index - 1 : index;
const rightIndex = leftIndex + 1;
spread.appendChild(pageNode(pages[leftIndex], "left"));
if (pages[rightIndex]) spread.appendChild(pageNode(pages[rightIndex], "right"));
}
const shown = mobile || index === 0 || index === pages.length - 1
? String(index + 1)
: String((index % 2 === 0 ? index : index + 1)) + "-" + String(Math.min((index % 2 === 0 ? index + 1 : index + 2), pages.length));
counter.textContent = shown + " / " + pages.length;
controls.first.disabled = index === 0;
controls.prev.disabled = index === 0;
controls.next.disabled = index >= pages.length - 1;
controls.last.disabled = index >= pages.length - 1;
spread.style.transform = "scale(" + zoom.toFixed(2) + ")";
}
function goTo(nextIndex, direction) {
const bounded = Math.max(0, Math.min(pages.length - 1, nextIndex));
if (bounded === index) return;
index = bounded;
render(direction);
}
function nextPage() {
goTo(isMobileLayout() || index === 0 ? index + 1 : index + 2, 1);
}
function prevPage() {
goTo(isMobileLayout() || index <= 1 ? index - 1 : index - 2, -1);
}
controls.first.addEventListener("click", () => goTo(0, -1));
controls.prev.addEventListener("click", prevPage);
controls.edgePrev.addEventListener("click", prevPage);
controls.next.addEventListener("click", nextPage);
controls.edgeNext.addEventListener("click", nextPage);
controls.last.addEventListener("click", () => goTo(pages.length - 1, 1));
controls.zoomOut.addEventListener("click", () => { zoom = Math.max(.75, zoom - .1); render(); });
controls.zoomIn.addEventListener("click", () => { zoom = Math.min(1.8, zoom + .1); render(); });
controls.fullscreen.addEventListener("click", () => {
if (!document.fullscreenElement) stage.requestFullscreen?.();
else document.exitFullscreen?.();
});
window.addEventListener("keydown", event => {
if (event.key === "ArrowRight" || event.key === "PageDown") nextPage();
if (event.key === "ArrowLeft" || event.key === "PageUp") prevPage();
if (event.key === "Home") goTo(0, -1);
if (event.key === "End") goTo(pages.length - 1, 1);
});
window.addEventListener("resize", () => render());
render();
</script>
</body>
</html>