summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorꦌꦫꦶꦏ꧀ꦦꦿꦧꦮꦑꦩꦭ꧀ <erik@darapsa.co.id>2021-07-04 18:52:14 +0800
committerꦌꦫꦶꦏ꧀ꦦꦿꦧꦮꦑꦩꦭ꧀ <erik@darapsa.co.id>2021-07-04 18:52:14 +0800
commita79348ff0a8af6d59696ee31fd7a293707989a84 (patch)
treef91c37373f1bb7474207055d600e4d2424ddbd8d
Separated web code
-rw-r--r--.gitignore3
-rw-r--r--catalog.cfg750
-rw-r--r--html/css/product.css74
-rw-r--r--namatoko.scss6
-rw-r--r--pages/flypage.html77
-rw-r--r--pages/ord/basket.html158
-rw-r--r--pages/ord/checkout.html138
-rw-r--r--pages/results.html92
-rw-r--r--templates/layout/noleft52
-rw-r--r--variables/BOTTOM16
10 files changed, 1366 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0bea6b6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*~
+*.min.css
+*.swp
diff --git a/catalog.cfg b/catalog.cfg
new file mode 100644
index 0000000..45ad3be
--- /dev/null
+++ b/catalog.cfg
@@ -0,0 +1,750 @@
+#==========================================================================#
+
+# catalog.cfg
+
+#==========================================================================#
+
+## We want to hide the .gdbm, .autonumber, .numeric, .sql, .db files
+## Needs to go before VariableDatabase
+DatabaseDefault HIDE_AUTO_FILES 1
+
+# Read in some initial default variables from a directory. These are
+# larger ones -- they could conceivably be maintained in the
+# VariableDatabase but might be easier to maintain in a file, especially
+# for development.
+#
+# Notable settings: COMMON_MENU, COPYRIGHT
+#
+DirConfig Variable variables
+
+# Read in large variables or dynamic variables from a series
+# of files in a directory
+VariableDatabase variable
+
+# Read in site-specific variables from a database (if any).
+# Used to create a information for a temporarily transferred
+# catalog, i.e. for development.
+#
+# Ignored if file doesn't exist.
+VariableDatabase site
+
+# Warn if any important modules or usertags are missing.
+
+Require module Digest::MD5 "Need %s %s for better cache keys."
+Require module Safe::Hole "Need %s %s for embedded perl object access."
+
+# Encoding
+Variable MV_UTF8 1
+Variable MV_HTTP_CHARSET utf-8
+
+#==========================================================================#
+
+# Can send various files outside the catalog directory, for disk partition
+# reasons, for example.
+
+ParseVariables Yes
+
+ifdef LOGDIR
+ErrorFile __LOGDIR__/error.log
+AsciiTrack __LOGDIR__/tracking.asc
+#TrackFile __LOGDIR__/usertrack
+endif
+
+ifndef LOGDIR
+AsciiTrack logs/tracking.asc
+#TrackFile logs/usertrack
+endif
+
+ifdef CACHEDIR
+SessionDatabase __CACHEDIR__/session
+ScratchDir __CACHEDIR__/tmp
+endif
+
+ifndef CACHEDIR
+SessionDatabase session
+ScratchDir tmp
+endif
+
+ifdef RUNDIR
+RunDir __RUNDIR__
+endif
+
+#==========================================================================#
+
+# Database setup
+
+ParseVariables Yes
+
+ifndef INDEV
+DatabaseDefault LOG_ERROR_SESSION 0
+endif
+
+## Quote all identifiers to avoid name clash issues and SQL injection attacks
+DatabaseDefault QUOTE_IDENTIFIERS 1
+
+ifdef MYSQL
+DatabaseDefault NO_ASCII_INDEX 1
+include dbconf/mysql/mysql.cfg
+endif
+
+ifdef PGSQL
+DatabaseDefault NO_ASCII_INDEX 1
+include dbconf/pgsql/pgsql.cfg
+endif
+
+ifdef SQLITE
+DatabaseDefault NO_ASCII_INDEX 1
+include dbconf/sqlite/sqlite.cfg
+endif
+
+# Default DBM if nothing else defined
+ifndef SOME_DATABASE
+Require We_need_a_database "%s, must have either MySQL or Postgres"
+endif
+
+### Uncomment these for DBI sessions
+# SessionType DBI
+# SessionDB sessions
+# Database sessions sessions.txt __SQLDSN__
+# NoImport sessions
+
+### You can change the length of the session ID. Must be integer between
+### 8 and 32.
+# Limit session_id_length 8
+
+#==========================================================================#
+
+# The URLs which are written to refer back to our catalog.
+
+<ParseVariables Yes>
+
+VendURL http://__SERVER_NAME____CGI_URL__
+SecureURL __SECURE_SERVER____CGI_URL__
+
+ifndef SECURE_ENABLE
+SecureURL http://__SERVER_NAME____CGI_URL__
+endif
+
+# Set the image path for relative images
+ImageDir __IMAGE_DIR__/
+ImageDirInternal http://__SERVER_NAME____IMAGE_DIR__/
+
+</ParseVariables>
+
+#==========================================================================#
+
+# Here we set up the catalog theme.
+
+<ParseVariables Yes>
+
+##
+## Here we pick up the region variables like LEFTONLY_TOP and LEFTONLY_BOTTOM.
+##
+## Commenting; new variables TOP and BOTTOM will be defined in variable directory.
+## No longer going to need multiple leftright_top, leftonly_top, etc; will be
+## defined in "display_class" in each page.
+
+#DirConfig Variable templates/regions
+
+# TRAFFIC is defined system-wide in interchange.cfg. When in low-traffic
+# mode, variables are dynamic -- i.e. when you change the region file
+# the page changes. Otherwise a reconfig must be done to change the
+# variable.
+#
+# Careful, this can change the way *other* variables are interpreted.
+ifdef @TRAFFIC =~ /low/i
+Pragma dynamic_variables
+Pragma dynamic_variables_file_only
+endif
+
+# Provide some links for the admin to edit pages if we have the UI in
+ifdef @UI
+Autoload admin_links
+endif
+
+#==========================================================================#
+
+# Internationalization
+LocaleDatabase locale
+
+# Default locale
+ifdef DEFAULT_LOCALE
+DefaultLocale __DEFAULT_LOCALE__
+endif
+
+ifdef DEFAULT_LOCALE
+# Database definitions for localized databases
+include dbconf/locales/{__LOCALES__}.cfg
+endif
+
+Locale en_US currency_symbol Rp
+Locale en_US mon_decimal_point ""
+Locale en_US frac_digits 0
+
+Locale id_ID currency_symbol Rp
+Locale id_ID p_cs_precedes 1
+Locale id_ID mon_thousands_sep .
+Locale id_ID mon_decimal_point ""
+Locale id_ID frac_digits 0
+
+#==========================================================================#
+
+# Various catalog settings.
+
+ScratchDefault mv_add_dot_html 0
+ScratchDefault mv_no_session_id 1
+ScratchDefault mv_no_count 1
+
+ifdef DEFAULT_COUNTRY
+ValuesDefault country __DEFAULT_COUNTRY__
+ValuesDefault b_country __DEFAULT_COUNTRY__
+endif
+
+ValuesDefault fname Pengguna lname Baru
+
+DirectoryIndex index.html
+SpecialPage catalog index
+SpecialPage violation ../special_pages/violation
+SpecialPage put_handler admin_publish
+SpecialPage report ../etc/report
+SpecialPage receipt ../etc/receipt
+
+# Allow others in our group to read/write files by default
+ReadPermission group
+WritePermission group
+
+# Don't send X-Track HTTP response header
+UserTrack no
+
+# If a specific user session accesses this catalog more than this many
+# times with no pauses of 30 seconds or more, the LockoutCommand (if set)
+# is executed. Set this to 0 if you're getting links to 127.0.0.1
+# during your testing.
+RobotLimit 100
+
+# We also count the number of accesses from the same IP address. More than
+# RobotLimit accesses within a one hour period will cause a Forbidden result.
+# Uncomment the following line to change the 1 hour period to 30 minutes.
+# Limit ip_session_expire 30
+
+# The default lockout time is one day. Uncomment the following line to change
+# it to just under an hour.
+# Limit robot_expire 0.04
+
+# Strip whitespace from top of pages so you don't have to scroll down
+# before the HTML starts when doing 'view source' in a browser.
+Pragma strip_white
+
+# Don't allow <!--[itl tag]-->
+Pragma no_html_comment_embed
+
+## DATA INSERTION PRAGMAS for form database inserts or updates
+
+## Restricts inserts to insert-only, but allows the fall-through
+## behavior from update to insert. No existing data will be clobbered.
+# Pragma dml=preserve
+
+## Pragma 'dml=strict' forces update or insert to only perform the
+## requested action.
+# Pragma dml=strict
+
+## Pragma 'dml=upsert' (the default) preserves Interchange's original
+## method of doing an update if the record exists, otherwise doing an
+## insert. This can clobber an existing key.
+# Pragma dml=upsert
+
+## END DATA INSERTION PRAGMAS
+
+#==========================================================================#
+
+# User session related settings.
+
+# Whether to encrypt passwords in UserDB
+UserDB default crypt 1
+UserDB default bcrypt 1
+
+# These 2 lines are needed for query/pw_reset
+UserDB default promote 1
+UserDB default from_plain 1
+
+# The pepper should be unique for your site, but note that if you change this,
+# it will make previously-crypted passwords inaccessible --
+# so set this before you start adding users, or don't set at all.
+UserDB default bcrypt_pepper __BCRYPT_PEPPER__
+
+# Set to 1 to make the username and password case-insensitive
+UserDB default ignore_case 1
+
+# Change a field to something that doesn't conflict in MySQL
+UserDB default time_field mod_time
+
+# Don't want people setting their credit limit or dealer status directly
+UserDB default scratch "dealer price_level credit_limit usernick"
+UserDB default expire_field expiration
+
+# Update the database with the date when creating a user
+UserDB default created_date_iso created
+
+# Update the database with the date when updating a user via set_values
+UserDB default updated_date_iso updated
+
+# Set some other things you want to retain but which don't deserve their
+# own field -- stored in serial field preferences
+UserDB default extra_fields <<EOU
+"
+ mv_same_billing
+ mv_credit_card_exp_type
+ mv_credit_card_exp_month
+ mv_credit_card_exp_year
+ fax_order
+ phone_cell
+ account_id
+ po_number
+"
+EOU
+
+# Log user login/logout etc in separate logfile
+UserDB default logfile logs/userdb.log
+
+# This makes the login username the user's email address
+# Uncomment to use
+UserDB default indirect_login usernick
+UserDB default assign_username 1
+
+# for indirect login to work with admin
+UserDB autocreate crypt 0
+UserDB autocreate time_field mod_time
+UserDB autocreate scratch "dealer price_level credit_limit"
+
+# This makes the password be inserted in an insert-only table.
+#
+# UserDB default enclair_db enclair
+#
+# You can set the following, which have the defaults shown in the
+# setting. You can also insert %M, which is the MD5 of the password, or
+# %D which is a datetime localtime value in the form YYYYmmddHHMMSS.
+#UserDB default enclair_key_field username
+#UserDB default enclair_field password
+#UserDB default enclair_query_template "INSERT INTO %t (%U,%P) values (%u,%p)"
+
+# minimal login stuff for affiliate
+UserDB affiliate user_field affiliate
+UserDB affiliate database affiliate
+UserDB affiliate time_field none
+UserDB affiliate crypt 0
+
+# Set this to Yes if you want auto-login capability for users.
+# You must be careful about malicious JavaScripts in your embedded code.
+## this is not secure at all, since it saves user's password.
+## can use alternate cookie to just save username.
+CookieLogin No
+
+# Limit the time your customers can retain a session
+SessionExpire 4 hours
+
+# Limit the time your customers can CookieLogin
+SaveExpire 30 days
+
+#==========================================================================#
+
+# Cart, order, and route settings.
+
+ParseVariables Yes
+
+MailOrderTo __ORDERS_TO__
+
+AlwaysSecure <<EOD
+ *
+EOD
+
+## Set this if you have a different secure server
+#AlwaysSecure order ord/basket process
+
+ifndef COMMON_ORDER_PROFILE
+Variable COMMON_ORDER_PROFILE <<EOV
+ [calc]
+ if ($CGI->{state_cs_in} && !$CGI->{state}){
+ $CGI->{state} = $CGI->{state_cs_in};
+ }
+ return;
+ [/calc]
+ mv_same_billing=always_pass
+ fname=required
+ lname=required
+ address1=required
+ address2=always_pass
+ city=required
+ country=required
+ state=multistate
+ zip=multizip
+ phone_day=phone
+ [if !session logged_in]email=email_only[/if]
+EOV
+endif
+
+## Payment-related stuff
+
+EncryptKey __PGP_KEY__
+
+## These routes are not order routes, but payment routes
+Route authorizenet id "__MV_PAYMENT_ID__"
+Route authorizenet secret "__MV_PAYMENT_SECRET__"
+Route authorizenet host "__MV_PAYMENT_HOST__"
+Route authorizenet referer "__MV_PAYMENT_REFERER__"
+
+Route itransact id "__MV_PAYMENT_ID__"
+
+Route netbilling id "__MV_PAYMENT_ID__"
+Route netbilling secret "__MV_PAYMENT_SECRET__"
+
+Route signio id "__MV_PAYMENT_ID__"
+Route signio secret "__MV_PAYMENT_SECRET__"
+Route signio partner "__MV_PAYMENT_PARTNER__"
+Route signio vendor "__MV_PAYMENT_VENDOR__"
+Route signio host "__MV_PAYMENT_HOST__"
+
+Route skipjack id "__MV_PAYMENT_ID__"
+Route skipjack partner "__MV_PAYMENT_PARTNER__"
+
+Route linkpoint id "__MV_PAYMENT_ID__"
+Route linkpoint host "__MV_PAYMENT_HOST__"
+Route linkpoint keyfile "__MV_PAYMENT_SECRET__"
+
+# This prevents a user from setting this value, you may want to unset
+# this if you have user-selectable handling charges like insurance
+FormIgnore mv_handling
+#
+#
+
+# Along these lines further, for better integrity and less chance of a
+# user screwing up your order routes:
+# Note that __ORDER_ROUTES__ is empty by default, default Route "default"
+# is used with cascades
+FormIgnore mv_order_route
+
+## This route places the order entry in the database. If you don't
+## have an inventory table (or a userdb table for that matter) make
+## sure you remove it from the list of "transactions" tables.
+
+Route log <<EOF
+ empty 1
+ encrypt 0
+ increment 0
+ report etc/log_transaction
+ supplant 0
+ track logs/log
+EOF
+
+ifdef TRANSACTION_TABLES
+Route log transactions '__TRANSACTION_TABLES__'
+endif
+
+## This route copies the user if they requested that. We don't
+## care (much) if it fails, so error_ok is set and failure will
+## not cause the order to fail
+Route copy_user <<EOF
+ empty 1
+ error_ok 1
+ encrypt 0
+ increment 0
+ report etc/mail_receipt
+ supplant 0
+ track logs/log
+EOF
+
+ParseVariables Yes
+## This route emails the order to you unless email is set to "",
+## and failsafe-logs the order report a couple of places
+Route main <<EOF
+ attach 0
+ credit_card 1
+ default 1
+ email '__ORDERS_TO__'
+ encrypt 0
+ errors_to '__ORDERS_TO__'
+ pgp_cc_key "__PGP_KEY__"
+ pgp_key "__PGP_KEY__"
+ receipt etc/receipt.html
+ report etc/report
+ supplant 1
+ individual_track orders
+ track logs/tracking.asc
+EOF
+
+## This route emails the order to you unless email is set to "",
+## and failsafe-logs the order report a couple of places
+Route main_entry <<EOF
+ attach 0
+ credit_card 1
+ email '__ORDERS_TO__'
+ encrypt 0
+ errors_to '__ORDERS_TO__'
+ pgp_cc_key "__PGP_KEY__"
+ pgp_key "__PGP_KEY__"
+ report etc/report
+ supplant 1
+ individual_track orders
+ track logs/tracking.asc
+EOF
+
+
+# Order desk route run if entered from admin
+# always
+Route entry master 1
+Route entry cascade "log main_entry copy_user"
+Route entry empty 1
+Route entry supplant 1
+Route entry no_receipt 1
+Route entry counter_tid logs/tid.counter
+Route entry write_tables "inventory userdb transactions orderline"
+Route entry transactions '__TRANSACTION_TABLES__'
+Route entry email '__ORDERS_TO__'
+
+# Default route is run if no routes set, this should be last Route
+# always
+Route default master 1
+Route default cascade "log main copy_user"
+Route default empty 1
+Route default supplant 1
+Route default counter_tid logs/tid.counter
+Route default write_tables "inventory userdb transactions orderline"
+Route default transactions '__TRANSACTION_TABLES__'
+Route default email '__ORDERS_TO__'
+
+## Uncomment this if you want Routes read dynamically from DB
+#Route default dynamic_routes 1
+## Uncomment this if you want ITL allowed in routes
+#Route default expandable 1
+
+
+## Sest
+SalesTax __TAXFIELD__
+TaxShipping __TAXSHIPPING__
+
+OrderCounter etc/order.number
+OrderLineLimit 200
+Profiles include/profiles/*.*
+
+## Sets the default as to whether items are aggregated or ordered
+## on separate lines. Can be overridden with mv_separate_items=0 in
+## URL or form.
+SeparateItems no
+
+#==========================================================================#
+
+ifdef UI_TRAFFIC_STATS
+TrackFile __UI_TRAFFIC_STATS__
+endif
+
+# Deal with customer click history. For example, after adding an item to
+# the cart, the user can return to a specific search results page.
+History 10
+
+# Allow delivery of soft goods (downloadable files).
+ActionMap deliver <<EOR
+sub {
+ $Scratch->{deliverable} = $CGI->{mv_arg};
+ $CGI->{mv_nextpage} = 'deliver';
+ if(! $Session->{username} and $CGI->{mv_username}) {
+ $Tag->userdb('login');
+ }
+ return 1;
+}
+EOR
+
+# Pricing setup
+#
+# If the user is logged in and is marked as a "dealer" (1 in the dealer
+# field in the userdb database) then they are given quantity discounts
+# based on price groups. (All products are in price group 1 as
+# distributed.) If the quantity is 1, then pricing comes from the
+# "wholesale" field in the products database.
+
+AutoModifier pricing:price_group
+
+#
+# If the user is not a dealer (or not logged in) then pricing just comes
+# from "price". Any quantity discounts will be set in the qN fields in the
+# database, and are separate from dealer quantity discounts.
+#
+
+Profile dealer <<EOR
+{
+ CommonAdjust => <<EOF,
+
+ pricing:w5,w10:,
+ ;:wholesale,
+ ;:wholesale:mv_sku,
+ ;$,
+ ==:options
+EOF
+ NonTaxableField => 'nontaxable',
+}
+EOR
+
+Profile distributor <<EOR
+{
+ CommonAdjust => <<EOF,
+
+ pricing:w5,w10:,
+ ;:wholesale,
+ ;:wholesale:mv_sku,
+ ;$,
+ ==:options,
+ -10%
+EOF
+ NonTaxableField => 'nontaxable',
+}
+EOR
+
+## This should match the default direct setting below this paragraph
+Profile default CommonAdjust "pricing:q5,q10 ;:sale_price, ;:price, ;$, :related, ==:options"
+Profile default NonTaxableField
+Profile default PriceField 0
+
+CommonAdjust pricing:q5,q10 ;:sale_price, ;:price, ;$, :related, ==:options
+PriceField 0
+
+## This sets the type of options pertaining to the product
+OptionsEnable option_type
+
+## Finally, set which tables products can be ordered from
+ProductFiles products variants
+
+#==========================================================================#
+
+## Tag which provides a CSS button-looking link
+UserTag b-link Order href form
+UserTag b-link HasEndTag
+UserTag b-link addAttr
+UserTag b-link Routine <<EOR
+sub {
+ my ($page, $form, $opt, $anchor) = @_;
+ # use vars qw/$Tag $Scratch/;
+ if($Scratch->{no_javascript}) {
+ $opt->{extra} ||= 'style="font-size: smaller"';
+ return $Tag->page($opt) . $anchor . '</a>';
+ }
+ my $class = $opt->{class} || 'btn';
+ my $url = $Tag->area($opt);
+ my $string = $Tag->filter('encode_entities', $anchor);
+ my @properties;
+ push @properties, "type=button";
+ push @properties, qq{value="$string"};
+ push @properties, qq{class="$class"};
+ for(qw/style id name/) {
+ next unless $opt->{$_};
+ push @properties, qq{$_="$opt->{$_}"};
+ }
+ push @properties, qq{onClick="window.location='$url'"};
+ push @properties, qq{onMouseOver="saveblink=window.status;window.status='$url'"};
+ push @properties, qq{onMouseOut="window.status=saveblink"};
+ if($opt->{title}) {
+ $opt->{title} = $Tag->filter('encode_entities', $opt->{title});
+ }
+ else {
+ $opt->{title} = $string;
+ }
+ push @properties, qq{title="$opt->{title}"};
+ push @properties, $opt->{extra} if $opt->{extra};
+
+ return qq{<input } . join(" ", @properties) . '>';
+}
+EOR
+
+### Shipping setup. See Interchange docs -- you will want to change
+### the origin postal code and default destination postal codes, for sure.
+Shipping Postal default_geo 45056
+Shipping QueryUPS default_geo 45056
+Shipping default dir products/ship
+
+#Database 2ndDayAir ship/2ndDayAir.csv CSV
+#Database 2ndDayAirAM ship/2ndDayAirAM.csv CSV
+#Database 3DaySelect ship/3DaySelect.csv CSV
+#Database Expedited ship/Expedited.csv CSV
+#Database Express ship/Express.csv CSV
+#Database Ground ship/Ground.csv CSV
+#Database NextDayAir ship/NextDayAir.csv CSV
+#Database NextDayAirSaver ship/NextDayAirSaver.csv CSV
+#Database Xarea ship/Xarea.csv CSV
+#Database Zone ship/Zone.csv CSV
+#Database usps ship/usps.txt TAB
+#Database Book ship/Book.txt TAB
+#Database Priority ship/Priority.txt TAB
+#Database air_pp ship/air_pp.txt TAB
+#Database surf_pp ship/surf_pp.txt TAB
+#Database ups_cache ship/ups_cache.txt __SQLDSN__
+#Database ups_cache AUTO_SEQUENCE ups_cache_seq
+#Database ups_cache DEFAULT_TYPE varchar(12)
+#Database ups_cache INDEX weight origin zip shipmode country
+
+include usertag/*.tag
+
+## Uncomment
+MaxQuantityField inventory:quantity
+
+## Builds a series of hidden variables given an IC-style form
+## specification, i.e.
+##
+## [hiddens]
+## mv_action=refresh
+## mv_nextpage=foo
+## [/hiddens]
+##
+UserTag hiddens Interpolate 1
+UserTag hiddens HasEndTag
+UserTag hiddens Routine <<EOR
+sub {
+ my $block = shift;
+ my @lines = grep /\S/, split /\n/, $block;
+
+ my @out;
+ for(@lines) {
+ s/\s+$//;
+ s/^\s+//;
+ my ($k,$v) = split /=/, $_, 2;
+ $v = $Tag->filter('encode_entities', $v);
+ push @out, qq{<input type=hidden name="$k" value="$v">};
+ }
+ return join "\n", @out;
+}
+EOR
+
+## When uncommented, delivers a redirect if we get a request for an image
+## DeliverImage Yes
+
+## Map a subroutine to happen if the page is not there
+SpecialSub missing ncheck_category
+
+UserTag edisplay Alias error auto=1 class="alert alert-danger list-unstyled"
+UserTag wdisplay Alias warnings auto=1 list_class="alert alert-success list-unstyled"
+UserTag ecgi Alias cgi keep=1 filter=encode_entities name=
+
+CodeDef string2uri Filter
+CodeDef string2uri Description Sanitize a string for use in a URL
+CodeDef string2uri Routine <<EOR
+sub {
+ my $val = shift;
+ $val =~ s|/|::|g;
+ $val =~ s|-|_|g;
+ $val =~ s|\s+|-|g;
+ return $val;
+}
+EOR
+
+CodeDef uri2string Filter
+CodeDef uri2string Description Revert sanitized URL back to a string
+CodeDef uri2string Routine <<EOR
+sub {
+ my $val = shift;
+ $val =~ s|-| |g;
+ $val =~ s|_|-|g;
+ $val =~ s|::|/|g;
+ return $val;
+}
+EOR
+
+SearchProfile include/profiles/searchprofiles
+
+Jobs log __LOGDIR__/jobs.log
+Jobs base_directory etc/jobs
diff --git a/html/css/product.css b/html/css/product.css
new file mode 100644
index 0000000..4655513
--- /dev/null
+++ b/html/css/product.css
@@ -0,0 +1,74 @@
+.container {
+ max-width: 640px;
+}
+
+/*
+ * Custom translucent site header
+ */
+
+.site-header {
+ background-color: rgba(0, 0, 0, .85);
+ -webkit-backdrop-filter: saturate(180%) blur(20px);
+ backdrop-filter: saturate(180%) blur(20px);
+}
+.site-header a {
+ color: #999;
+ transition: ease-in-out color .15s;
+}
+.site-header a:hover {
+ color: #fff;
+ text-decoration: none;
+}
+
+/*
+ * Dummy devices (replace them with your own or something else entirely!)
+ */
+
+.product-device {
+ position: absolute;
+ right: 10%;
+ bottom: -30%;
+ width: 300px;
+ height: 540px;
+ background-color: #333;
+ border-radius: 21px;
+ -webkit-transform: rotate(30deg);
+ transform: rotate(30deg);
+}
+
+.product-device::before {
+ position: absolute;
+ top: 10%;
+ right: 10px;
+ bottom: 10%;
+ left: 10px;
+ content: "";
+ background-color: rgba(255, 255, 255, .1);
+ border-radius: 5px;
+}
+
+.product-device-2 {
+ top: -25%;
+ right: auto;
+ bottom: 0;
+ left: 5%;
+ background-color: #e5e5e5;
+}
+
+
+/*
+ * Extra utilities
+ */
+
+.flex-equal > * {
+ -ms-flex: 1;
+ flex: 1;
+}
+@media (min-width: 640px) {
+ .flex-md-equal > * {
+ -ms-flex: 1;
+ flex: 1;
+ }
+}
+
+.overflow-hidden { overflow: hidden; } \ No newline at end of file
diff --git a/namatoko.scss b/namatoko.scss
new file mode 100644
index 0000000..701dc88
--- /dev/null
+++ b/namatoko.scss
@@ -0,0 +1,6 @@
+$thumbnail-border-width: 0;
+$light: #fffffff6;
+$grid-gutter-width: 0;
+$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Roboto Mono", monospace !default;
+
+@import "bootstrap/scss/bootstrap";
diff --git a/pages/flypage.html b/pages/flypage.html
new file mode 100644
index 0000000..094e5f3
--- /dev/null
+++ b/pages/flypage.html
@@ -0,0 +1,77 @@
+@_TOP_@
+ <div class="container pt-5">
+ <div class="container p-3">
+ <img class="img-fluid" src="__IMAGE_DIR__/items/[item-field image]" alt="[item-description]">
+ </div>
+ <!-- Product heading -->
+ <div class="container pl-3 pr-3">
+ <h4>[item-description]</h4>
+ <h5 class="text-primary">[item-price]</h5>
+ <p class="text-muted text-monospace">[item-field prod_group] • [item-field category]</p>
+ </div>[fly-list code="[data session arg]"]
+ <form class="col" action="[area href="[if-item-field option_type][item-code][else]ord/checkout[/else][/if-item-field]"]" method="POST">
+ <input type="hidden" name="mv_action" value="refresh">
+ <input type="hidden" name="mv_order_item" value="[item-code]">
+ <input type="hidden" name="mv_order_quantity" value="1">
+ [form-session-id][edisplay show_var=0][if-item-field option_type][seti check_opt]
+ &success=ord/checkout
+ [query
+ list=1
+ sql="select o_group,o_label from options where sku='[item-code]'"
+ ]mv_order_[sql-code]=mandatory [L]Need to select[/L] [sql-param o_label]
+ [/query][/seti]
+ <input type="hidden" name="mv_form_profile" value="check_opt">
+ <!-- Product form control -->
+ <div class=" container pl-3 pr-3">
+ <div class="row">[perl options]
+my $elements = '';
+for my $option (@{$Tag->query({sql
+ => "SELECT o_group,o_label,o_value,o_widget FROM options WHERE sku='[item-code]' ORDER BY o_sort"})}) {
+ my $o_label = $Tag->loc('', $option->[1]);
+ $elements .= qq(
+ <div class="form-group pr-2">
+ <label for="mv_order_$option->[0]">$o_label</label>
+ <select class="form-control" id="mv_order_$option->[0]" name="mv_order_$option->[0]">);
+ my @csvs = split(/,/, $option->[2]);
+ for my $csv (@csvs) {
+ my $variant = '[item-code]';
+ my @pair = split(/=/, $csv);
+ my $label = $pair[1];
+ my $default = '';
+ if ($label =~ /\*/) {
+ $default = $Tag->loc('', substr($label, 0, length($label) - 1));
+ } else {
+ $label = $Tag->loc('', $label);
+ }
+ $elements .= qq(
+ <option value="$pair[0]")
+ . ($default ? ' selected' : '') . '>'
+ . ($default ? $default : $label)
+ . '</option>';
+ }
+ $elements .= qq(
+ </select>
+ </div>);
+ }
+ return $elements;
+ [/perl]
+ </div>
+ </div>[/if-item-field]
+ <!-- Call to action button -->
+ <div class="container p-3">
+ <div class="row">
+ <div class="col p-2">
+ <button class="btn btn-outline-primary btn-lg btn-block" href="[area ord/basket]" type="submit">+ Keranjang</button>
+ </div>
+ <div class="col p-2">
+ <button class="btn btn-primary btn-lg btn-block" href="[area ord/checkout]" type="submit">[L]Checkout[/L]</button>
+ </div>
+ </div>
+ </div>
+ </form>[/fly-list]
+ <!-- Product description -->
+ <div class="container p-3">
+ <p class="text-muted text-monospace">[L]Description[/L]</p>
+ <p>[item-field comment]</p>
+ </div>
+ </div>__BOTTOM__
diff --git a/pages/ord/basket.html b/pages/ord/basket.html
new file mode 100644
index 0000000..4778d6a
--- /dev/null
+++ b/pages/ord/basket.html
@@ -0,0 +1,158 @@
+[tmp page_title]__COMPANY__ -- [L]Shopping Cart[/L][/tmp]
+
+@_TOP_@
+<!-- BEGIN CONTENT -->
+<div class="container p-4">
+ <!-- Hero Section-->
+ <div class="py-5">
+ <div class="text-start">
+ <h1>[L]Shopping Cart[/L]</h1>
+ </div>
+ </div>
+ <!-- End of Hero section -->
+ <div class="container g-5">
+ <div class="container">
+ <div>
+ [edisplay]
+
+ [calc]
+ my $cname = $Config->{CookieName} || 'MV_SESSION_ID';
+ $Scratch->{have_cookie} = $Tag->read_cookie($cname)
+ and delete $Scratch->{tried};
+ return;
+ [/calc]
+ [if scratch have_cookie]
+ [elsif scratch tried]
+ [L CART_MSG1]You must have cookies set to leave the basket. Check out now or forever lose your shopping cart.[/L]
+ [/elsif]
+ [else]
+ [set tried]1[/set]
+ [bounce href="[area ord/basket]"]
+ [/else]
+ [/if]
+
+ <form action="[process secure=1]" method=POST name="basket" class="form-horizontal" id="form">
+ [form-session-id]
+ <input type="hidden" name="mv_doit" value="refresh">
+ <input type="hidden" name="mv_orderpage" value="ord/basket">
+ <input type="hidden" name="mv_nextpage" value="index">
+ <input type="hidden" name="mv_nlines" value="[nitems lines=1]">
+
+ <div class="container">
+ <div class="container font-weight-bold text-center p-4">
+ <div class="row">
+ <div class="col-md-5">[L]Description[/L]</div>
+ <div class="col-md-7 d-none d-md-block">
+ <div class="row">
+ <div class="col-md-3">[L]Price[/L]</div>
+ <div class="col-md-4">[L]Quantity[/L]</div>
+ <div class="col-md-3">[L]Total[/L]</div>
+ <div class="col-md-2"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div>
+
+ [if items]
+ [then]
+ [item-list]
+
+ [item-calc]
+ #Log("Checking master item $master");
+ $row_class = ++$count % 2 ? 'cartnorm' : 'cartalt';
+ my $item = '[item-increment]';
+ my $up = q{[item-data merchandising upsell_to]};
+ my $cr = q{[item-data merchandising cross_sell]};
+ $upsell_remove{'[item-code]'} = 1;
+ $cross_remove{'[item-code]'} = 1;
+ my %seen = ( '' => 1 );
+
+ $Scratch->{upsell} .= " $up" if $up;
+ $Scratch->{cross_codes} .= " $cr" if $cr;
+ my @up = split /\s+/, $Scratch->{upsell};
+ my @cr = split /\s+/, $Scratch->{cross_codes};
+ @up = grep ( (!$seen{$_}++ && ! $upsell_remove{$_}), @up);
+ @cr = grep ( (!$seen{$_}++ && ! $cross_remove{$_}), @cr);
+ $Scratch->{upsell} = join " ", @up;
+ $Scratch->{cross_codes} = join " ", @cr;
+ return;
+ [/item-calc]
+
+ <!-- Product-->
+ <div class="container pb-4">
+ <div class="card p-4">
+ <div class="row d-flex align-items-center text-left text-md-center">
+ <div class="col-12 col-md-5"><a class="cart-remove close mt-3 d-md-none" href="#"><i class="fa fa-times"></i></a>
+ <div class="d-flex align-items-center"><a href="[area href="[item-sku]"]">[image src="[item-field image]" imagesubdir=items makesize="80x80" extra='class="cart-item-img" alt=""']</a>
+ <div class="text-left"><a href="[area href="[item-sku]"]"><strong>[item-description]</strong></a><br><span class="text-muted text-sm">Size: Large</span><br><span class="text-muted text-sm">Colour: Green</span>
+ </div>
+ </div>
+ </div>
+ <div class="col-12 col-md-7 mt-4 mt-md-0">
+ <div class="row align-items-center">
+ <div class="col-md-3">
+ <div class="row">
+ <div class="col-6 d-md-none text-muted">[L]Price[/L]</div>
+ <div class="col-6 col-md-12 text-right text-md-center">[item-discount-price]</div>
+ </div>
+ </div>
+ <div class="col-md-4">
+ <div class="row align-items-center">
+ <div class="d-md-none col-7 col-sm-9 text-muted">[L]Quantity[/L]</div>
+ <div class="col-5 col-sm-3 col-md-12">
+ <div class="d-flex align-items-center">
+ <div class="btn btn-items btn-items-decrease" onclick="document.getElementById('[item-quantity-name]').value--; document.getElementById('form').submit()">-</div>
+ <input class="form-control text-center border-0 border-md input-items" type="text" value="[item-quantity]" name="[item-quantity-name]" id="[item-quantity-name]" onchange="this.form.submit()">
+ <div class="btn btn-items btn-items-increase" onclick="document.getElementById('[item-quantity-name]').value++; document.getElementById('form').submit()">+</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-3">
+ <div class="row">
+ <div class="col-6 d-md-none text-muted">[L]Total price[/L]</div>
+ <div class="col-6 col-md-12 text-right text-md-center">[item-discount-subtotal]</div>
+ </div>
+ </div>
+ <div class="col-2 d-none d-md-block text-center">
+ <a class="cart-remove" href="#" onclick="document.getElementById('[item-quantity-name]').value = 0; document.getElementById('form').submit()"><span class="material-icons-outlined">close</span>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ [/item-list]
+ [/then]
+ [/if]
+ <!-- End of product -->
+ </div>
+ </div>
+ <div class="card bg-light border-dark">
+ <div class="card-header ">
+ <h6 class="text-uppercase mb-0">[L]Order Summary[/L]</h6>
+ </div>
+ <div class="card-body">
+ <div class="card-text">
+ <span>[L]Subtotal[/L]</span><span class="float-right">[subtotal]</span>
+ </div>
+ </div>
+ <div class="card-footer">
+ <strong><span>Total</span><span class="float-right">[total-cost]</span></strong>
+ </div>
+ </div>
+ <div class="mt-4">
+ <div class="d-flex justify-content-between flex-column flex-lg-row">
+ <a class="btn btn-lg btn-link text-muted" href="[area index]">[L]Continue Shopping[/L]</a>
+ <a class="btn btn-lg btn-primary" href="[area ord/checkout]">[L]Proceed to checkout[/L]</a>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
+<!-- END CONTENT -->
+@_BOTTOM_@ \ No newline at end of file
diff --git a/pages/ord/checkout.html b/pages/ord/checkout.html
new file mode 100644
index 0000000..53963ae
--- /dev/null
+++ b/pages/ord/checkout.html
@@ -0,0 +1,138 @@
+[tmp page_title]__COMPANY__ -- [L]Checkout[/L][/tmp]
+[tmp display_class]noleft[/tmp]
+[tmpn onepage]1[/tmpn]
+
+[include include/checkout/initialization]
+
+@_TOP_@
+ <!-- BEGIN CONTENT -->
+ <div class="container p-4">
+ <!-- Hero Section-->
+ <div class="py-5">
+ <h2>[L]Checkout form[/L]</h2>
+ [if session logged_in]
+ <p class="lead">[L]Please verify the information below and click the <b>'Place Order'</b> button to process your order.[/L]</p>
+ [include include/checkout/login_form][/else][/if][if !variable NO_TAX_POPUP]
+ [include include/checkout/tax_popup][/if]
+ [edisplay show_label=1 show_var=0 keep=1]
+ </div>
+ <!-- End Hero section-->
+ <div class="g-5">
+
+ <form action="[area href=@@MV_PAGE@@ secure=1]" method="POST" name="checkout">
+ [form-session-id]
+ <input type="hidden" name="mv_action" value="refresh"/>
+ <div>
+ <h4 class="d-flex justify-content-between align-items-center mb-3">[L]Order review[/L]</h4>
+ <ul class="list-group mb-3">
+ <!-- Product Item -->
+ [if items][then][item-list][item-calc]
+ #Log("Checking master item $master");
+ $row_class = ++$count % 2 ? 'cartnorm' : 'cartalt';
+ my $item = '[item-increment]';
+ my $up = q{[item-data merchandising upsell_to]};
+ my $cr = q{[item-data merchandising cross_sell]};
+ $upsell_remove{'[item-code]'} = 1;
+ $cross_remove{'[item-code]'} = 1;
+ my %seen = ( '' => 1 );
+ $Scratch->{upsell} .= " $up" if $up;
+ $Scratch->{cross_codes} .= " $cr" if $cr;
+ my @up = split /\s+/, $Scratch->{upsell};
+ my @cr = split /\s+/, $Scratch->{cross_codes};
+ @up = grep ( (!$seen{$_}++ && ! $upsell_remove{$_}), @up);
+ @cr = grep ( (!$seen{$_}++ && ! $cross_remove{$_}), @cr);
+ $Scratch->{upsell} = join " ", @up;
+ $Scratch->{cross_codes} = join " ", @cr;
+ return;
+ [/item-calc]
+ <li class="list-group-item d-flex justify-content-between lh-sm">
+ <div>
+ <a href="[area href="[item-sku]"]"><h6 class="my-0">[item-description]</h6></a>
+ <small class="text-muted">[item-discount-price]</small>
+ <small class="text-muted">x [item-quantity]</small>
+ </div>
+ <span class="text-muted">[item-discount-subtotal]</</span>
+ </li>
+ [/item-list]
+ <!-- End of Product Item -->
+ <li class="list-group-item d-flex justify-content-between">
+ <span>Subtotal</span>
+ <strong>[subtotal]</strong>
+ </li>
+ [/then][/if]
+ </ul>
+ </div>
+ <!-- Shipping Address -->
+ <div>
+ <h4 class="mb-3">Shipping</h4>
+ [include include/checkout/shipping_address]
+ </div>
+ <!-- End of Shipping Address -->
+ <!-- Billing Address -->
+ [include include/checkout/billing_address]
+ <!-- End of Billing Address -->
+ <!-- Delivery Method -->
+ <div>
+ <h4>Delivery method</h4>
+ <!-- Delivery option-->
+ <div class="row">
+ <div class="form-group col-md-6 d-flex align-items-center">
+ <input type="radio" name="mv_shipmode" id="option0" value="cpa" checked/>
+ <label class="ml-3" for="option0">
+ <strong class="d-block text-uppercase mb-2">[shipping-desc]</strong>
+ <span class="text-muted text-sm">
+ [L]Get it right on next day - fastest option possible.[/L]
+ </span>
+ </label>
+ </div>
+ </div>
+ <!-- End of delivery option -->
+ </div>
+ <!-- End of Delivery Method -->
+ <!-- Payment Method -->
+ <div>
+ <h4 class="mb-3">Payment method</h4>
+ <!-- Payment Option-->
+ <div class="row">
+ <div class="form-group col-md-6 d-flex align-items-center">
+ <input type="radio" name="mv_order_profile" value="purchase_order" id="payment-method-1" checked/>
+ <label class="ml-3" for="payment-method-1">
+ <strong class="d-block text-uppercase mb-2">[L]Manual Transfer[/L]</strong>
+ <span class="text-muted text-sm">[L]Please make payments by using the bank transfer method to BCA 12345678 a/n ABCDEFG.[/L]</span>
+ </label>
+ </div>
+ </div>
+ <!-- /Payment Option-->
+ </div>
+ <!-- End of Payment Method -->
+ <!-- Order summary -->
+ <div>
+ <h4 class="mb-3">Order Summary</h4>
+ <div>
+ <p class="text-sm">Shipping and additional costs are calculated based on values you have entered.</p>
+ <ul class="order-summary mb-0 list-unstyled">
+ <li class="order-summary-item"><span>[L]Subtotal[/L]</span><span>[subtotal]</span></li>
+ <li class="order-summary-item"><span>[L]Shipping[/L]</span><span>[shipping]</span></li>
+ <li class="order-summary-item border-0"><span>[L]Total[/L]</span><strong class="order-summary-total">[total-cost]</strong></li>
+ </ul>
+ </div>
+ </div>
+
+ <!-- End of Order Summary -->
+ <div class="d-grid gap-2 d-md-block mb-5">[if items]
+ &nbsp;&nbsp;
+ [button
+ text="[L]Place Order[/L]"
+ wait-text="-- [L]Wait[/L] --"
+ class="btn btn-primary btn-lg btn-block"
+ ] mv_todo=submit
+ [/button][else]
+ <b>[L]No items in your shopping cart![/L]</b>[/else][/if]
+ </div>
+ </form>
+
+ </div>
+ [tmp clear_errors][error all=1 comment="Clear errors"][/tmp]
+ </div>
+ <!-- END CONTENT -->
+__BOTTOM__
diff --git a/pages/results.html b/pages/results.html
new file mode 100644
index 0000000..aea3ff7
--- /dev/null
+++ b/pages/results.html
@@ -0,0 +1,92 @@
+@_TOP_@
+
+ <!-- Catalog Content -->
+ <div class="container pt-5">
+ <div class="row text-center text-lg-left pt-2">[search-region][on-match][tmp meta_header][calcn]
+ return unless $Values->{more_link};
+ (my $more_page = $Session->{last_url}) =~ s|^/.*/([^\..]+)(?:\.html)?|$1|;
+ if ($more_page =~ /^(Next|Prev)/) {
+ $Tag->tag({ op => 'header' }, qq{Cache-control: no-cache\nCache-control: no-store\nPragma: no-cache\nExpires: 0\nContent-type: text/html});
+ }
+ ## $mc set above.
+ my $ml = $Values->{mv_matchlimit} || '';
+ my $fm = $Values->{mv_first_match} || '';
+ my ($canon, $rel_prev, $rel_next);
+ #Debug("more_page=$more_page, ml=$ml, fm=$fm, mc=$mc");
+ my ($prev, $next);
+ REL: {
+ last REL if $mc < $ml;
+ if ($fm == 0 || $fm == 1) { # first page
+ $rel_next = $Values->{more_link} . '/2';
+ } elsif (($mc - $fm - $ml) <= 0) { # last page
+ $prev = sprintf("%.0f", (($fm+$ml) / $ml) - 1);
+ $rel_prev = $Values->{more_link} . '/' . ($prev ? $prev : '1');
+ } else { # page 2 to second-to-last
+ $prev = sprintf("%.0f", ($fm / $ml));
+ $next = $prev + 2;
+ $rel_prev = $Values->{more_link} . "/$prev";
+ $rel_next = $Values->{more_link} . "/$next";
+ }
+ }
+ CANON: {
+ last CANON if $more_page !~ /^1|(Next|Prev)/;
+ if ($more_page eq '1') {
+ $canon = '';
+ } else {
+ $canon = sprintf("%.0f", ($fm+$ml) / $ml);
+ $canon = '' if $canon == 1;
+ }
+ $canon = $Values->{more_link} . ($canon ? "/$canon" : '');
+ }
+ my @out;
+ my $url = ($Variable->{SAMPLEURL} . $Variable->{CGI_URL}) || '';
+ my $ext = $Scratch->{mv_add_dot_html} ? '.html' : '';
+ $url !~ /\/$/ and $url .= '/';
+ push @out, qq{<link rel="prev" href="$url$rel_prev$ext">} if $rel_prev;
+ push @out, qq{<link rel="next" href="$url$rel_next$ext">} if $rel_next;
+ $Tag->tmp('rel_next', 1) if $rel_next;
+ push @out, qq{<link rel="canonical" href="$url$canon$ext">} if $canon;
+ if ($prev || $next) { $Scratch->{page_title} = $Scratch->{page_title} . ' - Page ' . (($prev || $next)+1) };
+ return "\t" . join "\n\t", @out;
+ [/calcn][/tmp][/on-match][tmp bw_more][more-list][first-anchor][/first-anchor][last-anchor][/last-anchor][link-template]<li><a href="[area href=|[value more_link]/$ANCHOR$|]">$ANCHOR$</a></li>[/link-template]
+ <nav>
+ <ul class="pagination">
+ {PREV_LINK}{MORE_LIST}[if scratch rel_next]{NEXT_LINK}[/if]
+ </ul>
+ </nav>
+ [/more-list][/tmp][calc]
+ my $more = $Scratch->{bw_more};
+ $more and $more =~ s,<strong>,<li class="active"><a href="#">,;
+ $more and $more =~ s,</strong> *,</a></li>,g;
+ $Scratch->{bw_more} = $more;
+ return $Values->{more_link} ? $more : undef;
+ [/calc][search-list]
+ <div class="col-lg-4 col-md-4">
+ <a href="[area [item-code]]" class="d-block h-100">
+ <img src="__IMAGE_DIR__/thumb/[item-field thumb]" class="img-fluid img-thumbnail" alt="[item-description]" title="[item-code]">
+ </a>
+ </div>[/search-list][no-match]
+ <div>[comment]
+ Don't show the search string if it was empty, since it looks weird
+ and the user will see the error below anyway.
+ [/comment][tmp matchstring][value-extended name=mv_searchspec joiner=" | " filter=encode_entities][/tmp][if scratch matchstring]
+ [msg arg.0="[scratch matchstring]"]Sorry, no matches for <B>%s</B>.[/msg]
+ [/if][if value mv_search_error]
+ <B>
+ [L]Errors[/L]:
+ <BR>[value-extended name=mv_search_error joiner="<BR>" filter=encode_entities]
+ </B>
+ [/if]
+ </div>
+ [/no-match][if value more_link][scratch bw_more][else][more-list]
+ <div class=morelist>
+ [msg arg.0="[matches]" arg.1="[match-count]"]Matches %s of %s found.[/msg]
+ [link-template]<a href="$URL$" target="_top" class="morelink">$ANCHOR$</a>[/link-template]
+ <BR>[more]<BR>
+ </div>
+ [/more-list]
+ [/else]
+ [/if][/search-region]
+ </div>
+ </div>
+ <!-- .End of Catalog Content -->__BOTTOM__
diff --git a/templates/layout/noleft b/templates/layout/noleft
new file mode 100644
index 0000000..9c5e712
--- /dev/null
+++ b/templates/layout/noleft
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <meta name="description" content="">
+ <meta name="author" content="namatoko">
+ <meta name="generator" content="namatoko">
+ <title>__COMPANY__</title>
+ <link rel="stylesheet" type="text/css" href="">
+ <!-- Bootstrap Core CSS-->
+ <link href="__WWW_DIR__/css/bootstrap.min.css" rel="stylesheet">
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"/>
+ <style>
+ .bd-placeholder-img {
+ font-size: 1.125rem;
+ text-anchor: middle;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+
+ @media (min-width: 768px) {
+ .bd-placeholder-img-lg {
+ font-size: 3.5rem;
+ }
+ }
+ </style>
+ <link href="__WWW_DIR__/css/product.css" rel="stylesheet">
+</head>
+<body>
+ <!-- Navbar -->
+ <nav class="navbar navbar-light bg-light fixed-top">
+ <div class="container d-flex justify-content-between">
+ <a href="#" class="navbar-brand d-flex align-items-center">
+ <img src="__IMAGE_DIR__/__LOGO__" width="30" height="30" class="rounded-circle d-inline-block align-top mr-2" alt="">
+ __COMPANY__
+ </a>
+ <div class="nav-item">
+ <a class="navbar-icon-link" href="[area ord/basket]">
+ <span class="material-icons-outlined">
+ shopping_bag
+ </span>
+ </a>
+ </div>
+ </div>
+ </nav>
+ <!-- End of Navbar -->
+ {{:DEFAULT}}
+</body>
+</html>
diff --git a/variables/BOTTOM b/variables/BOTTOM
new file mode 100644
index 0000000..42a82a5
--- /dev/null
+++ b/variables/BOTTOM
@@ -0,0 +1,16 @@
+[output name=copyright]
+__COPYRIGHT__
+
+[output name=edit_controls]
+__ADL_PAGE__
+
+[unpack]
+[include file="templates/layout/[either][scratch display_class][or]noleft[/either]"]
+[/unpack]
+[comment] Clear these puppies [/comment][calc]
+ for (qw/
+ page_title
+ display_class
+ /) { delete $Scratch->{$_} }
+ return;
+[/calc]