Un solo motore totalitario per distruggere la frammentazione dei plugin di anteprima. il software moderno è paralizzato da sovrastrutture inutili. spesso, per mostrare una griglia di sotto-pagine, un carosello di articoli casuali o un semplice elenco di link utili, ci si ritrova con tre o quattro plugin diversi installati. questo crea un’entropia insostenibile nel database e fogli di stile ridondanti che appesantiscono il server.

per risolvere questa frammentazione su siti come luogogo.it, ho demolito i vecchi moduli isolati e ho fuso l’intera logica di rendering in un unico script totalitario slegato da bulma o kadence. un solo shortcode, [anteprime_contenuti], governa ora quattro layout grafici differenti portandosi dietro un css vanilla isolato che non va mai in conflitto con il tema attivo.

anatomia dei comandi e parametri di controllo

il comportamento del motore non è guidato da interfacce grafiche pesanti, ma viene pilotato da parametri precisi che puoi dichiarare all’interno delle parentesi quadre. ecco come funzionano le istruzioni sotto il cofano:

  • layout (default: “card”): è il selettore estetico principale. decide la veste grafica con cui i dati estratti vengono sputati a schermo. accetta quattro valori:
    • card: genera blocchi strutturati verticali con immagine in evidenza a 16:9, metadati e pulsante.
    • slider: trasforma le card in un carosello orizzontale scorrevole con frecce laterali.
    • testo: genera un elenco puntato minimalista con bordo sinistro colorato all’hover.
    • grafico: produce un menu compatto ad icone quadrate con l’immagine di anteprima rimpicciolita a lato del titolo.
  • ordine (default: “date”): cambia l’algoritmo di selezione del database. con date estrae i contenuti in ordine cronologico inverso. impostando ordine="rand", il motore attiva l’estrazione casuale globale, escludendo automaticamente le pagine istituzionali orfane (pagine radice senza padre) per mostrare solo articoli e sotto-pagine reali.
  • include (stringa): accetta una lista di id separati da virgola (es. include="12,14,99"). se valorizzato, forza la query a prelevare esclusivamente quei post nell’ordine esatto in cui li hai scritti, ignorando qualsiasi gerarchia automatica.
  • exclude (stringa): accetta una lista di id da censurare (es. exclude="45,67"). utile quando vuoi mostrare i figli di una pagina escludendo i contenuti di servizio o le utility.
  • colonne (default: “3”): determina la divisione dello spazio orizzontale su desktop per i layout card e slider. accetta valori da 1 a 4. se impostato su 1 senza slider, il sistema attiva automaticamente il layout hero espanso.
  • limite (default: “-1”): mette un tetto al numero di post stampati. il valore -1 significa “mostra tutto”. è consigliabile impostare un numero intero (es. limite="6") quando si sfrutta l’estrazione casuale.
  • titolo (stringa): inietta un titolo di sezione pulito immediatamente sopra il blocco dei contenuti, eliminando la necessità di creare intestazioni manuali nell’editor.
  • autoplay e delay (default: “si” / “3500”): controllano il carosello del layout slider. se non vuoi lo scorrimento automatico imposti autoplay="no". il delay regola il tempo di attesa in millisecondi prima del salto alla card successiva.

il codice sorgente completo da installare

lo script non ha dipendenze esterne. crea un file nominato mv-anteprime-contenuti.php, incollaci questo blocco e attivalo nella cartella dei plugin.

<?php
/**
 * Plugin Name: MV Shortcode - Anteprime Contenuti
 * Description: Motore unico [anteprime_contenuti] con quattro visualizzazioni (card, slider, testo, grafico). Comandi legacy eliminati, codice slegato da Bulma e isolato per Kadence.
 * Version: 3.3
 * Author: Marco Vaccaro
 * Author URI: https://marcovaccaro.it
 */

if ( ! defined( 'ABSPATH' ) ) { exit; }

function mv_css_anteprime_universale() {
    ?>
    <style>
        /* Griglia autonoma Flexbox (Layout: card) */
        .mv-grid { display: flex; flex-wrap: wrap; margin: 1.5rem -10px; gap: 20px 0; }
        .mv-col { padding: 0 10px; box-sizing: border-box; display: flex; width: 100%; }
        .mv-col.col-3 { width: 33.333%; }
        .mv-col.col-2 { width: 50%; }
        .mv-col.col-4 { width: 25%; }
        .mv-col.col-1 { width: 100%; }
        
        @media (max-width: 1024px) { .mv-col.col-3, .mv-col.col-4, .mv-col.col-2 { width: 50% !important; } }
        @media (max-width: 600px) { .mv-col { width: 100% !important; } }

        /* Struttura Slider isolata (Layout: slider) */
        .mv-slider-wrapper { position: relative; width: 100%; margin: 2rem 0; }
        .mv-slider-track { display: flex; gap: 1.5rem; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; padding: 1rem 0.5rem; scrollbar-width: none; }
        .mv-slider-track::-webkit-scrollbar { display: none; }
        .mv-slider-item { scroll-snap-align: start; min-width: 280px; display: flex; }
        .mv-slider-wrapper.col-1 .mv-slider-item { flex: 0 0 100%; }
        .mv-slider-wrapper.col-2 .mv-slider-item { flex: 0 0 calc(50% - 0.75rem); }
        .mv-slider-wrapper.col-3 .mv-slider-item { flex: 0 0 calc(33.333% - 1rem); }
        .mv-slider-wrapper.col-4 .mv-slider-item { flex: 0 0 calc(25% - 1.25rem); }
        
        .mv-slider-btn { position: absolute; top: 50%; transform: translateY(-50%); z-index: 10; background-color: #1a5f7a; color: #fff; border: none; border-radius: 50%; width: 45px; height: 45px; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; box-shadow: 0 4px 10px rgba(0,0,0,0.2); transition: background-color 0.3s ease, transform 0.2s ease; }
        .mv-slider-btn:hover { background-color: #113d4f; transform: translateY(-50%) scale(1.1); }
        .mv-slider-btn.prev { left: -15px; }
        .mv-slider-btn.next { right: -15px; }
        
        @media (max-width: 768px) {
            .mv-slider-btn { display: none; }
            .mv-slider-wrapper.col-1 .mv-slider-item, 
            .mv-slider-wrapper.col-2 .mv-slider-item, 
            .mv-slider-wrapper.col-3 .mv-slider-item,
            .mv-slider-wrapper.col-4 .mv-slider-item { flex: 0 0 85%; }
        }

        /* Struttura Card Standard */
        .mv-box { background: #fff; border: 1px solid #e0e0e0; border-radius: 6px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); width: 100%; }
        .mv-hover-card { transition: all 0.3s ease; cursor: pointer; }
        .mv-hover-card.is-vertical { height: 100%; display: flex; flex-direction: column; }
        .mv-hover-card:hover { transform: translateY(-5px); box-shadow: 0 15px 30px rgba(0,0,0,0.15) !important; }
        .mv-hover-card .card-image { overflow: hidden; position: relative; }
        .mv-hover-card .card-image img { transition: transform 0.5s ease; width: 100%; display: block; }
        .mv-hover-card:hover .card-image img { transform: scale(1.08); }
        
        /* Menu Testuale Puro (Layout: testo) */
        .mv-custom-menu-list { list-style: none; padding-left: 0; margin: 0; }
        .mv-custom-menu-list li { margin-bottom: 0.5rem; }
        .mv-custom-menu-list a { display: block; padding: 0.5em 0.75em; text-decoration: none; color: #4a4a4a; border-left: 2px solid #e0e0e0; transition: all 0.2s ease; font-size: 1rem; }
        .mv-custom-menu-list a:hover { color: #1a5f7a; border-left-color: #1a5f7a; background-color: #f9f9f9; }

        /* Menu Grafico ad Icone Piccole (Layout: grafico) */
        .mv-menu-grafico-list { display: flex; flex-direction: column; gap: 10px; }
        .mv-menu-grafico-item { display: flex; align-items: center; gap: 15px; padding: 8px; border-radius: 6px; transition: background-color 0.3s ease, transform 0.2s ease; text-decoration: none; color: inherit; }
        .mv-menu-grafico-item:hover { background-color: #f9f9f9; transform: translateX(5px); }
        .mv-menu-grafico-item:hover .mv-menu-grafico-title { color: #1a5f7a; }
        .mv-menu-grafico-img { width: 55px; height: 55px; border-radius: 6px; object-fit: cover; flex-shrink: 0; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
        .mv-menu-grafico-title { font-size: 1rem; font-weight: 600; line-height: 1.2; color: #363636; transition: color 0.3s ease; }

        /* Elementi globali di rifinitura */
        .mv-section-title { font-size: 1.25rem; font-weight: 700; margin-bottom: 1.25rem; color: #222; text-transform: uppercase; letter-spacing: 0.05em; }
        .mv-card-title { font-size: 1.25rem; font-weight: 700; margin-bottom: 0.75rem; line-height: 1.2; }
        .mv-card-title a { color: #333; text-decoration: none; transition: color 0.3s ease; }
        .mv-hover-card:hover .mv-card-title a { color: #1a5f7a !important; }
        .mv-meta { font-size: 0.8rem; color: #777; margin-bottom: 1.25rem; display: flex; flex-wrap: wrap; gap: 15px; }
        .mv-meta a { color: #777; text-decoration: underline; }
        .mv-content { font-size: 0.9rem; line-height: 1.6; margin-bottom: 2rem; flex-grow: 1; color: #4a4a4a; }
        .mv-button { display: inline-block; padding: 8px 16px; border: 1px solid #1a5f7a; color: #1a5f7a; text-align: center; border-radius: 4px; text-decoration: none; transition: all 0.3s; font-size: 0.85rem; font-weight: 600; }
        .mv-button.is-fullwidth { display: block; width: 100%; box-sizing: border-box; }
        .mv-hover-card:hover .mv-button { background-color: #1a5f7a; color: #fff; }
    </style>
    <?php
}
add_action('wp_head', 'mv_css_anteprime_universale');

function mv_shortcode_anteprime_universale($atts) {
    static $mv_esclusi_globali = array();

    $a = shortcode_atts(array(
        'id'       => '', 
        'include'  => '', 
        'exclude'  => '',
        'btn'      => 'Leggi tutto',
        'colonne'  => '3',
        'ordine'   => 'date', 
        'limite'   => '-1',   
        'slider'   => 'no',   
        'autoplay' => 'si',   
        'delay'    => 3500,
        'titolo'   => '',     
        'layout'   => 'card'   
    ), $atts);

    $is_slider = in_array(strtolower($a['slider']), array('si', 'true', '1')) || $a['layout'] === 'slider';
    $is_autoplay_disabled = in_array(strtolower($a['autoplay']), array('no', 'false', '0'));
    $delay = $is_autoplay_disabled ? 0 : intval($a['delay']);

    $col_class = 'col-3'; 
    if ($a['colonne'] == '2') $col_class = 'col-2';
    if ($a['colonne'] == '4') $col_class = 'col-4';
    if ($a['colonne'] == '1') $col_class = 'col-1';

    $args = array(
        'post_status'    => 'publish',
        'orderby'        => ($a['ordine'] === 'rand') ? 'rand' : 'date',
        'order'          => 'DESC'
    );

    if ($a['ordine'] === 'rand' && empty($a['id']) && empty($a['include'])) {
        $args['post_type'] = array('post', 'page');
        $limite_richiesto = ($a['limite'] == '-1') ? 6 : intval($a['limite']);
        $args['posts_per_page'] = $limite_richiesto * 3; 
        if (!empty($mv_esclusi_globali)) {
            $args['post__not_in'] = $mv_esclusi_globali;
        }
    } else {
        $args['posts_per_page'] = intval($a['limite']);
        if (!empty($a['include'])) {
            $include_ids = array_map('intval', explode(',', $a['include']));
            if (!empty($include_ids)) {
                $args['post__in'] = $include_ids;
                $args['orderby']  = 'post__in'; 
                $args['post_type'] = 'any';
            }
        } else {
            $parent_id = !empty($a['id']) ? intval($a['id']) : get_the_ID();
            if (!$parent_id) return '';
            $args['post_parent'] = $parent_id;
            $args['post_type'] = 'any';
        }

        if (!empty($a['exclude'])) {
            $exclude_ids = array_map('intval', explode(',', $a['exclude']));
            if (!empty($exclude_ids)) { $args['post__not_in'] = $exclude_ids; }
        }
    }

    $query_risultati = new WP_Query($args);
    if ( ! $query_risultati->have_posts() ) { return ''; }

    $post_validi_ids = array();
    $contatore_limite = ($a['ordine'] === 'rand' && empty($a['id']) && empty($a['include'])) ? $limite_richiesto : $query_risultati->post_count;

    while ( $query_risultati->have_posts() ) {
        $query_risultati->the_post();
        $p_id = get_the_ID();

        if ($a['ordine'] === 'rand' && empty($a['id']) && empty($a['include'])) {
            if (get_post_type($p_id) === 'page' && wp_get_post_parent_id($p_id) == 0) { continue; }
            $mv_esclusi_globali[] = $p_id;
        }

        $post_validi_ids[] = $p_id;
        if (count($post_validi_ids) >= $contatore_limite) { break; }
    }
    wp_reset_postdata();

    if (empty($post_validi_ids)) return '';
    $slider_id = uniqid('mv_slider_');

    ob_start(); ?>
    
    <?php if (!empty($a['titolo'])): ?>
        <p class="mv-section-title"><?php echo esc_html($a['titolo']); ?></p>
    <?php endif; ?>

    <?php 
    if ($a['layout'] === 'testo'): ?>
        <div class="mv-custom-menu-wrapper">
            <ul class="mv-custom-menu-list">
                <?php foreach ( $post_validi_ids as $post_id ) : ?>
                    <li>
                        <a href="<?php echo get_permalink($post_id); ?>">
                            <?php echo get_the_title($post_id); ?>
                        </a>
                    </li>
                <?php endforeach; ?>
            </ul>
        </div>

    <?php 
    elseif ($a['layout'] === 'grafico'): ?>
        <div class="mv-menu-grafico-list">
            <?php foreach ( $post_validi_ids as $post_id ) : 
                $g_title = get_the_title($post_id);
                $g_link  = get_permalink($post_id);
                $g_img   = get_the_post_thumbnail_url($post_id, 'thumbnail');
                ?>
                <a href="<?php echo esc_url($g_link); ?>" class="mv-menu-grafico-item">
                    <?php if ($g_img): ?>
                        <img src="<?php echo esc_url($g_img); ?>" alt="<?php echo esc_attr($g_title); ?>" class="mv-menu-grafico-img">
                    <?php else: ?>
                        <div class="mv-menu-grafico-img" style="background-color: #e9ecef; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; color: #b5b5b5;">📁</div>
                    <?php endif; ?>
                    <div class="mv-menu-grafico-title">
                        <?php echo esc_html($g_title); ?>
                    </div>
                </a>
            <?php endforeach; ?>
        </div>

    <?php 
    else: ?>
        <?php if ($is_slider): ?>
        <div class="mv-slider-wrapper <?php echo $col_class; ?>" id="<?php echo $slider_id; ?>">
            <button class="mv-slider-btn prev" onclick="mvSlideLeft('<?php echo $slider_id; ?>')">&#8249;</button>
            <div class="mv-slider-track">
        <?php else: ?>
        <div class="mv-grid">
        <?php endif; ?>

            <?php foreach ( $post_validi_ids as $post_id ) : 
                $link    = get_permalink($post_id);
                $title   = get_the_title($post_id);
                $img     = get_the_post_thumbnail_url($post_id, 'medium_large');
                $date    = get_the_date('', $post_id);
                $author  = get_the_author_meta('display_name', get_post_field('post_author', $post_id));
                
                $is_hero_layout = ($a['colonne'] == '1' && !$is_slider);
                $excerpt_limit = $is_hero_layout ? 65 : 40;
                $excerpt = has_excerpt($post_id) ? get_the_excerpt($post_id) : wp_trim_words(get_the_content(null, false, $post_id), $excerpt_limit);

                $item_parent_id = wp_get_post_parent_id($post_id);
                if ($item_parent_id && $item_parent_id > 0) {
                    $item_p_title = get_the_title($item_parent_id);
                    $item_p_url = get_permalink($item_parent_id);
                } else {
                    $item_p_title = (get_post_type($post_id) === 'post') ? 'Articolo' : 'Home Page';
                    $item_p_url = (get_post_type($post_id) === 'post') ? '#' : home_url('/');
                }
            ?>
            
            <?php if ($is_slider): ?>
            <div class="mv-slider-item">
            <?php else: ?>
            <div class="mv-col <?php echo esc_attr($col_class); ?>">
            <?php endif; ?>
                <div class="mv-box mv-hover-card is-vertical" onclick="location.href='<?php echo esc_url($link); ?>'" style="padding:0; overflow:hidden;">
                    <?php if ($img): ?>
                    <div class="card-image" style="aspect-ratio: 16/9; overflow: hidden; width: 100%;">
                        <a href="<?php echo esc_url($link); ?>" style="display: block; width: 100%; height: 100%;">
                            <img src="<?php echo esc_url($img); ?>" alt="<?php echo esc_attr($title); ?>" style="object-fit: cover; width: 100%; height: 100%;">
                        </a>
                    </div>
                    <?php endif; ?>
                    
                    <div class="card-content" style="padding: 2.5rem; flex-grow: 1; display: flex; flex-direction: column; box-sizing: border-box; <?php echo $is_hero_layout ? 'max-width: 850px; margin: 0 auto; width: 100%;' : ''; ?>">
                        <p class="mv-card-title" style="<?php echo $is_hero_layout ? 'font-size: 1.75rem; margin-bottom: 1rem;' : ''; ?>">
                            <a href="<?php echo esc_url($link); ?>">
                                <?php echo esc_html($title); ?>
                            </a>
                        </p>
                        
                        <div class="mv-meta" style="<?php echo $is_hero_layout ? 'margin-bottom: 1.5rem;' : ''; ?>">
                            <div><span>👤</span>&nbsp;<?php echo esc_html($author); ?></div>
                            <div><span>📅</span>&nbsp;<?php echo esc_html($date); ?></div>
                            <div>
                                <span>📂</span>&nbsp;
                                <?php if($item_p_url !== '#'): ?>
                                    <a href="<?php echo esc_url($item_p_url); ?>" onclick="event.stopPropagation();">
                                        <?php echo esc_html($item_p_title); ?>
                                    </a>
                                <?php else: ?>
                                    <span style="color: #777;"><?php echo esc_html($item_p_title); ?></span>
                                <?php endif; ?>
                            </div>
                        </div>
                        
                        <div class="mv-content" style="<?php echo $is_hero_layout ? 'font-size: 1.05rem; color: #444; margin-bottom: 2.5rem;' : ''; ?>">
                            <?php echo wp_kses_post($excerpt); ?>
                        </div>
                        
                        <div style="margin-top:auto;">
                            <span class="mv-button <?php echo $is_hero_layout ? '' : 'is-fullwidth'; ?>">
                                <?php echo esc_html($a['btn']); ?>
                            </span>
                        </div>
                    </div>
                </div>
            </div>
            <?php endforeach; ?>
        </div>

        <?php if ($is_slider): ?>
            <button class="mv-slider-btn next" onclick="mvSlideRight('<?php echo $slider_id; ?>')">&#8250;</button>
        </div>
        <script>
            if (typeof mvSlideLeft !== 'function') {
                window.mvSlideLeft = function(sliderId) {
                    const track = document.querySelector('#' + sliderId + ' .mv-slider-track');
                    const item = track.querySelector('.mv-slider-item');
                    if (!item) return;
                    const itemWidth = item.offsetWidth + 24;
                    track.scrollBy({ left: -itemWidth, behavior: 'smooth' });
                };
                window.mvSlideRight = function(sliderId) {
                    const track = document.querySelector('#' + sliderId + ' .mv-slider-track');
                    const item = track.querySelector('.mv-slider-item');
                    if (!item) return;
                    const itemWidth = item.offsetWidth + 24;
                    track.scrollBy({ left: itemWidth, behavior: 'smooth' });
                };
            }
            document.addEventListener('DOMContentLoaded', function() {
                const sliderWrapper = document.getElementById('<?php echo $slider_id; ?>');
                if (!sliderWrapper) return;
                const track = sliderWrapper.querySelector('.mv-slider-track');
                let autoScrollInterval;
                const scrollDelay = <?php echo $delay; ?>;

                function startAutoScroll() {
                    if (scrollDelay === 0) return;
                    autoScrollInterval = setInterval(function() {
                        const item = track.querySelector('.mv-slider-item');
                        if (!item) return;
                        const itemWidth = item.offsetWidth + 24;
                        if (track.scrollLeft + track.clientWidth >= track.scrollWidth - 10) {
                            track.scrollTo({ left: 0, behavior: 'smooth' }); 
                        } else {
                            track.scrollBy({ left: itemWidth, behavior: 'smooth' }); 
                        }
                    }, scrollDelay);
                }
                function stopAutoScroll() { clearInterval(autoScrollInterval); }

                startAutoScroll();
                sliderWrapper.addEventListener('mouseenter', stopAutoScroll);
                sliderWrapper.addEventListener('mouseleave', startAutoScroll);
                sliderWrapper.addEventListener('touchstart', stopAutoScroll, {passive: true});
                sliderWrapper.addEventListener('touchend', startAutoScroll, {passive: true});
            });
        </script>
        <?php endif; ?>
    <?php endif; ?>

    <?php return ob_get_clean();
}
add_shortcode('anteprime_contenuti', 'mv_shortcode_anteprime_universale');

esempi pratici e descrizione dell’effetto visivo

di seguito vengono analizzati i quattro casi d’uso principali, mostrando la sintassi esatta da scrivere nelle pagine e descrivendo l’output generato sul frontend dal motore css isolato.

1. griglia cronologica standard a tre colonne

  • comando: [anteprime_contenuti titolo="ultimi aggiornamenti"]
  • effetto: genera un’intestazione. sotto di essa, recupera automaticamente tutte le sotto-pagine collegate alla pagina corrente, impaginandole in una griglia simmetrica a 3 colonne su desktop. ogni card è un blocco bianco con angoli arrotondati e una leggera ombra; al passaggio del mouse la card si solleva di 5 pixel e l’immagine in evidenza (tagliata nativamente in 16:9) subisce un leggero zoom interno controllato senza debordare dal box.

2. articolo in evidenza con layout hero

  • comando: [anteprime_contenuti include="1661" colonne="1" btn="esamina il report"]
  • effetto: isola un singolo contenuto specifico. dal momento che la colonna è impostata a 1 e non è richiesto lo slider, il motore scarta il layout verticale compresso e attiva la visualizzazione hero. l’immagine occupa la larghezza totale del contenitore preservando le proporzioni 16:9 senza bande grigie o distorsioni. sotto di essa, il blocco di testo viene centrato e limitato a una larghezza di 850 pixel per impedire alle righe di allungarsi eccessivamente, aumentando il font del titolo a 1.75rem per dare massima rilevanza visiva.

3. carosello orizzontale di contenuti casuali

  • comando: [anteprime_contenuti ordine="rand" limite="6" layout="slider" titolo="potrebbe interessarti"]
  • effetto: ignora la gerarchia della pagina corrente. pesca 6 post casuali dall’intero sito, scartando le pagine orfane. invece di occupare spazio verticale, dispone le card in un flusso orizzontale continuo (carosello). su desktop mostra 3 card alla volta con due frecce laterali azzurre sospese ai lati; il sistema fa scorrere le card da solo ogni 3,5 secondi, ma interrompe il movimento se l’utente passa sopra con il mouse o tocca lo schermo da mobile. su smartphone le frecce spariscono e la traccia diventa scorrevole con il dito, mostrando l’85% della larghezza della card per far capire che c’è altro contenuto oltre il bordo dello schermo.

4. menu grafico compatto ad icone

  • comando: [anteprime_contenuti include="1665,944,1626" layout="grafico" titolo="altre rubriche"]
  • effetto: elimina totalmente la struttura a card grandi. genera una lista verticale compatta ideale per le barre laterali o i piè di pagina. ogni post incluso viene renderizzato come una riga singola: a sinistra compare l’immagine di anteprima quadrata rimpicciolita a 55 pixel (stile icona), a destra il titolo del post in grassetto. all’hover sulla riga, l’intero blocco si sposta fluidamente verso destra di 5 pixel e il testo si colora di azzurro, fornendo un feedback visivo immediato senza appesantire la pagina.

5. elenco testuale minimalista

  • comando: [anteprime_contenuti include="206" layout="testo" titolo="formazione"]
  • effetto: riduce l’output all’osso, eliminando immagini e metadati. stampa un elenco testuale puro. ogni elemento ha un bordo sinistro grigio chiaro di 2 pixel; quando l’utente passa il mouse sopra un link, il bordo di quel singolo elemento si colora istantaneamente di azzurro e lo sfondo della riga si tinge di un grigio quasi impercettibile, creando un menu di navigazione testuale pulito e privo di distrazioni visive.