Spring Framework

¿Qué es Spring?

  • Es un framework que integra los mejores frameworks o tecnologías permitiendo desarrollar aplicaciones robustas sobre la máquina virtual de java, evitando las muchas configuraciones.
     

¿Qué es Spring?

  • El core de Spring Framework provee las siguientes características:
    – Inyección de dependencias (DI)
    – Programación orientada a aspectos
    – Soporte para aplicaciones MVC y servicios RESTful.
    – Soporte para JDBC, JPA y JMS
    – Soporte para automatización de pruebas

Módulos Spring

Controlador IoC (DI)

Anotaciones para DI

@Autowired Se puede aplicar a métodos y propiedades. Automáticamente se asignan los objetos de los cuales la clase depende. Se puede indicar que una dependencia es opcional (required=false)
@Qualifier("name") Cuando hay varios candidatos para satisfacer la dependencia se especifica el nombre del bean mediante esta anotación
@PostConstruct Anota un método que se ejecuta después de construir los beans
@Component("name") Define un bean manejado por Spring

Anotaciones para DI

@Service Define un bean de la capa de servicio
@Controller Define un bean de la capa de control
@Repository Define un bean de la capa de datos
@Scope("prototype") Define el mecanismo para la creación del bean. Por defecto es el mecanismo es singleton. Puede ser prototype, request,
session, global-session
@Configuration Define una clase que permite la creación de beans (clases) implementadas fuera de nuestro proyecto
@Bean Define un bean implementado fuera de nuestro proyecto

Anotaciones para DI

@Import(ConfigA.class) Importa los beans definidos en otra clase anotada con
@Configuration
@Value Carga una propiedad con un valor definido en una archivo
.properties o por una expresion SpEL. Ej: @Value("$
{spring.datasource.password}"),
@Value(“#{ T(java.lang.Math).random() * 100.0}”), @Value(“#{
@bean.property}”)

Manejo de Transacciones

  • Spring Framework provee mecanismos para el manejo automático de transacciones.
  • Soporta automáticamente el manejo de transacciones para el acceso a base de datos.
  • Podemos usar la anotación @Transactional para hacer que una de nuestras clases sea manejado por el gestor de transacciones de Spring.
  • Un atributo muy útil de esta anotación es el timeout, que define el máximo tiempo de espera de una operación.

Capa de Persistencia

Spring JPA

  • Spring crea repositorios automáticamente con consultas básicas y permite añadir búsquedas más complejas mediante la interfaz repository.
  • Para búsquedas más eficientes de texto completo (full text search), se puede usar un sistema de indexado como Lucent.

Annotaciones de JPA

@Entity Declara la clase como una entidad o una tabla
@Table Sirve para especificar el nombre de la tabla
@Id Especifica la clave primaria de una tabla
@GeneratedValue Especifica como se genera la clave primaria. Puede ser: automatica, manual o generado secuancialmente.
@Transient Especifica que la propiedad de la clase no se almacena en la base de datos
@Column Especifica que la propiedad corresponde a una columna en la base de datos

Annotaciones de JPA

@SequenceGenerator Crea una secuencia para generar la clave primaria.
@JoinColumn Usado para enlazar dos entidades o tablas. Usado para las relaciones many-to-many y one-to-many.
@ManyToMany Usado para definir relaciones many-to-many
@ManyToOne Usado para definir relaciones many-to-one
@OneToMany Usado para definir relaciones one-to-many
@OneToOne Usado para definir relaciones one-to-one

Ejemplo JPA

package com.example.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Data
public class Libro {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
	private long id;
	private String titulo;
	private String autor;
	private String precio;

        public Libro()
        { }
        
	
	public Libro(long id, String titulo, String autor, String precio)
	{
		this.id=id;
                this.titulo=titulo;
		this.autor=autor;
		this.precio=precio;
	}
}

Ejemplo JPA

package com.example.persistency;


import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface LibroRepository extends CrudRepository<Libro, Long> {

    List<Libro> findByTitulo(String titulo);
    List<Libro>	findByTituloLike(String tituloSegment);
}
 public String cargarLibros()
    {
    	 Libro libro = new Libro("CSS3 For Web Developers", "Jeffrey Zeldman", "$ 35.000 pesos");     
         librosRepository.save( libro );
         libro = new Libro("Diseño de páginas responsivas", "Ethan Marcotte", "$ 50.000 pesos");     
         librosRepository.save( libro );
         libro = new Libro("You're my favorite clinet", "Mike Monteiro", "$ 25.000 pesos");     
         librosRepository.save( libro );
         libro = new Libro("On Web Typography", "Jason Santa maria","$ 40.000 pesos");     
         librosRepository.save( libro );
         libro = new Libro("Saas For Web Designers", "Dan Cederholm", "$ 75.000 pesos");     
         librosRepository.save( libro );
    	return "listo";
    }

Ejemplo JPA

spring.datasource.url=jdbc:mysql://localhost:3306/tienda
spring.datasource.username=USUARIO
spring.datasource.password=PASSWORD
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.generate-ddl=true

application.properties

Opciones en las Consultas

public interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastnname);
// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname,String fistname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname,String fistname);
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname,String fistname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

Modificadores de Resultados

public interface PersonRepository extends Repository<User, Long> {
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
}

Paginación

public interface PersonRepository extends Repository<User, Long> {
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
}

Los resultados se pueden paginar de la siguiente manera:

Spring permite controlar las páginas desde el controlador de navegación asi:

//usa los parámetros page, size y sort
@RequestMapping
public String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}

Capa Web

Web MVC

  • Spring Framework soporta el patrón MVC mediante la implementación de un DispatcherServlet
  • Para indicar el mapeo entre las URL y los bean correspondientes se usan anotaciones.

Web MVC

Anotaciones

@Controller Define un bean de la capa de control de navegación
@RequestMapping Define la URL a la cual se debe atender. Se define también el método HTTP, tipo de contenido consumido y generado.
@PathVariable Se usa en los parametros para indicar una variable dentro del camino (path) de la URL. Ej: @RequestMapping(path = "/accounts/{account}"

Anotaciones

@RequestBody Se usa para recibir un parametro que viaja por el body HTTP
@RequestParam Se usa para recibir un parametro presente en la URL.
@RequestHeader Usado para mapear un encabezado HTTP a una variable Java.
@CookieValue Usado para recibir como parámetro un valor almacenado
en las Cookies.
@SessionAttributes Usado para recibir y escribir un valor en la session.

Ejemplo MVC

package com.example.control;

import java.util.ArrayList;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;


@Controller
public class LibrosControlador {
	
	@Autowired
	private LibroRepository librosRepository;
	

	private Iterable<Libro> resultado;
	
    @RequestMapping({"/","/home"})
    public String home(@RequestParam(value="busqueda", required=false) String busqueda, Model model) {
    	if(busqueda!=null&&!busqueda.equals("Escriba el libro a buscar"))
    	{
    		resultado=librosRepository.findByTituloLike("%"+busqueda+"%");
    	}
    	else
    	{
    		resultado=librosRepository.findAll();
    	}
    	model.addAttribute("resultado",resultado);
        return "index";
    }
    

}

Tecnologías para Vistas

  • JSP y JSLT
  • Thymeleaf
  • Groovy Markup Templates
  • Velocity
  • FreeMarker
  • XML (Marshalling o Mapping)
  • XSLT
  • Documents (PDF/Excel)
  • JasperReports (CSV, Excel, HTML y PDF)
  • Feeds (RSS, Atom)
  • JSON

Thymeleaf

  • Permite hacer vistas que son documentos XML y HTML válidos aún sin ser procesadas por el servidor.
  • Usa un mecanismo para generar el DOM muy eficiente.
  • Define un lenguaje de expresiones propio, pero tiene soporte para el SpEL.
  • Usa los mismos nombres de los parámetros definidos por HTML y es completamente compatible con HTML5.

Thymeleaf

  • Define las siguientes variables para manejar contexto:
    – param: Accede a los parámetros enviados por HTTP.
    – session: Accede a las variables en la sesión.
    – application: Accede a los atributos del ServletContext.

Ejemplos Thymeleaf

<!-- ${} para obtener variables u objetos; || permite concatenar texto sin usar '' -->
<!-- el ejemplo genera   <span> conevar twovar, threevar-->
<span th:text="${conevar} + ' ' + |${twovar}, ${threevar}|">

<!-- @{} resuelve URLs; para los parametros se usan ()  -->
<!-- el ejemplo genera  <a href="/details/user.login?orderId=o.id">view</a> -->
<a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>

<!-- th:object obtiene un objeto para usarlo en un conjunto de etiquetas, las propiedades se obtienen mediante *{}  -->
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

<!-- Thymeleaf incluye unos objetos de utilidad como #calendars, #numbers, #lists -->
<!-- https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#appendix-b-expression-utility-objects -->
<p>
Today is: <span th:text="${#calendars.format(today,'dd MMMM
yyyy')}">13 May 2011</span>
</p>


<!-- Se pueden mostrar etiquetas opcionales -->
<div th:if="${user.isAdmin()} == false">
<!-- Condicionales con el carácter ? luego el valor si se cumple : y el valor si no-->
<tr th:class="${row.even}? 'even' : 'odd'">
...
</tr>
<div th:object="${session.user}">
...
<p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p>
</div>
<div th:text="${@authService.getUserName()}">...</div>
<p>Age: <span th:text="*{age != null}? *{age} : '(no age
specified)'">27</span>.</p>

<!-- Repetición de etiquetas para cada elemento de una lista o iterable con th:each-->
<tr th:each="prod : ${prods}" class="row" th:classappend="$
{prodStat.odd}? 'odd'">

<!-- Generación de etiquetas según el caso con th:switch y th:case-->
<!-- Obtención de mensajes o propiedades de la aplicación mediante #{} -->
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
<p th:case="*">User is some other thing</p>
</div>

Ejemplo Thymeleaf - Tienda


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:th="http://www.thymeleaf.org">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8" /> 
<link rel="shortcut icon" href="images/sign.ico" type="image/x-icon" />
<link href="estilos/index.css" rel="stylesheet" type="text/css"
	media="all" />

<title>Una tienda en linea responsiva</title>

<script
	src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js'
	type='text/javascript'></script>

<script type="text/javascript">
$(document).ready(function(){
    $("input").focus(function(){
    	if($(this).attr("class")=="emptyInput")
    		$(this).val("");
        $(this).removeClass("emptyInput");
    });
    $("input").blur(function(){
    	if($(this).val()=="")
    	{
    		$(this).val("Escriba el libro a buscar");
        	$(this).addClass("emptyInput");
    	}
    });
	}); 
</script>

</head>
<body id="index" class="">
	
	<div id='main'>
		<header>
		
		<h1>
			<a class='logo icon-aba-logo' href='/'>Tienda de Libros</a> <span
				class='logo-tagline'>Libros para diseñadores de páginas</span>
		</h1>
		<nav class='primary-nav'>
		<ul>
			<li id='nav-home'><a href='/'>Home</a></li>

			<li id='nav-help'><a href='/paginas/ayuda/'>Ayuda</a></li>
			<li>
				<form method="post" action="/tienda/buscar" th:action="@{/}">
					<input class="emptyInput" name="busqueda" value="Escriba el libro a buscar"></input>
					<input type="submit" value="Buscar"></input>
				</form>
			</li>
		</ul>
		</nav> <a id='nav-cart' class='callout shopping-cart' href='/cart'
			title='View Cart'> <span class='shopping-cart-count'>0</span> <span
			class='shopping-cart-icon icon-cart-empty'></span>

		</a> </header>


		<div class='content' id='home'>
			
			<section class='feature'> <a
				href='/products/css3-for-web-designers'> <span
				class='feature-edition'>Second Edition</span>
				<div class='feature-big-cover'></div>
				<div class='feature-small-cover'>
					<div class="book-thumb icon-aba-cover-2"></div>
				</div>
				<div class='feature-info'>
					<h2 class='feature-header'>
						<span class='feature-author'>Dan Cederholm</span> <span
							class='feature-title'>CSS3 for Web Designers</span>
					</h2>
					<h3 class='foreword-byline'>
						<span class='byline-prefix'>foreword by</span> <span
							class='byline-name'>Jeffrey Zeldman</span>
					</h3>
					<p class='foreword-text'>Rediscover a universe of creative
						possibilities with CSS3. Dan Cederholm updates his essential guide
						with new examples, polished code, and a new chapter on micro
						layouts.</p>
					<p class='feature-format-note'>Available in paperback, ePub,
						PDF, and mobi</p>
				</div>
			</a> <a class='feature-buy-button button button--large'
				href='/products/css3-for-web-designers' title='Buy Now'>Buy Now</a>
			</section>

			<section class='other-books subsection divider-top-thick divider-bottom-thick'>
			<h3>Also from A Book Apart</h3>
			<ul class='books'>


				
			
				<li th:each="libro : ${resultado}" class='book'><a
					href='/products/titulo' th:href="@{'/products/'+${libro.titulo}}">
						<div class="book-thumb"><img src="images/Books.png" class="book-image"></img></div>
						<h4 class='book-title' th:text="${libro.titulo}">Título</h4>
						<p class='book-byline' >
							<span class='byline-prefix'>por</span> <span class='byline-name' th:text="${libro.autor}">Autor</span>
							<br></br>
							<br th:text="${libro.precio}">precio</br>
						</p> 
						<span class='button book-buy-button' title='Comprar'>Comprar</span>
						</a>
				</li>
				

			</ul>
			</section>
	
		</div>



		<footer>
		<ul class='footer-links links'>
			<li>© Copyright 2015,  Gustavo Uribe</li>
		</ul>
		</footer>
	</div>


</body>

</html>

/templates/index.html

Ejemplo Thymeleaf - Tienda

@
-ms-viewport {
	width: device-width
}

*, *:before, *:after {
	box-sizing: border-box
}

html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p,
	blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn,
	em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var,
	b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend,
	table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas,
	details, embed, figure, figcaption, footer, header, hgroup, menu, nav,
	output, ruby, section, summary, time, mark, audio, video {
	border: 0;
	font: inherit;
	font-size: 100%;
	margin: 0;
	padding: 0;
	vertical-align: baseline
}

html {
	line-height: 1
}

ol, ul {
	list-style: none
}

table {
	border-collapse: collapse;
	border-spacing: 0
}

caption, th, td {
	font-weight: normal;
	text-align: left;
	vertical-align: middle
}

q, blockquote {
	quotes: none
}

q:before, q:after, blockquote:before, blockquote:after {
	content: "";
	content: none
}

a img {
	border: none
}

article, aside, details, figcaption, figure, footer, header, hgroup,
	menu, nav, section, summary {
	display: block
}

.wf-loading thead th, .wf-loading .button, .wf-loading .callout h3,
	.wf-loading .primary-nav, .wf-loading h2.title, .wf-loading .subsection h3,
	.wf-loading .book-byline .byline-name, .wf-loading .feature-header,
	.wf-loading .feature-title, .wf-loading .feature-author, .wf-loading .press-date,
	.wf-loading #product .title .callout, .wf-loading #product .title .callout p,
	.wf-loading .product-byline, .wf-loading .product-option-info h4,
	.wf-loading .alert__title, .wf-loading .product-option-note,
	.wf-loading .offer-message {
	visibility: hidden
}

body {
	font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans",
		Verdana, Arial, sans-serif
}

h4, .logo-tagline, .byline-prefix, .foreword-text, .book-summary,
	.feature-author, .feature-format-note, .press p, .press ul, .press ol,
	.alert--fancy p, .product-byline .byline-name i, .books .byline-name i,
	.product-foreword p, .product-desc-gift p, .product-bundle-summary p,
	.product-review-quote, .product-review-byline .byline-title,
	.product-author-bio p, .product-bundle-author-bio p, .book-desc,
	.newsletter-message, #faqs h4, #faqs p, .about-person-name,
	.about-person-title, .about-text p, .basic-text p {
	font-family: Georgia, "Times New Roman", Times, serif
}

thead th, .button, .callout h3, .primary-nav, .subsection h3,
	.byline-name, .book-byline .byline-name, .press-article h3, .press-date,
	#product .title .callout p, .product-option-info h4,
	.product-out-of-stock strong, .alert__title, .offer-message,
	.savings-qty, .savings-discount, .savings-msg, #cart-main .callout:before
	{
	font-family: "titling-gothic-condensed", "Helvetica Neue", Helvetica,
		Arial, sans-serif
}

h2, .button--large, .feature-title {
	font-family: "titling-gothic-skyline", "Helvetica Neue", Helvetica,
		Arial, sans-serif
}

body {
	border-top: solid 5px #82bc00;
	color: #333;
	background: #fff;
	font-size: 1em;
	width: 100%;
	zoom: 1
}

body:before, body:after {
	content: "";
	display: table
}

body:after {
	clear: both
}

a, .feature-title {
	-webkit-transition: color .25s;
	transition: color .25s;
	color: #82bc00
}

a:hover, .feature-title:hover {
	color: #997e11
}

em {
	font-style: italic
}

strong {
	font-weight: bold
}

h2 {
	font-weight: 400;
	font-style: normal;
	font-size: 5em;
	text-transform: uppercase
}

h2.title {
	margin-bottom: 24px
}

h2.title em {
	font-style: normal
}

h4 {
	margin-bottom: 4px;
	font-size: 1.125em;
	line-height: 1.25em
}

h4 a {
	text-decoration: none
}

th, tr, td {
	vertical-align: baseline
}

thead th {
	font-weight: 500;
	font-style: normal;
	margin-bottom: 12px;
	font-size: .675em;
	line-height: 1.5em;
	text-transform: uppercase;
	letter-spacing: .1em
}

.wf-active thead th {
	font-size: .75em
}

.bulleted-list li {
	margin-bottom: 12px;
	padding: 0px 0px 0px 12px;
	background-position: 0px 6px;
	font-size: .75em;
	line-height: 1.25em
}

.bulleted-list li a {
	font-style: italic
}

.bulleted-list li:last-child {
	margin-bottom: 0px
}

.callout {
	border-radius: 4px;
	padding: 8px;
	background-color: #f6f5ea
}

.callout h3 {
	font-weight: 500;
	font-style: normal;
	font-size: .625em;
	text-transform: uppercase;
	letter-spacing: .1em;
	color: #997e11;
	margin-bottom: 7px
}

.callout p {
	font-size: .75em;
	line-height: 1.5em
}

.callout-for-touch {
	clear: both;
	margin-bottom: 10px;
	text-align: center;
	color: #999;
	background-color: #f7f7f7
}

.wf-active .callout h3 {
	font-size: .75em
}

#main {
	width: 100%;
	margin: 0px
}

header {
	zoom: 1;
	position: relative;
	margin-bottom: 30px
}

header:before, header:after {
	content: "";
	display: table
}

header:after {
	clear: both
}

h1 {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

.primary-nav {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%;
	font-weight: 400;
	font-style: normal;
	margin-top: 20px;
	font-size: .9375em;
	line-height: 1.1em;
	letter-spacing: .1em;
	text-transform: uppercase
}

.primary-nav li {
	display: inline;
	float: left;
	margin-top: 10px;
	margin-right: 3px
}

.primary-nav li:last-child {
	margin-right: 0px
}

.primary-nav li a,form {
	border-radius: 3px;
	display: block;
	padding: 6px 8px 6px 8px;
	text-decoration: none;
	background-color: #fff;
	border: 1px solid #fff;
	color: #333;
	-webkit-transition: color .25s, background-color .25s, border-color .25s;
	transition: color .25s, background-color .25s, border-color .25s
}

.primary-nav li a.selected, .primary-nav li a:hover {
	color: #1e90de;
	background-color: #f6f5ea;
	border-color: #bcb79c
}

body#index #nav-home a, body#collection #nav-store a, body#product #nav-store a,
	body#product-tk #nav-store a, body#blog #nav-press a, body#article #nav-press a,
	body.about #nav-about a, body.help #nav-help a {
	color: #1e90de;
	background-color: #f6f5ea;
	border-color: #bcb79c
}

body#index #nav-home a:hover, body#collection #nav-store a:hover, body#product #nav-store a:hover,
	body#product-tk #nav-store a:hover, body#blog #nav-press a:hover, body#article #nav-press a:hover,
	body.about #nav-about a:hover, body.help #nav-help a:hover {
	border-color: #997e11
}

body#cart #nav-cart {
	color: #1e90de;
	background-color: #f6f5ea;
	border-color: #bcb79c
}

body#cart #nav-cart:hover {
	border-color: #997e11
}

.wf-active .primary-nav {
	font-size: 1.1em
}

.logo {
	display: block;
	text-indent: -9999px;
	width: 244px;
	height: 32px;
	margin: 26px 0px 3px 0px
}

.logo-tagline {
	font-size: .8em;
	color: #82bc00;
	font-style: italic
}

.shopping-cart {
	position: absolute;
	top: 24px;
	right: 3.125%;
	padding: 15px;
	text-decoration: none;
	-webkit-transition: border-color .25s;
	transition: border-color .25s;
	border: #fff solid 1px
}

.shopping-cart:hover {
	border-color: #bcb79c
}

.shopping-cart-count {
	display: inline;
	float: left;
	border-radius: 20px;
	padding: 4px 4px 3px 4px;
	min-width: 20px;
	font-size: .825em;
	text-align: center;
	background-color: #c1bfb0;
	color: #fff
}

.shopping-cart-icon {
	display: inline;
	float: left;
	filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70);
	opacity: .7;
	width: 25px;
	height: 20px;
	margin-left: 4px
}

.is-full .shopping-cart-count {
	background-color: #7db72f
}

.is-full .shopping-cart-icon {
	filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
	opacity: 1
}

aside {
	display: none;
	border-left: solid 1px #ccc;
	padding-left: 20px
}

aside .subsection {
	padding-top: 20px;
	margin-top: 20px;
	border-top-color: #ccc
}

aside #question-callout a {
	padding-left: 19px;
	background-position: 0px 2px
}

aside h4 {
	margin-bottom: 10px
}

aside .subsection a {
	text-decoration: none
}

aside .book-thumb {
	margin: 0px
}

h2.title {
	font-size: 1.875em
}

.content h2.title {
	display: inline;
	float: left;
	width: 93.75%;
	font-size: 1.875em;
	margin-left: 3.125%;
	margin-right: 3.125%
}

.wf-active h2.title {
	font-size: 5em;
	letter-spacing: 1px
}

.subsection h3, th {
	color: #997e11
}

.content-with-sidebar {
	zoom: 1;
	display: block;
	width: auto;
	margin-left: 0px;
	margin-right: 0px;
	margin-left: 0px !important;
	margin-right: 0px !important
}

.content-with-sidebar:before, .content-with-sidebar:after {
	content: "";
	display: table
}

.content-with-sidebar:after {
	clear: both
}

.content-with-sidebar h2.title {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

.subsection h3 {
	font-weight: 500;
	font-style: normal;
	margin-bottom: 12px;
	font-size: .75em;
	line-height: 1.5em;
	text-transform: uppercase;
	letter-spacing: .1em
}

.wf-active .subsection h3 {
	font-size: .9375em
}

.divider-top, .divider-top-thick {
	padding-top: 30px;
	margin-top: 30px;
	border-top-style: solid;
	border-top-width: 1px
}

.divider-top-thick {
	border-top-width: 5px
}

.divider-bottom, .divider-bottom-thick {
	padding-bottom: 30px;
	margin-bottom: 30px;
	border-bottom-style: solid;
	border-bottom-width: 1px
}

.divider-bottom-thick {
	border-bottom-width: 5px
}

.divider-top, .divider-bottom {
	border-color: #ccc
}

.divider-top-thick, .divider-bottom-thick {
	border-color: #f2f1e2
}

.byline-name {
	font-weight: 500;
	font-style: normal;
	text-transform: uppercase;
	letter-spacing: .1em;
	text-decoration: none
}

.byline-prefix {
	font-style: italic;
	text-transform: none;
	letter-spacing: 0px
}

.book-thumb {
	-moz-box-sizing: content-box;
	-webkit-box-sizing: content-box;
	box-sizing: content-box;
	display: inline;
	float: left;
	margin-right: 10px;
	height: 154px;
	width: 100px;
	background-size: cover;
	-webkit-transition: border-color .25s;
	transition: border-color .25s;
	border: solid 1px #eee
}

.gift-card-thumb {
	display: inline;
	float: left;
	margin-top: 5px;
	margin-right: 10px;
	height: 100px;
	width: 100px;
	background-size: cover
}

.bundle-thumb {
	width: 150px
}

.book-thumb:hover {
	border-color: #ccc
}

.book-byline {
	display: block;
	margin: 4px 0px 20px 0px;
	color: #998f52
}

.book-byline .byline-prefix {
	font-size: .75em
}

.book-byline .byline-name {
	font-weight: 500;
	font-style: normal;
	font-size: .675em;
	text-transform: uppercase;
	letter-spacing: .1em
}

.product-bundle-offer .book-byline {
	margin-bottom: 10px
}

.wf-active .book-byline .byline-name {
	font-size: .75em
}

.book-summary {
	margin-top: 16px;
	font-size: .825em;
	line-height: 1.5em
}

.books {
	margin-bottom: -30px
}

.books li {
	margin-bottom: 30px
}

#collection .books li {
	margin-bottom: 60px
}

.books a {
	text-decoration: none
}

.feature {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%;
	text-decoration: none;
	box-shadow: inset 0px 0px 80px rgba(0, 0, 0, 0.05);
	border-radius: 5px;
	position: relative;
	background: #f6f5ea;
	min-height: 300px;
	margin-bottom: 10px
}

.feature a {
	text-decoration: none
}

.feature .byline-prefix {
	font-size: .875em
}

.feature .byline-name {
	font-size: .75em
}

.feature .foreword-text {
	color: #333;
	margin: 16px 0px;
	font-size: .9375em;
	line-height: 1.45em
}

.feature-edition {
	color: #fff;
	background: #c1bfb0;
	padding: 10px 10px 5px 5px;
	font-size: .75em;
	font-family: titling-gothic-condensed, "Helvetica Neue", Helvetica,
		Arial, sans-serif;
	letter-spacing: 1px;
	text-transform: uppercase;
	position: absolute;
	top: 0;
	right: 0;
	border-radius: 0 5px 0 3px
}

.feature-image img {
	display: block;
	width: 100%;
	height: auto
}

.feature-big-cover {
	border-radius: 5px;
	-webkit-transition: background-position 1s, width 1s;
	transition: background-position 1s, width 1s;
	position: absolute;
	left: 0px;
	top: 0px;
	width: 0px;
	height: 100%;
	z-index: 1;
	background: transparent
		url(//cdn.shopify.com/s/files/1/0051/7692/t/2/assets/aba-home-2-2e-feat.jpg?8276597854392778624)
		no-repeat;
	background-size: auto 327px;
	background-position: -430px 0px
}

.feature-small-cover .book-thumb {
	display: block;
	float: none;
	margin: 25px
}

.feature-info {
	zoom: 1;
	display: block;
	width: auto;
	margin-left: 0px;
	margin-right: 0px;
	position: relative;
	margin-right: 0px !important;
	padding: 25px
}

.feature-info:before, .feature-info:after {
	content: "";
	display: table
}

.feature-info:after {
	clear: both
}

.feature-header {
	zoom: 1;
	display: block;
	width: auto;
	margin-left: 0px;
	margin-right: 0px;
	margin-bottom: 0px;
	font-size: 1em
}

.feature-header:before, .feature-header:after {
	content: "";
	display: table
}

.feature-header:after {
	clear: both
}

.feature-title {
	display: block;
	font-size: 2.5em;
	font-style: normal;
	text-decoration: none;
	color: #82bc00
}

.feature-author {
	display: block;
	margin-bottom: 4px;
	font-size: 1.5em;
	text-transform: none;
	color: #333
}

.feature-format-note {
	margin: 13px 0px 26px 0px;
	font-style: italic;
	font-size: .8125em;
	line-height: 1.5em;
	color: #b1a55f
}

.feature-buy-button {
	box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
	position: absolute;
	right: 10px;
	bottom: -20px
}

.wf-active .feature-title {
	font-weight: 400;
	letter-spacing: 1px;
	font-size: 5.5em
}

.press-room {
	position: relative
}

.press-room .links {
	position: absolute;
	top: 25px;
	right: 0px;
	text-align: right
}

.press {
	position: relative
}

.press p {
	margin-bottom: 16px;
	font-size: 1em;
	line-height: 1.5em
}

.press ul, .press ol {
	margin-left: 130px;
	margin-bottom: 16px;
	font-size: 1em;
	line-height: 1.5em
}

.press ul {
	list-style: disc
}

.press ol {
	list-style: decimal
}

.press-thumb {
	float: left;
	margin: 5px 10px 10px 0
}

#blog .press-listing p {
	margin-bottom: 16px;
	font-size: 1em;
	line-height: 1.5em
}

.press-listing p {
	margin-bottom: 6px;
	font-size: .825em;
	line-height: 1.5em
}

.press-article .press-thumb {
	width: 90px !important;
	height: auto
}

.press-article h3 {
	margin-bottom: 8px;
	font-weight: 500;
	font-style: normal;
	font-size: .9375em;
	line-height: 1.5em;
	text-transform: uppercase;
	letter-spacing: .1em
}

.press-date {
	margin-bottom: 8px;
	font-weight: 500;
	font-style: normal;
	font-size: .625em;
	text-transform: uppercase;
	letter-spacing: .1em;
	color: #888
}

.press-article img {
	width: 100%
}

.wf-active .press-date {
	font-size: .75em
}

.press-more {
	font-size: .75em;
	line-height: 1.5em
}

#pagination {
	margin-bottom: 10px;
	font-size: .75em;
	line-height: 1.5em
}

#pagination .current, #pagination a {
	border-radius: 2px;
	padding: 2px 6px;
	margin: 0 2px
}

#pagination .current {
	background: #997e11;
	color: #fff
}

#pagination a {
	background: #f6f5ea;
	color: #997e11;
	text-decoration: none
}

#pagination a:hover {
	background: #997e11;
	color: #fff
}

.links {
	font-size: .75em;
	line-height: 1.5em;
	color: #888
}

.links li {
	display: inline-block;
	margin-right: 7px
}

.links li:before {
	content: "\2022";
	margin-right: 8px
}

.links li:first-child:before {
	display: none
}

.links li:last-child {
	margin-right: 0px
}

.links-rss {
	padding-left: 18px;
	background-position: 0px
}

.links-twitter {
	padding-left: 19px
}

#product .title {
	margin-bottom: 6px
}

#product .title .callout {
	display: inline;
	float: left;
	padding: 4px 8px;
	margin: 6px 0px 0px 0px;
	color: #fff;
	background-color: #7db72f
}

#product .title .second-edition {
	background-color: #c1bfb0
}

#product .title .callout p {
	font-style: normal;
	text-transform: uppercase;
	font-size: .4em;
	letter-spacing: .1em
}

#product .other-books, #cart .other-books {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

#product .other-books .books, #cart .other-books .books {
	zoom: 1;
	display: block;
	width: auto;
	margin-left: 0px;
	margin-right: 0px
}

#product .other-books .books:before, #product .other-books .books:after,
	#cart .other-books .books:before, #cart .other-books .books:after {
	content: "";
	display: table
}

#product .other-books .books:after, #cart .other-books .books:after {
	clear: both
}

#product .other-books .books li, #cart .other-books .books li {
	zoom: 1;
	display: block;
	width: auto;
	margin-left: 0px;
	margin-right: 0px
}

#product .other-books .books li:before, #product .other-books .books li:after,
	#cart .other-books .books li:before, #cart .other-books .books li:after
	{
	content: "";
	display: table
}

#product .other-books .books li:after, #cart .other-books .books li:after
	{
	clear: both
}

.wf-active #product .title .callout {
	padding: 5px 10px 4px 10px;
	margin: 14px 0px
}

.wf-active #product .title .callout p {
	font-weight: 300;
	font-size: .875rem
}

.wf-active .product-byline {
	font-size: 100%
}

.wf-active .product-option-info h4 {
	font-size: .875em;
	line-height: 1.5em
}

.wf-active .product-option-note {
	margin: 4px 0px 0px 4px
}

.wf-active .offer-message {
	font-size: 1.5em;
	letter-spacing: 1px
}

.product-title-text {
	display: inline;
	float: left;
	margin-right: 18px
}

.product-byline {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%;
	margin-bottom: 24px;
	color: #998f52;
	font-size: .75em
}

.product-byline .byline-prefix {
	font-size: 1.125em
}

.product-byline .byline-name {
	font-size: 1.25em;
	line-height: 1.2em
}

.product-byline .byline-name i, .books .byline-name i {
	font-size: .875em;
	margin-right: .1em;
	font-style: italic;
	letter-spacing: 0;
	font-weight: normal;
	text-transform: none
}

.hero-image {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%;
	height: auto;
	margin-bottom: 30px
}

.product-options {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

.gift-card-options {
	margin-top: 50px;
	margin-bottom: 30px
}

.product-options-list li {
	border-radius: 5px;
	position: relative;
	background-color: #f6f5ea;
	padding: 12px 10px 14px 10px
}

.product-options-list li:first-child {
	padding-left: 10px
}

.product-options-list li:last-child {
	border-right: none
}

.product-option-icon {
	filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
	opacity: .5;
	position: absolute;
	top: 12px;
	left: 10px;
	height: 36px
}

.product-option-paperback .product-option-icon {
	width: 36px
}

.product-option-paperback .product-option-info {
	padding-left: 40px
}

.product-option-ebook .product-option-icon {
	width: 36px
}

.product-option-ebook .product-option-info {
	padding-left: 40px
}

.product-option-gift .product-option-icon {
	width: 36px
}

.product-option-gift .product-option-info {
	padding-left: 40px
}

.product-option-bundle .product-option-icon {
	width: 64px
}

.product-option-bundle .product-option-info {
	padding-left: 68px
}

.product-option-info h4 {
	display: inline;
	margin-bottom: 2px;
	float: left;
	font-style: normal;
	font-weight: 500;
	font-size: .75em;
	line-height: 1.5em;
	text-transform: uppercase
}

.product-option-note {
	display: inline;
	float: left;
	position: relative;
	margin: 2px 0px 0px 4px
}

.product-option-note:hover>.product-option-callout {
	display: block
}

.product-option-question-icon {
	filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=75);
	opacity: .75;
	position: absolute;
	top: 0px;
	left: 0px;
	width: 12px;
	height: 12px;
	text-indent: -9999px
}

.product-option-question-icon:hover {
	filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
	opacity: 1
}

.product-option-callout p {
	line-height: 1.5em;
	color: #ccc
}

.product-option-callout {
	position: absolute;
	background: rgba(0, 0, 0, 0.8);
	left: -84px;
	bottom: 12px;
	padding: 8px 12px;
	z-index: 999;
	display: none;
	width: 180px
}

.product-option-callout:after {
	top: 100%;
	border: solid transparent;
	content: " ";
	height: 0;
	width: 0;
	position: absolute;
	pointer-events: none
}

.product-option-callout:after {
	border-color: rgba(0, 0, 0, 0);
	border-top-color: rgba(0, 0, 0, 0.8);
	border-width: 8px;
	left: 50%;
	margin-left: -8px
}

.product-option-price {
	display: block;
	clear: both;
	font-size: .6875em;
	font-weight: bold;
	color: #998f52
}

.product-out-of-stock {
	background: #efeddc;
	border-radius: 0 5px 5px 0;
	bottom: 0;
	margin: 0;
	padding: 12px 10px 0;
	position: absolute;
	right: 0;
	text-align: center;
	top: 0;
	width: auto;
	color: #bcb79c
}

.product-out-of-stock strong {
	display: block;
	font-size: .875em;
	font-weight: normal;
	line-height: 1.5em;
	letter-spacing: 1px;
	margin-bottom: 2px;
	text-transform: uppercase
}

.product-out-of-stock em {
	display: block;
	font-style: normal;
	font-size: .6875em
}

.product-option-add-button {
	position: absolute;
	top: 18px;
	right: 16px;
	padding-left: 16px;
	padding-right: 16px;
	font-size: 1em
}

.product-content, .product-bundle-offer {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

.product-bundle-offer a {
	text-decoration: none
}

h3.offer-message {
	margin: 20px 0 10px
}

.wf-active h3.offer-message {
	font-size: 1.25em
}

.product-bundle-offer-books {
	zoom: 1;
	position: relative;
	width: 100%;
	width: 620px;
	min-height: 130px;
	padding-top: 20px
}

.product-bundle-offer-books:before, .product-bundle-offer-books:after {
	content: "";
	display: table
}

.product-bundle-offer-books:after {
	clear: both
}

.product-bundle-offer-book {
	display: inline;
	float: left;
	position: relative;
	height: 123px
}

.ieold .product-bundle-offer-book {
	width: 250px
}

.product-bundle-offer-book h4 {
	font-size: 1em;
	font-style: italic;
	margin-bottom: 0px
}

.product-bundle-offer-book.left-book {
	width: 80px
}

.ieold .product-bundle-offer-book.left-book {
	padding-right: 110px
}

.left-book h4, .left-book .book-byline {
	display: none
}

.product-bundle-offer-book.right-book {
	width: 80px
}

.ieold .product-bundle-offer-book.right-book {
	padding-left: 110px
}

.product-bundle-offer-book.right-book .product-bundle-offer-book-thumb {
	left: 0px
}

.product-bundle-offer-book .book-byline {
	margin-top: 0px
}

.product-bundle-offer-book-thumb {
	position: absolute;
	top: 0px;
	width: 80px;
	height: 123px;
	margin: 0px
}

.ieold .product-bundle-offer-book-thumb {
	width: 100px;
	height: 154px
}

.product-bundle-offer-book-divider {
	display: inline;
	float: left;
	background-position: center center;
	width: 80px;
	height: 123px
}

.offer {
	margin-top: 20px;
	border-left: none;
	padding: 0px
}

.ieold .offer {
	margin-top: 80px
}

.offer-message {
	zoom: 1;
	margin-top: 6px;
	font-weight: 300;
	font-style: normal;
	line-height: 1.25em;
	color: #7db72f;
	font-size: 1.125em;
	letter-spacing: 0em;
	text-transform: uppercase;
	text-align: center
}

.offer-message:before, .offer-message:after {
	content: "";
	display: table
}

.offer-message:after {
	clear: both
}

.offer-form {
	margin-top: 15px
}

.product-foreword {
	display: inline;
	float: left;
	width: 100%
}

.product-desc-gift {
	display: inline;
	float: left;
	width: 100%
}

.product-bundle-summary {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

.product-foreword p, .product-desc-gift p, .product-bundle-summary p {
	margin-bottom: 24px;
	font-size: 1.25em;
	line-height: 1.5em
}

.product-foreword p:last-child, .product-desc-gift p:last-child,
	.product-bundle-summary p:last-child {
	margin-bottom: 0px
}

.product-foreword-byline {
	font-size: .75em
}

.product-contents, .product-tk-thumb, .product-gift-thumb {
	display: inline;
	float: left;
	width: 100%;
	border-color: #ccc
}

.product-contents h4 {
	font-size: .9375em;
	line-height: 1.5em
}

.product-contents .bulleted-list {
	margin-bottom: 16px
}

.product-contents .bulleted-list:last-child {
	margin-bottom: 0px
}

.product-additional-info h4, .product-contents h4 {
	color: #666
}

.product-tk-thumb .book-thumb-tk {
	width: 220px;
	height: 339px;
	display: block;
	border: 1px solid #eee;
	background-size: cover
}

.product-gift-thumb .gift-thumb {
	width: 220px;
	height: 131px;
	display: block;
	background-size: cover
}

.ieold .product-tk-thumb .book-thumb-tk {
	width: 100px;
	height: 154px
}

.product-reviews {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%;
	padding-bottom: 0px
}

.product-reviews blockquote {
	padding-left: 40px
}

.product-review-list li {
	margin-bottom: 30px
}

.product-review-quote {
	font-size: .9375em;
	font-style: italic;
	line-height: 1.5em;
	margin-bottom: 15px
}

.product-review-byline {
	font-size: .8125em;
	text-indent: -1.1em
}

.product-review-byline .byline-title {
	display: block;
	margin: 6px 0px 0px 0px;
	font-style: italic;
	line-height: 1.25em;
	color: #998f52;
	text-indent: 0
}

.product-bundle-author-list {
	zoom: 1;
	margin-bottom: -30px
}

.product-bundle-author-list:before, .product-bundle-author-list:after {
	content: "";
	display: table
}

.product-bundle-author-list:after {
	clear: both
}

.product-bundle-author-list li {
	margin-bottom: 30px
}

.product-author-info, #product-tk .product-author-info {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

.product-author-headshot {
	margin-left: 0px !important;
	margin-bottom: 20px;
	height: auto;
	display: inline;
	float: left;
	width: 30%;
	margin-right: 5%
}

.product-author-bio p, .book-desc {
	margin-bottom: 15px;
	font-size: 1em;
	line-height: 1.5em
}

.product-author-bio p:last-child {
	margin-bottom: 0px
}

.product-bundle-authors-info {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

.product-bundle-author-headshot {
	margin-bottom: 20px;
	display: inline;
	float: left;
	width: 30%;
	margin-right: 5%
}

.product-bundle-author-bio p {
	margin-bottom: 15px;
	font-size: 1em;
	line-height: 1.5em
}

.product-bundle-author-bio p:last-child {
	margin-bottom: 0px
}

.product-additional-info {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%;
	border-color: #ccc
}

.foreword-byline {
	color: #998f52
}

.q {
	display: inline;
	float: left;
	width: 200px;
	margin: 0px 6px 0px 0px;
	border-radius: 2px;
	border: solid 1px #ccc;
	padding: 5px 4px;
	font-size: .8em;
	color: #888
}

#q-search-form {
	display: inline-block;
	margin-bottom: 30px
}

#newsletter {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%;
	padding: 20px 0px 16px 0px;
	margin-bottom: 30px
}

#newsletter h3 {
	margin-bottom: 6px
}

.newsletter-note {
	margin-left: 0px !important
}

.newsletter-message {
	font-size: .825em;
	line-height: 1.5em
}

#mc_embed_signup {
	margin-right: 0px !important
}

#mc-embedded-subscribe-form {
	margin-top: 15px
}

#mc-embedded-subscribe, #q-search {
	display: inline;
	float: right;
	margin-top: 0px
}

.mc-field-group {
	display: inline;
	float: right
}

#mce-EMAIL, .q {
	display: inline;
	float: left;
	width: 200px;
	margin: 0px 6px 0px 0px;
	border-radius: 2px;
	border: solid 1px #ccc;
	padding: 5px 4px;
	font-size: .8em;
	color: #888
}

#mce-responses .response {
	display: inline;
	float: right;
	border-radius: 2px;
	width: 96%;
	padding: 8px;
	margin-top: 10px !important;
	font-style: italic;
	font-size: .75em;
	line-height: 1.25em;
	color: #fff
}

#mce-responses #mce-error-response {
	background-color: #dd4b39
}

#mce-responses #mce-success-response {
	background-color: #7db72f
}

#mce-responses a:link, #mce-responses a:visited {
	color: #fff
}

#mce-responses a:hover, #mce-responses a:active {
	opacity: .8
}

footer {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%;
	padding-bottom: 50px;
	color: #888
}

.footer-links {
	zoom: 1;
	display: block;
	width: auto;
	margin-left: 0px;
	margin-right: 0px;
	margin-left: 0px !important;
	font-size: .6875em;
	line-height: 2em
}

.footer-links:before, .footer-links:after {
	content: "";
	display: table
}

.footer-links:after {
	clear: both
}

.footer-links li {
	display: block
}

.footer-links li:before {
	display: none
}

.footer-partner {
	display: inline;
	float: right;
	margin-top: 5px
}

.footer-partner-arcustech {
	clear: right;
	margin-top: 20px
}

.footer-partner-byline {
	display: inline;
	float: left;
	display: block;
	margin-top: 2px;
	font-size: .6875em
}

.footer-partner-logo {
	-webkit-transition: opacity .25s;
	transition: opacity .25s;
	filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70);
	opacity: .7;
	display: inline;
	float: left;
	display: block;
	width: 38px;
	height: 38px;
	margin: -10px 0px 0px 5px;
	text-indent: -9999px
}

.footer-partner-logo:hover {
	filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
	opacity: 1
}

#home .other-books {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

#home .other-books .books {
	zoom: 1;
	display: block;
	width: auto;
	margin-left: 0px;
	margin-right: 0px
}

#home .other-books .books:before, #home .other-books .books:after {
	content: "";
	display: table
}

#home .other-books .books:after {
	clear: both
}

#home .other-books .books li {
	zoom: 1;
	display: block;
	width: auto;
	margin-left: 0px;
	margin-right: 0px;
	margin-bottom: 30px
}

#home .other-books .books li:before, #home .other-books .books li:after
	{
	content: "";
	display: table
}

#home .other-books .books li:after {
	clear: both
}

#home .press-room {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

#home .press-room .press {
	zoom: 1;
	display: block;
	width: auto;
	margin-left: 0px;
	margin-right: 0px;
	margin-bottom: -30px
}

#home .press-room .press:before, #home .press-room .press:after {
	content: "";
	display: table
}

#home .press-room .press:after {
	clear: both
}

#home .press-room .press li {
	zoom: 1;
	display: block;
	width: auto;
	margin-left: 0px;
	margin-right: 0px;
	margin-bottom: 30px
}

#home .press-room .press li:before, #home .press-room .press li:after {
	content: "";
	display: table
}

#home .press-room .press li:after {
	clear: both
}

#products {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

#products .books {
	margin-bottom: 30px
}

#products .books:last-child {
	margin-bottom: 0px
}

#products .book-summary {
	padding-left: 112px
}

#products .books .bundle {
	width: 450px;
	margin-left: 10px;
	margin-right: 10px
}

#products .books .bundle .book-thumb {
	width: 150px
}

#products .books .single {
	width: 300px;
	margin-left: 10px;
	margin-right: 10px
}

#cart-main {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

#press-room .press-list li, #press-room #pagination {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

#press {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

#help {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

#help .callout {
	position: relative
}

#help .callout p {
	background-position: 0px 2px;
	padding-left: 24px
}

#help .callout a {
	font-weight: bold
}

#faqs {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

#faqs h4 {
	margin-bottom: 4px;
	font-weight: bold;
	font-size: 1em;
	line-height: 1.25em
}

#faqs p {
	margin-bottom: 1.5em;
	font-size: 1em;
	line-height: 1.5em
}

#about, #basic {
	display: inline;
	float: left;
	width: 93.75%;
	margin-left: 3.125%;
	margin-right: 3.125%
}

.about-people {
	margin-top: 20px
}

.about-person-info {
	max-height: 48px
}

.about-person-signature {
	margin-bottom: 10px;
	height: 72px
}

.about-person-avatar {
	display: inline;
	float: left;
	margin-right: 10px;
	width: 48px
}

.about-person-name {
	display: block;
	font-size: .875em;
	line-height: 1.25em
}

.about-person-title {
	font-size: .75em;
	font-style: italic;
	color: #998f52
}

.about-main-people, .about-additional-people {
	zoom: 1;
	margin-bottom: -30px
}

.about-main-people:before, .about-main-people:after,
	.about-additional-people:before, .about-additional-people:after {
	content: "";
	display: table
}

.about-main-people:after, .about-additional-people:after {
	clear: both
}

.about-main-people li, .about-additional-people li {
	margin-bottom: 30px
}

.about-text p, .basic-text p {
	margin-bottom: 16px;
	font-size: 1.125em;
	line-height: 1.5em
}

#cart-form {
	margin-top: 30px
}

#cart-head #head-options span {
	display: none
}

#cart-table {
	width: 100%
}

#cart-table th, #cart-table tr {
	padding: 10px 0px
}

#cart-table td {
	padding: 0px 30px 0px 0px
}

#cart-table td.cart-item-options {
	padding: 0px
}

#cart-body th {
	padding-right: 30px
}

#cart-body th, #cart-body td {
	font-size: .75em;
	line-height: 1.5em
}

.cart-item-title {
	font-weight: bold
}

.cart-item-quantity {
	text-align: right
}

.cart-item-remove-option {
	display: block;
	width: 14px;
	height: 16px;
	text-indent: -9999px
}

.cart-item-remove-option span {
	display: none
}

.cart-total, .cart-options {
	text-align: right
}

.cart-total-label {
	color: #666
}

.cart-total-price {
	font-weight: bold;
	color: #997e11
}

.cart-checkout-button {
	margin-left: 5px
}

button[disabled], button[disabled]:hover, button[disabled]:focus {
	cursor: progress;
	opacity: .5
}

button[disabled]:before {
	background:
		url(//cdn.shopify.com/s/files/1/0051/7692/t/2/assets/loading_12x12_green.gif?8276597854392778624)
		no-repeat 50% 50%;
	content: '';
	display: inline-block;
	height: 12px;
	margin-right: 6px;
	position: relative;
	width: 12px
}

button[disabled].is-disabled, button[disabled].is-disabled:hover, button[disabled].is-disabled:focus
	{
	cursor: not-allowed
}

button[disabled].is-disabled:before {
	content: none
}

.cart-update-button {
	display: none
}

.cart-update-button.is-next-action {
	display: inline-block
}

#cart .other-books h3 {
	font-size: 1.5em;
	letter-spacing: 1px;
	font-weight: 300;
	margin-bottom: 20px
}

#cart .books li {
	margin-bottom: 40px
}

.savings {
	background: #f6f5ea;
	border-radius: 4px;
	padding: 15px;
	min-height: 154px;
	min-width: 300px
}

.savings-table {
	float: left;
	width: 125px;
	margin-right: 10px
}

.savings-table tr {
	border-top: 1px #ccc solid
}

.savings-table .savings-qty-hed {
	padding: 0 14px 4px 8px
}

.savings-table .savings-discount-hed {
	padding: 0 8px 4px 8px
}

.savings-qty {
	padding: 8px 14px 8px 8px
}

.savings-discount {
	padding: 8px
}

.savings-table thead tr {
	border-top: none
}

.savings-qty, .savings-discount {
	color: #666;
	font-size: 16px;
	letter-spacing: 1px
}

.savings-discount span {
	color: #999;
	letter-spacing: 0
}

.savings-msg {
	float: right;
	width: 110px;
	height: 110px;
	text-align: center;
	text-transform: uppercase;
	letter-spacing: 1px;
	color: #fff;
	margin-top: 5px;
	margin-right: 8px
}

.savings-msg span {
	width: 88px;
	margin: 23px auto 0;
	display: block;
	line-height: 1.3em
}

@media only screen and (max-width: 480px) {
	.logo {
		width: 100%;
		height: 24px;
		background-size: contain
	}
	.logo-tagline {
		font-size: .6em
	}
	#mce-EMAIL, .q {
		width: 100%
	}
	.mc-field-group {
		float: none;
		display: block
	}
	#mc-embedded-subscribe, #q-search {
		margin-top: 8px
	}
	#nav-home {
		display: none
	}
	aside .book-thumb {
		margin: 0px;
		float: none
	}
	#cart-table table, #cart-table thead, #cart-table tbody, #cart-table tfoot,
		caption, #cart-table th, #cart-table td, #cart-table tr {
		display: block;
		-webkit-text-size-adjust: none
	}
	#cart-table thead {
		border: 0;
		clip: rect(0, 0, 0, 0);
		height: 1px;
		margin: -1px;
		overflow: hidden;
		padding: 0;
		position: absolute;
		width: 1px
	}
	#cart-table tr {
		overflow: hidden
	}
	#cart-table td {
		text-align: right;
		margin: 0px 0px 4px 0px;
		padding: 0px
	}
	#cart-table td:nth-child(2):before {
		content: "Price: ";
		font-weight: bold
	}
	#cart-table td:nth-child(3):before {
		content: "Qty: ";
		font-weight: bold
	}
	#cart-table td:nth-child(4):before {
		content: "Total: ";
		font-weight: bold
	}
	#cart-body tr {
		position: relative;
		padding: 0px 0px 20px 0px
	}
	#cart-body th {
		position: absolute;
		top: 0px;
		left: 0px;
		width: 70%;
		padding: 0px
	}
	.cart-item-remove-option {
		display: inline;
		float: right
	}
	.wf-active h2.title {
		font-size: 3em
	}
	.wf-active .product-byline .byline-prefix, .wf-active .product-byline .byline-name,
		.wf-active .product-byline .byline-name i {
		font-size: 1em
	}
	.product-option-icon {
		display: none
	}
	.product-option-question-icon {
		display: none
	}
	.product-option-paperback .product-option-info, .product-option-ebook .product-option-info,
		.product-option-gift .product-option-info, .product-option-bundle .product-option-info
		{
		padding-left: 0px
	}
	.offer-form {
		zoom: 1
	}
	.offer-form:before, .offer-form:after {
		content: "";
		display: table
	}
	.offer-form:after {
		clear: both
	}
	.offer-dropdown {
		width: 100%
	}
	.offer-add-button {
		float: right;
		margin-top: 10px
	}
	#home .feature-small-cover {
		display: block;
		width: 100%
	}
	.feature-small-cover .book-thumb {
		margin: 25px auto 0
	}
	#home .feature-header {
		text-align: center
	}
	#home .foreword-byline {
		text-align: center
	}
	#home .press-room .links li:before {
		display: none
	}
	#home .press-room .links-rss {
		display: none
	}
	#products .books .single, #products .books .bundle {
		zoom: 1;
		display: block;
		width: auto;
		margin-left: 0px;
		margin-right: 0px;
		margin-bottom: 30px
	}
	#products .books li:before, #products .books li:after {
		content: "";
		display: table
	}
	#products .books li:after {
		clear: both
	}
	#products .books li:last-child {
		margin-bottom: 0px
	}
	.about-people li {
		display: inline;
		float: left;
		width: 93.75%;
		margin-left: 3.125%;
		margin-right: 3.125%
	}
	.footer-links {
		text-align: center
	}
	.footer-partner {
		zoom: 1;
		display: block;
		width: auto;
		margin-left: 0px;
		margin-right: 0px;
		margin-top: 24px;
		float: none;
		padding-left: 33%
	}
	.footer-partner:before, .footer-partner:after {
		content: "";
		display: table
	}
	.footer-partner:after {
		clear: both
	}
}

@media only screen and (max-width: 768px) {
	.product-options-list {
		zoom: 1;
		display: block;
		width: auto;
		margin-left: 0px;
		margin-right: 0px;
		float: none
	}
	.product-options-list:before, .product-options-list:after {
		content: "";
		display: table
	}
	.product-options-list:after {
		clear: both
	}
	.product-option-question-icon {
		display: none
	}
	.product-options-list li {
		zoom: 1;
		display: block;
		width: auto;
		margin-left: 0px;
		margin-right: 0px;
		margin-bottom: 10px;
		float: none
	}
	.product-options-list li:before, .product-options-list li:after {
		content: "";
		display: table
	}
	.product-options-list li:after {
		clear: both
	}
	.product-options-list li button {
		border-radius: 0 5px 5px 0;
		position: absolute;
		top: 0px !important;
		right: 0px !important;
		height: 100% !important;
		margin: 0px !important;
		padding: 17px !important;
		font-size: 1.25em !important;
		border: none
	}
	.product-bundle-offer-books {
		width: 100%;
		text-align: center
	}
	.product-foreword p, .product-desc-gift p, .product-bundle-summary p {
		margin-bottom: 16px;
		font-size: 1.125em;
		line-height: 1.5em
	}
	.product-contents, .product-tk-thumb, .product-gift-thumb {
		padding-top: 30px;
		margin-top: 30px;
		border-top-style: solid;
		border-top-width: 1px
	}
	.product-additional-info {
		padding-top: 30px;
		margin-top: 30px;
		border-top-style: solid;
		border-top-width: 1px
	}
	.center-books {
		width: 240px;
		height: 123px;
		margin: 0 auto 20px
	}
	.product-bundle-offer-book {
		min-height: 123px
	}
	.product-bundle-offer-book.left-book {
		padding-right: 80px
	}
	.right-book-info {
		display: block;
		clear: both;
		padding: 0 30px
	}
}

@media only screen and (min-width: 481px) and (max-width: 768px) {
	.newsletter-note {
		zoom: 1;
		display: block;
		width: auto;
		margin-right: 0px;
		float: none
	}
	.newsletter-note:before, .newsletter-note:after {
		content: "";
		display: table
	}
	.newsletter-note:after {
		clear: both
	}
	#mc_embed_signup {
		zoom: 1;
		display: block;
		width: auto;
		margin-left: 0px;
		float: none
	}
	#mc_embed_signup:before, #mc_embed_signup:after {
		content: "";
		display: table
	}
	#mc_embed_signup:after {
		clear: both
	}
	#mc-embedded-subscribe-form {
		margin-top: 15px
	}
	#mce-responses .response {
		width: 100%
	}
	#product .other-books .books, #cart .other-books .books {
		width: 106.25%;
		margin-left: -3.125%;
		margin-right: -3.125%;
		zoom: 1
	}
	#product .other-books .books li, #cart .other-books .books li {
		display: inline;
		float: left;
		width: 43.75%;
		margin-left: 3.125%;
		margin-right: 3.125%
	}
	#product .other-books .books li:last-child {
		display: none
	}
	.product-options-list li {
		position: relative
	}
	.product-option-question-icon {
		display: none
	}
	.offer-form {
		display: inline;
		float: left;
		position: relative;
		left: 50%
	}
	.offer-dropdown, .offer-add-button {
		display: inline;
		float: left;
		position: relative;
		right: 50%
	}
	.product-author-headshot {
		display: inline;
		float: left;
		width: 18.75%;
		margin-left: 3.125%;
		margin-right: 0px
	}
	.product-author-bio {
		display: inline;
		float: left;
		width: 68.75%;
		margin-left: 3.125%;
		margin-right: 3.125%
	}
	.product-bundle-author-list li {
		zoom: 1
	}
	.product-bundle-author-list li:before, .product-bundle-author-list li:after
		{
		content: "";
		display: table
	}
	.product-bundle-author-list li:after {
		clear: both
	}
	.product-bundle-author-headshot {
		display: inline;
		float: left;
		width: 18.75%;
		margin-left: 0px;
		margin-right: 0px
	}
	.product-bundle-author-bio {
		display: inline;
		float: left;
		width: 68.75%;
		margin-left: 3.125%;
		margin-right: 3.125%
	}
	#home .feature-small-cover {
		display: inline;
		position: absolute
	}
	#home .feature-info {
		margin-left: 125px
	}
	#home .feature-header {
		display: inline
	}
	#home .other-books .books {
		width: 106.25%;
		margin-left: -3.125%;
		margin-right: -3.125%
	}
	#home .other-books .books li {
		display: inline;
		float: left;
		width: 43.75%;
		margin-left: 3.125%;
		margin-right: 3.125%
	}
	#products .books {
		display: block;
		width: 106.25%;
		margin-left: -3.125%;
		margin-right: -3.125%;
		zoom: 1
	}
	#products .books:before, #products .books:after {
		content: "";
		display: table
	}
	#products .books:after {
		clear: both
	}
	#products .books .single {
		display: inline;
		float: left;
		width: 43.75%;
		margin-left: 3.125%;
		margin-right: 3.125%
	}
	#products .books .bundle {
		display: inline;
		float: left;
		width: 87.5%;
		margin-left: 3.125%;
		margin-right: 3.125%
	}
	.about-people li {
		display: inline;
		float: left;
		width: 43.75%;
		margin-left: 3.125%;
		margin-right: 3.125%
	}
	.footer-links {
		display: inline;
		float: left;
		width: 68.75%;
		margin-left: 3.125%;
		margin-right: 3.125%
	}
}

@media only screen and (min-width: 769px) and (max-width: 1024px) {
	#newsletter {
		width: 620px
	}
	.newsletter-note {
		width: 300px
	}
	.primary-nav {
		width: 620px;
		margin-left: 10px;
		margin-right: 10px
	}
	#main {
		width: 640px;
		margin: 0px auto
	}
	#cart-table table, #cart-table thead, #cart-table tbody, #cart-table tfoot,
		caption, #cart-table th, #cart-table td, #cart-table tr {
		display: block;
		-webkit-text-size-adjust: none
	}
	#cart-table thead {
		border: 0;
		clip: rect(0, 0, 0, 0);
		height: 1px;
		margin: -1px;
		overflow: hidden;
		padding: 0;
		position: absolute;
		width: 1px
	}
	#cart-table tr {
		overflow: hidden
	}
	#cart-table td {
		text-align: right;
		margin: 0px 0px 4px 0px;
		padding: 0px
	}
	#cart-table td:nth-child(2):before {
		content: "Price: ";
		font-weight: bold
	}
	#cart-table td:nth-child(3):before {
		content: "Qty: ";
		font-weight: bold
	}
	#cart-table td:nth-child(4):before {
		content: "Total: ";
		font-weight: bold
	}
	#cart-body tr {
		position: relative;
		padding: 0px 0px 20px 0px
	}
	#cart-body th {
		position: absolute;
		top: 0px;
		left: 0px;
		width: 280px;
		padding: 0px
	}
	.cart-item-remove-option {
		display: inline;
		float: right
	}
	#product .other-books, #cart .other-books {
		width: 620px
	}
	#product .other-books .books {
		width: 660px
	}
	#product .other-books .books li:last-child {
		display: none
	}
	.product-byline {
		width: 620px;
		font-size: .875em
	}
	.hero-image {
		width: 620px
	}
	.product-options {
		width: 620px
	}
	.product-options-list {
		width: 660px
	}
	.product-options-list li {
		margin-bottom: 20px
	}
	.product-content, .product-bundle-offer {
		width: 620px
	}
	.product-bundle-offer-book {
		width: 260px
	}
	.center-books {
		margin-left: 60px
	}
	.offer-form {
		display: inline;
		float: left;
		position: relative;
		left: 50%
	}
	.offer-dropdown, .offer-add-button {
		display: inline;
		float: left;
		position: relative;
		right: 50%
	}
	.product-foreword, .product-bundle-summary {
		width: 380px
	}
	.product-desc-gift {
		width: 380px
	}
	.product-foreword-byline {
		font-size: .875em
	}
	.product-contents, .product-tk-thumb, .product-gift-thumb {
		width: 220px
	}
	.product-reviews {
		width: 620px
	}
	.product-review-list {
		width: 660px
	}
	.product-review-list-2 li, .product-review-list-3 li,
		.product-review-list-4 li {
		width: 300px
	}
	.product-bundle-author-list li {
		zoom: 1
	}
	.product-bundle-author-list li:before, .product-bundle-author-list li:after
		{
		content: "";
		display: table
	}
	.product-bundle-author-list li:after {
		clear: both
	}
	.product-bundle-authors-info {
		width: 620px
	}
	.product-bundle-author-bio {
		width: 460px
	}
	.product-additional-info {
		width: 620px;
		padding-top: 30px;
		margin-top: 30px;
		border-top-style: solid;
		border-top-width: 1px
	}
	#home .feature {
		width: 620px
	}
	#home .feature-small-cover {
		display: inline;
		position: absolute
	}
	#home .feature-info {
		margin-left: 125px
	}
	#home .feature-header {
		display: inline
	}
	#home .other-books {
		width: 620px
	}
	#home .other-books .books {
		width: 660px
	}
	#home .press-room {
		width: 620px
	}
	#home .press-room .press {
		width: 660px
	}
	#home .press-room .press li {
		width: 300px
	}
	.content h2.title {
		width: 620px
	}
	.content-with-sidebar, .content-with-sidebar h2.title {
		width: 380px
	}
	#products {
		width: 620px
	}
	#products .books .bundle {
		width: 620px
	}
	#cart-main, #cart .other-books {
		width: 380px
	}
	#press-room .press-list li, #press-room #pagination {
		width: 380px
	}
	#press {
		width: 380px
	}
	#about, #basic {
		width: 380px
	}
	.about-people li {
		width: 180px
	}
	#help, #faqs {
		width: 380px
	}
	footer {
		width: 620px
	}
	.footer-links {
		width: 380px
	}
	.footer-links li:nth-child(3):before {
		display: none
	}
}

@media only screen and (min-width: 769px) {
	.newsletter-note {
		display: inline;
		float: left
	}
	#mc_embed_signup {
		display: inline;
		float: left;
		width: 300px
	}
	#mc-embedded-subscribe-form {
		margin-top: 23px
	}
	h1 {
		width: 244px
	}
	.shopping-cart {
		right: 10px
	}
	.callout-for-touch {
		display: none
	}
	.right-book-info {
		padding-left: 320px
	}
	.product-option-add-button {
		top: 16px;
		padding: 6px;
		font-size: .625em
	}
	#product .other-books .books li {
		display: inline;
		float: left;
		width: 300px
	}
	#cart .other-books .books li {
		display: inline;
		float: left;
		width: 380px
	}
	.product-options-list {
		display: block;
		zoom: 1
	}
	.product-options-list:before, .product-options-list:after {
		content: "";
		display: table
	}
	.product-options-list:after {
		clear: both
	}
	.product-options-list li {
		display: inline;
		float: left;
		width: 300px
	}
	.product-bundle-contents .product-contents:not (:first-child ){
		padding-top: 30px;
		margin-top: 30px;
		border-top-style: solid;
		border-top-width: 1px
	}
	.product-review-list {
		display: block;
		zoom: 1
	}
	.product-review-list:before, .product-review-list:after {
		content: "";
		display: table
	}
	.product-review-list:after {
		clear: both
	}
	.product-review-list-2 li, .product-review-list-3 li,
		.product-review-list-4 li {
		display: inline;
		float: left
	}
	.product-author-info, #product-tk .product-author-info {
		width: 620px
	}
	.product-author-headshot {
		display: inline;
		float: left;
		width: 140px
	}
	.product-author-bio {
		display: inline;
		float: left;
		width: 460px;
		margin-left: 10px;
		margin-right: 0px
	}
	.product-bundle-author-headshot {
		display: inline;
		float: left;
		width: 140px;
		margin-right: 10px;
		margin-left: 0px
	}
	.product-bundle-author-bio {
		display: inline;
		float: left;
		margin-right: -1px;
		margin-left: 10px
	}
	#home .feature {
		display: inline;
		float: left
	}
	#home .other-books .books li {
		display: inline;
		float: left;
		width: 300px
	}
	#home .press-room .press li {
		display: inline;
		float: left
	}
	.content-with-sidebar {
		display: inline;
		float: left
	}
	aside {
		display: inline;
		width: 220px;
		float: right
	}
	aside .book-summary {
		clear: both;
		padding-top: 10px
	}
	#products .books {
		display: block;
		zoom: 1
	}
	#products .books:before, #products .books:after {
		content: "";
		display: table
	}
	#products .books:after {
		clear: both
	}
	#products .books li {
		display: inline;
		float: left
	}
	.about-people li {
		display: inline;
		float: left
	}
	.about-text p, .basic-text p {
		font-size: 1em
	}
	.footer-links {
		display: inline;
		float: left
	}
	.footer-links li, .footer-links li:before {
		display: inline
	}
	#newsletter, .newsletter-note, #mc_embed_signup, h1, #product .other-books,
		#product .other-books .books li, #cart .other-books, #cart .other-books .books li,
		.product-byline, .hero-image, .product-options, .product-options-list li,
		.product-bundle-offer, .product-content, .product-bundle-summary,
		.product-reviews, .product-review-list-2 li, .product-review-list-3 li,
		.product-review-list-4 li, .product-author-info, #product-tk .product-author-info,
		.product-author-headshot, .product-bundle-authors-info,
		.product-additional-info, #home .feature, #home .other-books, #home .other-books .books li,
		#home .press-room, #home .press-room .press li, .content h2.title,
		.content-with-sidebar, .content-with-sidebar h2.title, aside,
		#products, #products .books li, #cart-main, #press-room .press-list li,
		#press-room #pagination, #press, #about, #basic, .about-people li,
		#help, #faqs, footer, .footer-links {
		margin-left: 10px;
		margin-right: 10px
	}
	.product-foreword, .product-desc-gift {
		margin-left: 0;
		margin-right: 10px
	}
	.product-contents, .product-tk-thumb, .product-gift-thumb {
		margin-left: 10px;
		margin-right: 0
	}
	#product .other-books .books, #cart .other-books .books,
		.product-options-list, .product-review-list, #home .other-books .books,
		#home .press-room .press, #products .books {
		margin-left: -10px;
		margin-right: -10px
	}
}

@media only screen and (min-width: 1025px) {
	#newsletter {
		width: 940px
	}
	.newsletter-note {
		width: 620px
	}
	.primary-nav {
		width: 460px;
		margin-left: 34px;
		margin-right: 10px;
		margin-top: 0px
	}
	.primary-nav li {
		margin-top: 0px;
		margin-right: 10px
	}
	.primary-nav li a,form {
		border-radius: 0px;
		padding: 34px 8px 3px 8px;
		border-width: 0 0 1px
	}
	#main {
		width: 960px;
		margin: 0px auto
	}
	#product .other-books {
		width: 940px
	}
	#cart .other-books {
		width: 700px
	}
	#product .other-books .books {
		width: 980px
	}
	#cart .other-books .books {
		width: 720px
	}
	#cart .other-books .books li {
		width: 340px
	}
	.product-byline {
		width: 940px
	}
	.hero-image {
		width: 940px
	}
	.product-options {
		width: 940px
	}
	h2.offer-message {
		width: 620px
	}
	h3.offer-message {
		width: 300px;
		float: right;
		margin-top: -26px
	}
	.right-book-info {
		padding-left: 255px
	}
	.product-content, .product-bundle-offer {
		width: 940px
	}
	.product-bundle-offer-books {
		display: inline;
		float: left
	}
	.offer {
		display: inline;
		float: right;
		border-left: solid 5px #eee;
		padding: 0px 0px 15px 30px;
		margin-top: 0px
	}
	.offer-message {
		text-align: left
	}
	.offer-form {
		margin-top: 10px
	}
	.product-foreword, .product-bundle-summary {
		width: 620px
	}
	.product-desc-gift {
		width: 500px
	}
	.product-foreword p, .product-desc-gift p, .product-bundle-summary p {
		font-size: 1.5em
	}
	.product-foreword-byline {
		font-size: 1em
	}
	#product-tk .product-author-info {
		width: 940px;
		border-right: none
	}
	.product-contents, .product-tk-thumb {
		width: 300px
	}
	.product-gift-thumb {
		width: 420px
	}
	.product-tk-thumb .book-thumb-tk {
		width: 300px;
		height: 463px
	}
	.product-gift-thumb .gift-thumb {
		width: 420px;
		height: 250px
	}
	.ieold .product-tk-thumb .book-thumb-tk {
		width: 100px;
		height: 154px
	}
	.product-reviews {
		width: 940px
	}
	.product-review-list {
		width: 980px
	}
	.product-review-list-2 li {
		width: 460px
	}
	.product-review-list-3 li {
		width: 300px
	}
	.product-review-list-4 li {
		width: 220px
	}
	.product-author-info {
		border-right: solid 1px #ccc
	}
	.product-author-bio {
		padding-right: 20px;
		margin-right: -1px
	}
	.product-bundle-authors-info {
		width: 940px
	}
	.product-bundle-author-list {
		display: block;
		width: 980px;
		margin-left: -10px;
		margin-right: -10px
	}
	.product-bundle-author-list li {
		display: inline;
		float: left;
		width: 460px;
		margin-left: 10px;
		margin-right: 10px
	}
	.product-bundle-author-bio {
		width: 300px;
		margin-right: 0px;
		margin-bottom: 0px;
		padding-right: 20px
	}
	.product-additional-info {
		width: 300px
	}
	.product-bundle-author-list-library {
		width: 620px
	}
	.product-bundle-author-list-library li {
		width: 620px
	}
	.product-bundle-author-list-library .product-bundle-author-bio {
		width: 460px
	}
	.product-bundle-offer .product-options-list li {
		background-color: transparent;
		border-radius: 0;
		margin-left: 0
	}
	.product-bundle-offer .product-options-list {
		width: 300px;
		float: right;
		margin-left: 0;
		margin-right: 0;
		background-color: #f6f5ea;
		border-radius: 5px
	}
	#home .subsection {
		border-top-width: 5px
	}
	#home .feature {
		width: 940px
	}
	#home .feature-big-cover {
		width: 430px;
		background-position: 0px 0px
	}
	.feature-author {
		padding-top: 25px
	}
	#home .feature-small-cover {
		display: none
	}
	#home .feature-info {
		display: inline;
		width: 480px;
		margin-left: 10px;
		margin-right: 10px;
		float: right;
		padding: 0px 25px 0px 0px
	}
	#home .other-books {
		width: 940px
	}
	#home .other-books .books {
		width: 980px
	}
	#home .press-room {
		width: 940px
	}
	#home .press-room .press {
		width: 980px
	}
	#home .press-room .press li {
		width: 460px
	}
	.content h2.title {
		width: 940px
	}
	.content-with-sidebar, .content-with-sidebar h2.title {
		width: 700px
	}
	#products {
		width: 940px
	}
	#products .books {
		width: 980px
	}
	#products .books li {
		width: 300px
	}
	#cart-main {
		width: 700px
	}
	.savings {
		margin-right: 50px !important;
		width: 300px !important
	}
	#press-room .press-list li, #press-room #pagination {
		width: 700px
	}
	#press {
		width: 700px
	}
	#about, #basic {
		width: 700px
	}
	.about-people {
		margin-left: -10px !important;
		margin-right: -10px !important
	}
	.about-people li {
		width: 220px
	}
	#help, #faqs {
		width: 700px
	}
	footer {
		width: 940px
	}
	.footer-links {
		width: 580px;
		line-height: 1.85em
	}
	.footer-partner {
		margin-top: 4px
	}
	.footer-partner-arcustech {
		margin-right: 40px;
		clear: none
	}
	.press-title {
		margin-left: 100px
	}
	.press-thumb {
		position: absolute;
		left: 0px;
		top: 0px;
		margin: 0;
		height: auto
	}
	.press-article h3, .press-date, .press p {
		margin-left: 100px
	}
}

#announcement {
	width: 100%;
	background: #291700;
	padding: .8em 0 .8em;
	font-size: .9em;
	color: #fff;
	text-align: center;
	border-bottom: solid 5px #82bc00
}

#announcement a {
	color: #fff
}

#announcement a:hover {
	opacity: .7
}

@media only screen and (max-width: 480px) {
	#announcement {
		font-size: .75em
	}
}

.library-collection .hero-image {
	margin-left: 0;
	margin-right: 0;
	width: 100%
}

.library-bar {
	float: right;
	width: 100%;
	background: #f6f5ea;
	box-shadow: inset 0px 0px 80px rgba(0, 0, 0, 0.05);
	border-radius: 5px;
	margin-top: -46px;
	padding: 12px 10px 10px;
	margin-bottom: 20px;
	position: relative
}

.library-collection:before, .library-collection:after {
	content: "";
	display: table
}

.library-collection:after {
	clear: both
}

.subsection .library-bar h3 {
	font-weight: 400;
	letter-spacing: 1px;
	line-height: 1;
	color: #333;
	margin-bottom: 10px;
	margin-right: 0;
	font-size: 1em
}

.subsection .library-bar p {
	margin-bottom: 45px;
	color: #333;
	font-size: .9375em;
	line-height: 1.45em;
	margin-right: 0
}

@media only screen and (min-width: 480px) {
	.library-bar {
		margin-bottom: 0
	}
	.subsection .library-bar h3 {
		font-size: 1.2em;
		margin-right: 150px
	}
	.subsection .library-bar p {
		margin-bottom: 10px;
		margin-right: 150px
	}
	.library-bar .feature-buy-button {
		bottom: initial;
		top: 10px
	}
}

@media only screen and (min-width: 1024px) {
	.library-collection .hero-image {
		width: 620px
	}
	.library-bar {
		position: relative;
		margin-top: 5px;
		padding: 20px;
		width: 300px;
		min-height: 258px
	}
	.subsection .library-bar h3 {
		font-size: 1.8em;
		line-height: 1;
		padding-top: 0;
		float: left;
		margin-right: 0;
		margin-bottom: 20px
	}
	.subsection .library-bar p {
		margin-right: 0
	}
	.library-bar .feature-buy-button {
		bottom: 14px;
		top: initial
	}
}

@media only screen and (max-width: 1024px) {
	.press ul, .press ol {
		margin-left: 30px
	}
}

.button {
	background-color: #7db72f;
	background-image: -webkit-linear-gradient(#7db72f, #4e7d0e);
	background-image: linear-gradient(#7db72f, #4e7d0e);
	border: solid 1px #64991e;
	border-radius: 3px;
	color: #fff;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
	cursor: pointer;
	font-size: .625em;
	font-style: normal;
	font-weight: 500;
	letter-spacing: .1em;
	outline: none;
	margin: 0px;
	padding: 6px;
	text-align: center;
	text-decoration: none;
	text-transform: uppercase;
	vertical-align: baseline;
	zoom: 1
}

.button:hover {
	color: #fff;
	background: #538018
}

.wf-active .button {
	padding: 5px 8px;
	font-size: .75em
}

.button--large {
	border: solid 2px #fff;
	display: block;
	font-size: 1.5em;
	letter-spacing: .05em;
	padding: 8px 10px 5px
}

.wf-active .button--large {
	font-weight: 400;
	font-size: 3em;
	padding: 8px 10px 5px
}

.button--alt {
	background-color: #fbfbf6;
	background-image: -webkit-linear-gradient(#fbfbf6, #e8e5d4);
	background-image: linear-gradient(#fbfbf6, #e8e5d4);
	border: solid 1px #d1cdba;
	color: #1e90de
}

.button--alt:hover {
	color: #1e90de;
	background: #e8e5d4
}

.alert {
	background-color: #f7f7f7;
	color: #666;
	margin-bottom: 10px
}

.alert>* {
	display: inline
}

.alert__title {
	color: #333;
	font-size: .85em;
	font-style: normal;
	font-weight: 500;
	line-height: 1.5;
	margin-right: 5px;
	text-transform: uppercase
}

.alert__title--block {
	display: block
}

.icon-audiobook {
	display: inline-block;
	height: 19px;
	margin-right: 5px;
	margin-bottom: -2px;
	width: 20px
}

#product .alert {
	display: inline;
	float: left;
	margin-right: 3.125%;
	margin-left: 3.125%;
	text-align: center;
	width: 93.75%
}

@media only screen and (min-width: 769px) {
	#product .alert {
		margin-right: 10px;
		margin-left: 10px;
		width: 620px
	}
}

@media only screen and (min-width: 1025px) {
	#product .alert {
		width: 940px
	}
}

.product-options+.alert {
	margin-top: 10px;
	margin-bottom: 0
}

.divider-bottom+.alert {
	margin-top: 0;
	margin-bottom: 10px
}

.alert:last-child {
	margin-bottom: 0
}

#product .product-content .alert {
	margin-right: 0;
	margin-left: 0;
	width: 100%
}

#product .book .alert {
	margin-bottom: 0
}

#cart-main .alert {
	margin-top: 10px
}

.alert--info {
	background-color: #cae6fa;
	color: #333
}

.alert--warning {
	background-color: #ffffa5;
	color: #8b730e
}

.alert--warning .alert__title {
	color: #8b730e
}

.alert--success {
	background-color: #7db72f
}

.alert--error {
	background-color: #dd4b39
}

.alert--success, .alert--error {
	color: #fff
}

.alert--success a, .alert--error a {
	color: inherit;
	-webkit-transition: all .25s;
	transition: all .25s
}

.alert--success a:hover, .alert--success a:focus, .alert--error a:hover,
	.alert--error a:focus {
	opacity: .75
}

.alert--success .alert__title, .alert--error .alert__title {
	color: #fff
}

.alert--em {
	font-style: italic
}

.alert--em em {
	font-style: normal
}

.alert--block>* {
	display: block
}

.alert--small {
	padding: 5px 8px 8px
}

.alert--large {
	padding: 10px
}

.alert--large p {
	font-size: .85em
}

.alert--left {
	text-align: left
}

.book-image{
	width: 100%
}

#product .extra, #collection .extra {
	margin-bottom: 30px
}

.emptyInput{
	color: #babdb6;
}

/static/estilos/index.css

Ejemplo 2

/static/images/Books.png

/static/images/sign.ico

Aspectos de Seguridad

Estándares Soportados

  • OpenID
  • HTTP BASIC
  • HTTP Digest
  • LDAP
  • Form-based authentication
  • Java Authentication and Authorization Service (JAAS)
  • ...

Valores por Defecto

  • Spring-boot-starter-security por defecto maneja la autenticación en memoria y crea un usuario “user”, con una contraseña aleatoria y role USER.
  • Para fijar una contraseña para este usuario podemos usar la propiedad security.user.password.
  • El mecanismo de autenticación por defecto es HTTP Basic.

Configuración

  • La configuración se realiza por medio de la clase WebSecurityConfigurerAdapter
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .antMatchers("/estilos/**").permitAll()
                .antMatchers("/images/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .httpBasic()
                .and()
            .logout()
                .permitAll();
    }
}

Usuarios desde la Base de Datos

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .antMatchers("/estilos/**").permitAll()
                .antMatchers("/images/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .httpBasic()
                .and()
            .logout()
                .permitAll();
            //En la base de datos los roles deben almacenarse como ROLE_ADMIN
            //.antMatchers("/admin/**").hasRole("ADMIN")                                   
            //.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
    }

    @Autowired
    private DataSource dataSource;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth
			.jdbcAuthentication()
				.dataSource(dataSource)
				.usersByUsernameQuery(
                        "select username,password, enabled from users where username=?")
                .authoritiesByUsernameQuery(
                        "select username, authority from authorities where username=?");
    }
}

Usuarios desde la Base de Datos

package co.edu.unicomfacauca.tienda.model;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
@Data
public class Users {
	
	@Id
	private String username;
	private String password;
	private Boolean enabled;
	
	@OneToMany(cascade=CascadeType.ALL)
	private List<Authorities> authorities;

}
package co.edu.unicomfacauca.tienda.repository;

import org.springframework.data.repository.CrudRepository;

public interface UsersRepository extends CrudRepository<Users, Long> {

}

Usuarios desde la Base de Datos

package com.example.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
@Data
public class Authorities {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;
	
	@ManyToOne
	@JoinColumn(name="username")
	private Users users;
	private String authority;
	
}
package com.example.persistency;

import org.springframework.data.repository.CrudRepository;

public interface AuthoritiesRepository extends CrudRepository<Authorities, Long> {

}

Login Page

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example </title>
    </head>
    <body>
        <!-- En caso de error o logout se envía como parámetro un booleano  --> 
        <div th:if="${param.error}">
            Invalid username and password.
        </div>
        <div th:if="${param.logout}">
            You have been logged out.
        </div>
        <form th:action="@{/login}" method="post">
            <div><label> User Name : <input type="text" name="username"/> </label></div>
            <div><label> Password: <input type="password" name="password"/> </label></div>
            <!-- csf es incluido automáticamente e impide que un usuario (casi siempre malvado) repita la petición  -->   
            <div><input type="submit" value="Sign In"/></div>
        </form>
    </body>
</html>

Internacionalización

Internacionalización con Spring Framework

  • Spring implementa una clase llamada MessageSource la cual carga los mensajes definidos en diferentes idiomas
  • Los mensajes deben estar definidos en archivos .properties que definan la localidad. Ej: errors_en.properties, errors_es_CO.properties.
  • Usando spring-boot los mensajes se buscan en los archivos llamados messages, concatenados con la localidad.

resources/messages_es.properties

ready = listo
usersLoaded = Tabla usuarios inicializada
buy = comprar

resources/messages.properties

ready = ready
usersLoaded = Users table initialized
buy = buy

Ejemplos

String getMessage(String code, Object[] args, String default, Locale loc)
 String message = resources.getMessage("message", null, "Default", null);
# in exceptions_en_GB.properties
argument.required=The '{0}' argument is required.
String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", Locale.UK);

Ejemplos

   @RequestMapping("/numLibros")
    @ResponseBody
    public String numeroDeLibros(Locale locale)
    {
    	long numLibros=librosRepository.count();
    	return messages.getMessage("numLibros", new Object [] {""+numLibros}, "my null", locale );
    }
@Configuration
public class WebConfigurer extends WebMvcConfigurerAdapter {
 
	    @Bean
	    public LocaleResolver localeResolver() {
	    	SessionLocaleResolver slr = new SessionLocaleResolver();
	    	slr.setDefaultLocale(Locale.forLanguageTag("es-co"));
	    	return slr;
	    }
	
            //http://localhost:8080/home?lang=en
	    @Bean
	    public LocaleChangeInterceptor localeChangeInterceptor() {
	        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
	        lci.setParamName("lang");
	        return lci;
	    }
	 
	    @Override
	    public void addInterceptors(InterceptorRegistry registry) {
	        registry.addInterceptor(localeChangeInterceptor());
	    }
	 

}

REST en Spring

Generación de Recursos

  • @ResponseBody: Indica que la respuesta no es una vista sino un recurso serializado
  • @RequestBody: Indica que se recibe en el cuerpo HTTP un recurso serializado
  • @RestController: Evita que se tenga que anotar cada método del controlador con @ResponseBody

Generación de Recursos

package com.example.control;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.model.Libro;
import com.example.persistency.LibroRepository;

@RestController
public class ControladorRestLibros {
	
	@Autowired
	private LibroRepository libroRepository;
	
	@RequestMapping(value="/rest/libros",method=RequestMethod.GET, produces="application/json")
	public List<Libro> getLibros()
	{
		return (List<Libro>) libroRepository.findAll();
	}
	
	@RequestMapping(value="/rest/libro/{id}",method=RequestMethod.DELETE)
	public List<Libro> deleteLibros(@PathVariable (value="id") Long id)
	{
		Libro libro=new Libro();
		libro.setId(id);
		libroRepository.delete(libro);
		return (List<Libro>) libroRepository.findAll();
	}
	
	@RequestMapping(value="/rest/libro/{id}",method=RequestMethod.GET)
	public Libro getLibro(@PathVariable (value="id") Long id)
	{
		return libroRepository.findOne(id);
	}
	
	@RequestMapping(value="/rest/libro",method=RequestMethod.PUT)
	public Libro crearLibro(@RequestBody Libro libro)
	{
		return libroRepository.save(libro);
	}

}

Consumo de Recursos

Para el consumo de recuro se hace uso de la clase RestTemplate

Operaciones RestTemplate

Método Descripción
delete() Ejecuta una solicitud DELETE sobre un recurso
exchange() Ejecuta el método HTTP especificado sobre la URL. Devuelve un response entity.
execute() Ejecuta el método HTTP especificado sobre la URL. Devuelve un objeto.
getForObject() Ejecuta una solicitud GET sobre un recurso
optionsForAllow() Ejecuta una solicitud OPTIONS sobre una URL

Operaciones RestTemplate

Método Descripción
postForEntity() Publica un recurso. Devuelve un response entity.
postForLocation() Publica un recurso. Devuelve la URL del nuevo recurso.
postForObject() Publica un recurso. Devuelve el objeto creado.
put() Incluye datos de un recurso mediante la función put

Operaciones RestTemplate

package com.example;

import java.util.Base64;
import java.util.Random;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import com.example.Libro;

@Component
public class ConsumirRest {
	
	@Autowired
	private RestTemplate restTemplate;
	
	
	public void ConsumirRestSinSeguridad()
	{
		
		new Thread("consumirRest" ){
	        public void run(){
	        	try {
					Thread.sleep(6000);
				} catch (InterruptedException e) {}
	    		Libro libro=restTemplate.getForObject("http://localhost:8080/rest/libro/3", Libro.class);
	    		System.out.println(libro);
	    		restTemplate.delete("http://localhost:8080/rest/libro/6");
	    		libro.setAutor("Gustavo123");
	    		libro.setId(0);
	    		restTemplate.putForObject("http://localhost:8080/rest/libro", libro, Libro.class);
	        }
	      }.start();
		
	}
	
	@PostConstruct
	public void ConsumirRestConSeguridad()
	{
		
		new Thread("consumirRestSeguro" ){
	        public void run(){
	        	try {
					Thread.sleep(6000);
				} catch (InterruptedException e) {}
	    		//Definimos las cabeceras HTTP
	        	HttpHeaders requestHeaders = new HttpHeaders();
	    		requestHeaders.set("Content-Type", "text/plain");
	    		//Se fijan las credenciales por medio de las cabeceras HTTP
	    		String basicCredentials = Base64.getEncoder().encodeToString (("gaurgo"+":"+"123").getBytes());
	    		requestHeaders.set("Authorization", "Basic " + basicCredentials);
	    											
	    		HttpEntity<String> httpEntity = new HttpEntity<String>("Body", requestHeaders);
	    		ResponseEntity<Libro> libroResponse = restTemplate.exchange("http://localhost:8080/rest/libro/3", HttpMethod.GET, httpEntity,Libro.class);
	    		System.out.println(libroResponse.getBody());
	    		
	    		//NUEVO POST
	    		Libro libro=libroResponse.getBody();
	    		libro.setAutor("Gustavo12052007");
	    		libro.setId(0);
	    		requestHeaders.set("Content-Type", "application/json");
	    		HttpEntity<Libro> httpEntity2 = new HttpEntity<Libro>(libro, requestHeaders);
	    		restTemplate.exchange("http://localhost:8080/rest/libro", HttpMethod.PUT,httpEntity2, Libro.class);
	        }
	      }.start();
		
	}
	
}
Made with Slides.com