]> bicyclesonthemoon.info Git - ott/bsta/commitdiff
add title to viewer; further rework of 2words - only index generator left to do
authorb <rowerynaksiezycu@gmail.com>
Sat, 21 Oct 2023 16:45:09 +0000 (16:45 +0000)
committerb <rowerynaksiezycu@gmail.com>
Sat, 21 Oct 2023 16:45:09 +0000 (16:45 +0000)
2words.1.pl
bsta_lib.1.pm

index c00ee4fbb98854a65575dc69ff6e730ad5762295..9649b764ff036566150c288e4a75df220dbae588 100644 (file)
@@ -27,7 +27,13 @@ use Encode ('encode', 'decode');
 
 ###PERL_LIB: use lib /botm/lib/bsta
 use botm_common (
-       'read_data_file', 'write_data_file'
+       'read_data_file', 'write_data_file',
+       'join_path',
+       'merge_url',
+       'html_entitiy_encode_dec',
+       'print_html_start', 'print_html_end',
+       'print_html_head_start', 'print_html_head_end',
+       'print_html_body_start', 'print_html_body_end'
 );
 use bsta_lib (
        'STATE', 'INTF_STATE',
@@ -49,6 +55,8 @@ use bsta_lib (
 );
 use  File::Copy;
 
+###PERL_PATH_SEPARATOR:     PATH_SEPARATOR     = /
+
 ###PERL_CGI_PATH:           CGI_PATH           = /bsta/
 ###PERL_CGI_2WORDS_PATH:    CGI_2WORDS_PATH    = /bsta/2words
 ###PERL_CGI_CSS_PATH:       CGI_CSS_PATH       = /bsta/bsta.css
@@ -107,10 +115,10 @@ my $intf_state;
 my $intf_pass;
 my $intf_pause;
 my $intf_mode;
-my $story_path;
+my $story_i_path;
 my $story_file;
 my $story_lock;
-my @storylines;
+my @story_lines;
 my $ong_state;
 my $page;
 my $cmd_clear;
@@ -177,17 +185,14 @@ if (open ($story_file,"+<:encoding(UTF-8)",DATA_STORY_PATH())){
        $story_id   = int($story{'id'});
        $intf_pass  = int($story{'pass'});
        $intf_state = int($story{'state'});
-       $intf_mode  = $intf_state;
-       $intf_pause = $intf_state & 0x01;
-       if ($intf_pause) {
-               $intf_mode &= 0xFE;
-       }
+       $intf_mode  = $intf_state & INTF_STATE->{'mode'};
+       $intf_pause = $intf_state & INTF_STATE->{'||'};
        
        if ($IP ne $last_IP) {
                $turn = 1;
        }
        else {
-               $turn = 0       ;
+               $turn = 0;
        }
        
        if (
@@ -220,6 +225,7 @@ if (open ($story_file,"+<:encoding(UTF-8)",DATA_STORY_PATH())){
                # TODO: consider allowing non-ASCII letters in words.
                # (not very important in English language)
                elsif ($words =~ /^([!"\(\),\.:;\?][ \t]*)?([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\? \t][ \t]*)([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\?]?[ \t]*)$/) {
+                       # we have 2 words
                        $first_letter  = lc(substr($2, 0, 1));
                        $second_letter = lc(substr($4, 0, 1));
                        if (
@@ -232,28 +238,36 @@ if (open ($story_file,"+<:encoding(UTF-8)",DATA_STORY_PATH())){
                                $message = 'The second word can\'t also start with '.uc($first_letter).'.';
                        }
                        else {
+                               # words are valid
+                               # update state
                                $story{'content'} = $story{'content'} . $words."\n";
                                $turn = 0;
                                $story{'lastip'} = $IP;
                                $story{'letter'} = $second_letter;
                                
                                if ($cgi{'next'} ne '') {
+                                       # start next game
                                        if (split(/\r?\n/,$story{'content'}) >= (STORY_LENGTH-1)) {
-                                               $story_path = DATA_STORY_PATH.$story_id;
-                                               write_data_file($story_path, '', '', \%story);
+                                               # store finished game
+                                               $story_i_path = DATA_STORY_PATH.$story_id;
+                                               write_data_file($story_i_path, '', '', \%story);
+                                               # init new game
                                                $new_story{'id'     } = $story_id + 1;
                                                $new_story{'letter' } = '';
                                                $new_story{'lastip' } = $IP;
                                                $new_story{'content'} = '';
                                                $new_story{'pass'   } = 0;
                                                $new_story{'state'  } = INTF_STATE->{'X'};
+                                               # reset hidden interface
                                                $intf_state = INTF_STATE->{'X'};
                                                $intf_pass = 0;
                                                $intf_mode = INTF_STATE->{'X'};
                                                $intf_pause= 0;
                                                if($ong_state == STATE->{'inactive'}) {
+                                                       # ONG not activated yet; reset index
                                                        writeindex(WWW_INDEX_PATH,0,0,0); # TO REPLACE
                                                }
+                                               # save new game
                                                write_data_file($story_file, '', '', \%new_story);
                                        }
                                        else {
@@ -262,7 +276,9 @@ if (open ($story_file,"+<:encoding(UTF-8)",DATA_STORY_PATH())){
                                        }
                                }
                                else {
+                                       # continue the game
                                        if ($intf_pass == 1) {
+                                               # hidden interface was already active; deactivate
                                                $intf_pass = 2;
                                                $story{'pass'} = 2;
                                                if($ong_state == STATE->{'inactive'}) {
@@ -270,45 +286,57 @@ if (open ($story_file,"+<:encoding(UTF-8)",DATA_STORY_PATH())){
                                                }
                                        }
                                        elsif(lc($2).' '.lc($4) eq $settings{'unlock'}) {
+                                               # correct password for the hidden interface!
                                                if ($intf_pass != 0) {
                                                        $message = 'The password has already been used in this story.';
                                                }
                                                elsif ($ong_state != STATE->{'inactive'}) {
+                                                       # ONG already active, nothing to do here
                                                        $message = "???";
                                                }
                                                else {
-                                                       my %frame_data = read_data_file(DATA_PATH.0);
-                                                       my %default = read_data_file(DATA_DEFAULT_PATH());
+                                                       # ready to activate?
+                                                       my $frame_data_path;
+                                                       my %frame_data;
+                                                       my %default;
+                                                       my $frame_file;
                                                        my $in_path;
                                                        my $out_path;
                                                        
-                                                       $frame_data{'ongtime'} = $time;
-                                                       $goto_list{'title-0'} = $frame_data{'title'};
-                                                       $goto_list{'ongtime-0'} = $frame_data{'ongtime'};
-                                                       writedatafile(DATA_PATH.0,%frame_data);
-                                                       writedatafile(DATA_LIST_PATH,%goto_list);
+                                                       # prepare to ONG frame 0!
                                                        
-                                                       foreach my $ind (keys %default) {
-                                                               unless(defined($frame_data{$ind})){
-                                                                       $frame_data{$ind}=$default{$ind};
-                                                               }
-                                                       }
+                                                       $frame_data_path = joint_path(PATH_SEPARATOR(), DATA_PATH(), 0);
+                                                       %frame_data = read_data_file($frame_data_path);
+                                                       %default    = read_data_file(DATA_DEFAULT_PATH());
+                                                       %frame_data = merge_settings(\%default, \%frame_data);
                                                        
-                                                       $inpath = DATA_PATH.sprintf($settings{'frame'},0,$frame_data{'ext'});
-                                                       $outpath = WWW_PATH.sprintf($settings{'frame'},0,$frame_data{'ext'});
+                                                       $frame_file = sprintf($settings{'frame'}, 0, $frame_data{'ext'});
+                                                       $in_path  = join_path(PATH_SEPARATOR(), DATA_PATH(), $frame_file);;
+                                                       $out_path = join_path(PATH_SEPARATOR(), WWW_PATH(),  $frame_file);
+                                                       
+                                                       # set ONG time of frame 0
+                                                       # NOTE: might get overwritten later if ONG not launched
+                                                       $frame_data{'ongtime'} = $time;
+                                                       write_data_file($frame_data_path, '', '' \%frame_data);
                                                        
-                                                       if(copy ($inpath, $outpath)) {
+                                                       # update the GOTO list with frame 0
+                                                       $goto_list{'title-0'  } = $frame_data{'title'};
+                                                       $goto_list{'ongtime-0'} = $frame_data{'ongtime'};
+                                                       write_data_file(DATA_LIST_PATH(), '', '', \%goto_list);
+                                                       
+                                                       if(copy ($in_path, $out_path)) {
+                                                               # new state of hidden interface
                                                                $intf_pass = 1;
-                                                               $intf_state = 0;
-                                                               $intf_mode=0;
-                                                               $intf_pause=0;
-                                                               $story{'pass'} = '1';
-                                                               $story{'state'} = '0';
-                                                               writeindex(WWW_INDEX_PATH,1,0,0);
+                                                               $intf_state = INTF_STATE->{'X'};
+                                                               $intf_mode  = INTF_STATE->{'X'};
+                                                               $intf_pause = 0;
+                                                               $story{'pass'} = 1;
+                                                               $story{'state'} = INTF_STATE->{'X'};
+                                                               writeindex(WWW_INDEX_PATH,1,0,0); # to replace
                                                        }
                                                }
                                        }
-                                       writedatafile($story_file,%story);
+                                       write_data_file($story_file, '', '', \%story);
                                }
                        }
                        else {
@@ -316,22 +344,20 @@ if (open ($story_file,"+<:encoding(UTF-8)",DATA_STORY_PATH())){
                        }
                }
        }
-       elsif (($cgi{'s'} ne '') && ($intf_pass==1) && ($ong_state == 0)) {
-               $intf_state = int($cgi{'s'});
-               if($intf_state > 63 || $intf_state <0) {
-                       $intf_state = 0;
-               }
-               $intf_mode = $intf_state;
-               $intf_pause = $intf_state & 1;
-               if ($intf_pause) {
-                       $intf_mode -= 1;
-               }
+       elsif (
+               ($cgi{'s'} ne '') &&
+               ($intf_pass == 1) &&
+               ($ong_state == STATE->{'inactive'})
+       ) {
+               $intf_state = int($cgi{'s'}) & INTF_STATE->{'mask'};
+               $intf_mode  = $intf_state & INTF_STATE->{'mode'};
+               $intf_pause = $intf_state & INTF_STATE->{'||'};
                $story{'state'} = $intf_state;
-               writeindex(WWW_INDEX_PATH,1,$intf_mode,$intf_pause);
-               writedatafile($story_file,%story);
+               writeindex(WWW_INDEX_PATH,1,$intf_mode,$intf_pause); # TO REPLACE
+               write_data_file($story_file, '', '' \%story);
        }
-       @storylines = split(/\r?\n/,$story{'content'});
-       if(@storylines & 1) {
+       @story_lines = split(/\r?\n/, $story{'content'});
+       if(@story_lines & 1) {
                $turn = !$turn;
        }
        
@@ -343,121 +369,169 @@ if($method eq 'HEAD') {
        exit;
 }
 
-print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
-print '<html lang="en"><head>'."\n";
-print '<title>Two words &bull; '.WEBSITE_NAME.'</title>'."\n";
-print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
-print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
-print '<link rel="stylesheet" href="'.CGI_CSS_PATH.'">'."\n";
-print '</head><body>'."\n";
-print '<a href="/"><img id="botmlogo" src="'.CGI_LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
-print '<div id="all">'."\n";
+my $max_page = int(($story_id - FIRSTPAGE_LENGTH - 1) / PAGE_LENGTH) + 1;
+my $newer_available = ($page > 0);
+my $older_available = ($page < $max_page);
+my $show_intf = ($intf_pass == 1) && ($ong_state == STATE=>{'inactive'});
+
+my $twowords_url = CGI_2WORDS_PATH;
+my $newer_url;
+my $older_url;
+my $oldest_url;
+my $newerst_url = merge_url(
+       {'path' => $twowords_url},
+       {'path' => 0}
+);
+if ($newer_available) {
+       $newer_url = merge_url(
+               {'path' => $twowords_url},
+               {'path' => $page-1}
+       );
+}
+if ($older_available) {
+       $older_url = merge_url(
+               {'path' => $twowords_url},
+               {'path' => $page+1}
+       );
+       $oldest_url = merge_url(
+               {'path' => $twowords_url},
+               {'path' => $max_page}
+       );
+}
+my $buton_5_url = merge_url(
+       {'path' => CGI_PATH},
+       {'path' => 
+       
+
+my $_twowords_url = html_entity_encode_dec($twowords_url , 1);
+my $_newest_url   = html_entity_encode_dec($newest_url   , 1);
+my $_newer_url    = html_entity_encode_dec($newer_url    , 1);
+my $_older_url    = html_entity_encode_dec($older_url    , 1);
+my $_oldest_url   = html_entity_encode_dec($oldest_url   , 1);
+my $_message      = html_entity_encode_dec($message      , 1);
+my $_website_name = html_entity_encode_dec(WEBSITE_NAME(), 1);
 
-print '<div id="inst" class="ins">'."\n";
+print_html_start(\*STDOUT);
+print_html_head_start(\*STDOUT);
+
+
+print '  <title>Two words &bull; '.$_website_name.'</title>'."\n";
+print '  <link rel="start" href="'.$_oldest_url.'">'."\n";
+if ($older_available) {
+       print '  <link rel="prev" href="'.$_older_url.'">'."\n";
+}
+if ($newer_available) {
+       print '  <link rel="next" href="'.$_newer_url.'">'."\n";
+}
 
-print '<div id="title">'."\n";
-print '<H1 id="titletext">Two words</H1>'."\n";
-print '</div>'."\n";
+print_html_head_end($fh);
+print_html_body_start($fh);
+
+print '   <div id="inst" class="ins">'."\n";
+
+print '    <div id="title">'."\n";
+print '     <h1 id="titletext">Two words</h1>'."\n";
+print '    </div>'."\n";
 
 if ($page == 0) {
-       print '<div id="storypuzzle">'."\n";
-       for (my $i = 0; $i < @storylines; ++$i){
-               print '<span class="'.($turn?'ni':'br').'">'.entityencode($storylines[$i]).'</span>'."\n";
+       print '    <div id="storypuzzle">'."\n";
+       for (my $i = 0; $i < @story_lines; ++$i) {
+               print '     <span class="'.($turn ? 'ni':'br').'">'.html_entity_encode_dec($story_lines[$i], 1).'</span>'."\n";
                $turn = !$turn;
        }
-       print '</div>'."\n";
+       print '    </div>'."\n";
 
-       print '<div id="command">'."\n";
+       print '    <div id="command">'."\n";
        if ($message ne '') {
-               print '<span class="br">'.entityencode($message).'</span>'."\n";
+               print '     <span class="br">'.$_message.'</span>'."\n";
        }
        
        if ($turn) {
-               print '<form method="post" action="'.CGI_2WORDS_PATH.'">'."\n";
+               print '     <form method="post" action="'.$_twowords_url.'">'."\n";
                if ($message eq '') {
                        if ($story{"content"} eq '') {
-                               print 'Two words, please:<br>'."\n";
+                               print '      Two words, please:<br>'."\n";
                        }
                        else {
-                               print 'Please continue, two words:<br>'."\n";
+                               print '      Please continue, two words:<br>'."\n";
                        }
                }
-               print '<input class="intx" type="text" name="words">'."\n";
-               print '<input class="inbt" type="submit" value="enter">'."\n";
-               if(@storylines >= (STORY_LENGTH-1)) {
-                       print '<input class="inbt" type="submit" name="next" value="enter and then start a new one">'."\n";
+               print '      <input class="intx" type="text" name="words">'."\n";
+               print '      <input class="inbt" type="submit" value="enter">'."\n";
+               if(@story_lines >= (STORY_LENGTH-1)) {
+                       print '      <input class="inbt" type="submit" name="next" value="enter and then start a new one">'."\n";
                }
-               print '</form>'."\n";
+               print '     </form>'."\n";
        }
        else {
                if ($message eq '') {
-                       print 'Wait for it.'."\n";
+                       print '     Wait for it.'."\n";
                }
        }
-       print '</div>'."\n";
+       print '    </div>'."\n";
 }
 elsif ($message ne '') {
-       print '<div id="command">'."\n";
-       print '<span class="br">'.entityencode($message).'</span>'."\n";
-       print '</div>'."\n";
+       print '    <div id="command">'."\n";
+       print '     <span class="br">'.$_message.'</span>'."\n";
+       print '    </div>'."\n";
 }
+print '   </div>'."\n";
 
-if(($intf_pass == 1) && ($ong_state == 0)) {
-       print '</div><div id="framespace">'."\n";
-       print '<table id="intftable" cellspacing="0" cellpadding="0"><tr class="intf">'."\n";
+if ($show_intf) {
+       print '   <div id="framespace">'."\n";
+       print '    <table id="intftable" cellspacing="0" cellpadding="0">'."\n";
+       print '     <tr class="intf">'."\n";
+       print '      <td colspan="6" class="intf"><img src="'.$_intf_img.'.gif" alt="" class="intf"></td>'."\n";
+       print '     </tr>'."\n";
        
-       print '<td colspan="6" class="intf"><img src="'.CGI_PATH.'intf-00';
-       if ($intf_mode == 4) {
-               print '_04';
-       }
-       elsif ($intf_mode == 8) {
-               print '_08';
-       }
-       elsif ($intf_state == 16) {
-               print '_10';
-       }       
-       print'.gif" alt="" class="intf"></td>'."\n";
-       
-       print '</tr><tr class="intf">'."\n";
-       print '<td class="intf"><img src="'.CGI_PATH.'intf-20.gif" alt="o" class="intf"></td>'."\n";
-       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?17:16).'"><img src="'.CGI_PATH.'intf-10'.(($intf_mode==16)?'_':'').'.gif" class="intf" alt="&gt"></td>'."\n";
-       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?9:8).'"><img src="'.CGI_PATH.'intf-08'.(($intf_mode==8)?'_':'').'.gif" class="intf" alt="&lt;&lt;"></td>'."\n";
-       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?5:4).'"><img src="'.CGI_PATH.'intf-04'.(($intf_mode==4)?'_':'').'.gif" class="intf" alt="&gt;&gt;"></td>'."\n";
-       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?0:0).'"><img src="'.CGI_PATH.'intf-02.gif" class="intf" alt="^"></td>'."\n";
-       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?$intf_mode:$intf_mode+1).'"><img src="'.CGI_PATH.'intf-01'.($intf_pause?'_':'').'.gif" class="intf" alt="||"></td>'."\n";
-       print '</tr></table>'."\n";
+       print '     <tr class="intf">'."\n";
+       print '      <td class="intf"><img src="'.$_button_5_img.'" alt="o" class="intf"></td>'."\n";
+       print '      <td class="intf"><a href="'.$_button_4_url.'"><img src="'.$_button_4_img.'" class="intf" alt="&gt;"></a></td>'."\n";
+       print '      <td class="intf"><a href="'.$_button_3_url.'"><img src="'.$_button_3_img.'" class="intf" alt="&lt;&lt;"></a></td>'."\n";
+       print '      <td class="intf"><a href="'.$_button_2_url.'"><img src="'.$_button_2_img.'" class="intf" alt="&gt;&gt;"></a></td>'."\n";
+       print '      <td class="intf"><a href="'.$_button_1_url.'"><img src="'.$_button_1_img.'" class="intf" alt="^"></a></td>'."\n";
+       print '      <td class="intf"><a href="'.$_button_0_url.'"><img src="'.$_button_0_img.'" class="intf" alt="||"></a></td>'."\n";
+       print '     </tr>'."\n";
+       print '    </table>'."\n";
+       print '   </div>'."\n";
 }
 
-print '</div><div id="insb" class="ins">'."\n";
-print '<div id="undertext">'."\n";
-for (my $i = $story_id-1-(($page!=0)?((($page-1)*PAGE_LENGTH)+FIRSTPAGE_LENGTH):0); $i > ($story_id-1-($page*PAGE_LENGTH)- FIRSTPAGE_LENGTH) && $i >= 0; --$i) {
-       $story_path = DATA_STORY_PATH.$i;
-       %new_story = readdatafile($story_path);
-       print '<p class="'.(($i&1)?'br':'ni').'" id="s'.$i.'">'.entityencode($new_story{'content'}).'</p>'."\n";
+print '   <div id="insb" class="ins">'."\n";
+
+print '    <div id="undertext">'."\n";
+for (my $i = $id_start; $i > $id_stop; --$i) {
+       $story_i_path = DATA_STORY_PATH.$i;
+       %new_story = read_data_file($story_i_path);
+       print '     <p class="'.(($i&1)?'br':'ni').'" id="s'.$i.'">'.html_entity_encode_dec($new_story{'content'}).'</p>'."\n";
 }
+print '    </div>'."\n";
 
-print '</div>'."\n";
-print '<div id="underlinks">'."\n";
-print '<a href="'.CGI_PATH.'">BSTA</a> | <a href="'.CGI_2WORDS_PATH.'">Once again</a>';
-if(($story_id - ($page*PAGE_LENGTH)) - FIRSTPAGE_LENGTH > 0) {
-       print ' | <a href="'.CGI_2WORDS_PATH.'/'.($page+1).'">Before</a>';
+print '    <div id="underlinks">'."\n";
+print '     <a href="'.$_bsta_url.'">BSTA</a> |'."\n";
+print '     <a href="'.$_twowords_url.'">Once again</a>';
+if ($older_available) {
+       print ' |'."\n";
+       print '     <a href="'.$_older_url.'">Before</a>';
 }
-if($page > 0) {
-       print ' | <a href="'.CGI_2WORDS_PATH.'/'.($page-1).'">Unbefore</a>';
+if ($newer_available) {
+       print ' |'."\n";
+       print '     <a href="'.$newer_url.'">Unbefore</a>';
 }
-if(($story_id - ($page*PAGE_LENGTH)) - FIRSTPAGE_LENGTH > 0) {
-       print ' | <a href="'.CGI_2WORDS_PATH.'/'.(int(($story_id - FIRSTPAGE_LENGTH - 1) / PAGE_LENGTH) + 1).'">Initially</a>';
+if ($older_available) {
+       print ' |'."\n";
+       print '<a href="'.$_oldest_url.'">Initially</a>';
 }
 if($turn) {
-       print ' | (Entering words here is irreversible. Your actions might be remembered forever. So please be reasonable.)';
+       print ' |'."\n";
+       print '     (Entering words here is irreversible. Your actions might be remembered forever. So please be reasonable.)';
 }
 print "\n";
-print '</div>'."\n";
+print '    </div>'."\n";
+
+print '   </div>'."\n";
 
-print '</div>'."\n";
-print '</div>'."\n";
-print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
-print '</body></html>'."\n";
+print_html_body_end(\*STDOUT);
+print_html_end(\*STDOUT);
 
 sub writeindex {
        (my $indexpath, my $pass, my $mode, my $pause) = @_;
index 14550b60ad80e90c5ce5d6d6bad7408d2cb3e3cb..50eea1a49e6369201628d4a3897d53028b4cc0f4 100644 (file)
@@ -83,6 +83,7 @@ use botm_common (
 
 ###PERL_SCHEME:             SCHEME             = http
 ###PERL_WEBSITE:            WEBSITE            = 1190.bicyclesonthemoon.info
+###PERL_WEBSITE_NAME:       WEBSITE_NAME       = Bicycles on the Moon
 ###PERL_FAVICON_PATH:       FAVICON_PATH       = /img/favicon.png
 
 
@@ -101,7 +102,9 @@ use constant INTF_STATE => {
        '<<' => 0b001000,
        '|<<'=> 0b001001,
        '>'  => 0b010000,
-       '>|' => 0b010001
+       '>|' => 0b010001,
+       'mask'=>0b111111,
+       'mode'=>0b111110,
 };
 use constant TEXT_MODE => {
        'normal' => 0,
@@ -842,6 +845,7 @@ sub print_viewer_page {
        # my $prev_frame = $frame - 1;
        my $next_frame = $frame + 1;
        
+       my $story = $settings->{'story'};
        my $title = $frame_data->{'title'};
        my $command = ($frame_data->{'command'} ne '') ?
                $frame_data->{'command'} :
@@ -1041,9 +1045,12 @@ sub print_viewer_page {
        my $_frame_next_url  = html_entity_encode_dec($frame_next_url , 1);
        my $_frame_full_url  = html_entity_encode_dec($frame_full_url , 1);
        
+       my $_story   = html_entity_encode_dec($story,   1);
        my $_title   = html_entity_encode_dec($title,   1);
        my $_command = html_entity_encode_dec($command, 1);
        
+       my $_website_name = html_entity_encode_dec(WEBSITE_NAME(), 1);
+       
        if ($text_mode == TEXT_MODE->{'info'}) {
                if ($show_command) {
                        $frame_data->{'command'} = $command;
@@ -1070,6 +1077,11 @@ sub print_viewer_page {
        print_html_start($fh);
        print_html_head_start($fh);
        
+       print $fh '  <title>'.$_title;
+       if ($story ne $title) {
+               print $fh ' &bull; '.$_story;
+       }
+       print $fh ' &bull; '.$_website_name.'</title>'."\n";
        print $fh '  <link rel="index" href="'.$_goto_url.'">'."\n";
        print $fh '  <link rel="start" href="'.$_viewer_0_url.'">'."\n";
        if ($prev_available) {