diff --git a/Makefile b/Makefile
index f3dbebe..8bfc6e1 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 DEFINES = /D_WIN32_WINNT=0x500 /DISOLATION_AWARE_ENABLED /D_WIN32_IE=0x500 /DWIN32_LEAN_AND_MEAN /DWIN32
 # Use the multi-threaded static libc because libpng and zlib do; not sure if anything bad
 # happens if those mix, but don't want to risk it.
-CFLAGS  = /W3 /nologo -MT -Iextlib -I..\common\win32 /D_DEBUG /D_CRT_SECURE_NO_WARNINGS /I. /Zi /EHs /O2
+CFLAGS  = /W3 /nologo -MT -Iextlib -I..\common\win32 /D_DEBUG /D_CRT_SECURE_NO_WARNINGS /I. /Zi /EHs  # /O2
 
 HEADERS = ..\common\win32\freeze.h ui.h solvespace.h dsc.h sketch.h expr.h polygon.h srf\surface.h
 
@@ -61,7 +61,7 @@ LIBS = user32.lib gdi32.lib comctl32.lib advapi32.lib shell32.lib opengl32.lib g
 
 all: $(OBJDIR)/solvespace.exe
     @cp $(OBJDIR)/solvespace.exe .
-    solvespace fail.slvs
+    solvespace t8.slvs
 
 clean:
 	rm -f obj/*
diff --git a/draw.cpp b/draw.cpp
index f81b128..ddefaf7 100644
--- a/draw.cpp
+++ b/draw.cpp
@@ -798,11 +798,9 @@ void GraphicsWindow::Selection::Clear(void) {
 void GraphicsWindow::Selection::Draw(void) {
     Vector refp;
     if(entity.v) {
-        glLineWidth(1.5);
         Entity *e = SK.GetEntity(entity);
         e->Draw();
         if(emphasized) refp = e->GetReferencePos();
-        glLineWidth(1);
     }
     if(constraint.v) {
         Constraint *c = SK.GetConstraint(constraint);
@@ -810,13 +808,17 @@ void GraphicsWindow::Selection::Draw(void) {
         if(emphasized) refp = c->GetReferencePos();
     }
     if(emphasized && (constraint.v || entity.v)) {
+        // We want to emphasize this constraint or entity, by drawing a thick
+        // line from the top left corner of the screen to the reference point
+        // of that entity or constraint.
         double s = 0.501/SS.GW.scale;
         Vector topLeft =       SS.GW.projRight.ScaledBy(-SS.GW.width*s);
         topLeft = topLeft.Plus(SS.GW.projUp.ScaledBy(SS.GW.height*s));
         topLeft = topLeft.Minus(SS.GW.offset);
 
         glLineWidth(40);
-        glColor4d(1.0, 1.0, 0, 0.2);
+        DWORD rgb = Style::Color(Style::HOVERED);
+        glColor4d(REDf(rgb), GREENf(rgb), BLUEf(rgb), 0.2);
         glBegin(GL_LINES);
             glxVertex3v(topLeft);
             glxVertex3v(refp);
@@ -1018,8 +1020,9 @@ void GraphicsWindow::Paint(int w, int h) {
     if(SS.AllGroupsOkay()) {
         glClearColor(0, 0, 0, 1.0f);
     } else {
-        // Draw a red background whenever we're having solve problems.
-        glClearColor(0.4f, 0, 0, 1.0f);
+        // Draw a different background whenever we're having solve problems.
+        DWORD rgb = Style::Color(Style::DRAW_ERROR);
+        glClearColor(0.4f*REDf(rgb), 0.4f*GREENf(rgb), 0.4f*BLUEf(rgb), 1.0f);
         // And show the text window, which has info to debug it
         ForceTextWindowShown();
     }
@@ -1085,8 +1088,8 @@ void GraphicsWindow::Paint(int w, int h) {
     }
 
     // Draw the traced path, if one exists
-    glLineWidth(1);
-    glColor3d(0, 1, 1);
+    glLineWidth(Style::Width(Style::ANALYZE));
+    glxColorRGB(Style::Color(Style::ANALYZE));
     SContour *sc = &(SS.traced.path);
     glBegin(GL_LINE_STRIP);
     for(i = 0; i < sc->l.n; i++) {
@@ -1095,21 +1098,23 @@ void GraphicsWindow::Paint(int w, int h) {
     glEnd();
 
     // And the naked edges, if the user did Analyze -> Show Naked Edges.
-    glLineWidth(7);
-    glColor3d(1, 0, 0);
+    glLineWidth(Style::Width(Style::DRAW_ERROR));
+    glxColorRGB(Style::Color(Style::DRAW_ERROR));
     glxDrawEdges(&(SS.nakedEdges), true);
 
     // Then redraw whatever the mouse is hovering over, highlighted.
     glDisable(GL_DEPTH_TEST); 
-    glxLockColorTo(1, 1, 0);
+    glxLockColorTo(Style::Color(Style::HOVERED));
     hover.Draw();
 
     // And finally draw the selection, same mechanism.
-    glxLockColorTo(1, 0, 0);
+    glxLockColorTo(Style::Color(Style::SELECTED));
     for(i = 0; i < MAX_SELECTED; i++) {
         selection[i].Draw();
     }
 
+    glxUnlockColor();
+
     // And finally the toolbar.
     if(SS.showToolbar) {
         ToolbarDraw();
diff --git a/drawconstraint.cpp b/drawconstraint.cpp
index 6eab77e..49b22c0 100644
--- a/drawconstraint.cpp
+++ b/drawconstraint.cpp
@@ -350,7 +350,6 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
     Vector gu = SS.GW.projUp.ScaledBy(1/SS.GW.scale);
     Vector gn = (gr.Cross(gu)).WithMagnitude(1/SS.GW.scale);
 
-    glxColor3d(1, 0.1, 1);
     switch(type) {
         case PT_PT_DISTANCE: {
             Vector ap = SK.GetEntity(ptA)->PointGetNum();
@@ -465,12 +464,25 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
                 break;
             }
 
+            // Let's adjust the color of this constraint to have the same
+            // rough luma as the point color, so that the constraint does not
+            // stand out in an ugly way.
+            DWORD cd = Style::Color(Style::DATUM),
+                  cc = Style::Color(Style::CONSTRAINT);
+            // convert from 8-bit color to a vector
+            Vector vd = Vector::From(REDf(cd), GREENf(cd), BLUEf(cd)),
+                   vc = Vector::From(REDf(cc), GREENf(cc), BLUEf(cc));
+            // and scale the constraint color to have the same magnitude as
+            // the datum color, maybe a bit dimmer
+            vc = vc.WithMagnitude(vd.Magnitude()*0.9);
+            // and set the color to that.
+            glxColorRGB(RGBf(vc.x, vc.y, vc.z));
+
             for(int a = 0; a < 2; a++) {
                 Vector r = SS.GW.projRight.ScaledBy((a+1)/SS.GW.scale);
                 Vector d = SS.GW.projUp.ScaledBy((2-a)/SS.GW.scale);
                 for(int i = 0; i < 2; i++) {
                     Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum();
-                    glxColor3d(0.4, 0, 0.4);
                     glBegin(GL_QUADS);
                         glxVertex3v(p.Plus (r).Plus (d));
                         glxVertex3v(p.Plus (r).Minus(d));
@@ -878,7 +890,10 @@ s:
 void Constraint::Draw(void) {
     dogd.drawing = true;
     dogd.sel = NULL;
-    glLineWidth(1);
+
+    glLineWidth(Style::Width(Style::CONSTRAINT));
+    glxColorRGB(Style::Color(Style::CONSTRAINT));
+
     DrawOrGetDistance(NULL);
 }
 
diff --git a/drawentity.cpp b/drawentity.cpp
index ecf4b88..1153579 100644
--- a/drawentity.cpp
+++ b/drawentity.cpp
@@ -40,7 +40,7 @@ void Entity::DrawAll(void) {
         double s = 3.5/SS.GW.scale;
         Vector r = SS.GW.projRight.ScaledBy(s);
         Vector d = SS.GW.projUp.ScaledBy(s);
-        glxColor3d(0, 0.8, 0);
+        glxColorRGB(Style::Color(Style::DATUM));
         glxDepthRangeOffset(6);
         glBegin(GL_QUADS);
         for(i = 0; i < SK.entity.n; i++) {
@@ -52,6 +52,9 @@ void Entity::DrawAll(void) {
 
             Vector v = e->PointGetNum();
 
+            // If we're analyzing the sketch to show the degrees of freedom,
+            // then we draw big colored squares over the points that are
+            // free to move.
             bool free = false;
             if(e->type == POINT_IN_3D) {
                 Param *px = SK.GetParam(e->param[0]),
@@ -68,12 +71,12 @@ void Entity::DrawAll(void) {
             if(free) {
                 Vector re = r.ScaledBy(2.5), de = d.ScaledBy(2.5);
 
-                glxColor3d(0, 1.0, 1.0);
+                glxColorRGB(Style::Color(Style::ANALYZE));
                 glxVertex3v(v.Plus (re).Plus (de));
                 glxVertex3v(v.Plus (re).Minus(de));
                 glxVertex3v(v.Minus(re).Minus(de));
                 glxVertex3v(v.Minus(re).Plus (de));
-                glxColor3d(0, 0.8, 0);
+                glxColorRGB(Style::Color(Style::DATUM));
             }
 
             glxVertex3v(v.Plus (r).Plus (d));
@@ -85,7 +88,6 @@ void Entity::DrawAll(void) {
         glxDepthRangeOffset(0);
     }
 
-    glLineWidth(1.5);
     for(i = 0; i < SK.entity.n; i++) {
         Entity *e = &(SK.entity.elem[i]);
         if(e->IsPoint())
@@ -94,10 +96,13 @@ void Entity::DrawAll(void) {
         }
         e->Draw();
     }
-    glLineWidth(1);
 }
 
 void Entity::Draw(void) {
+    hStyle hs = Style::ForEntity(h);
+    glLineWidth(Style::Width(hs));
+    glxColorRGB(Style::Color(hs));
+
     dogd.drawing = true;
     DrawOrGetDistance();
 }
@@ -302,14 +307,6 @@ void Entity::DrawOrGetDistance(void) {
 
     Group *g = SK.GetGroup(group);
 
-    if(group.v != SS.GW.activeGroup.v) {
-        glxColor3d(0.5, 0.3, 0.0);
-    } else if(construction) {
-        glxColor3d(0.1, 0.7, 0.1);
-    } else {
-        glxColor3d(1, 1, 1);
-    }
-
     switch(type) {
         case POINT_N_COPY:
         case POINT_N_TRANS:
@@ -325,7 +322,7 @@ void Entity::DrawOrGetDistance(void) {
                 Vector r = SS.GW.projRight.ScaledBy(s/SS.GW.scale);
                 Vector d = SS.GW.projUp.ScaledBy(s/SS.GW.scale);
 
-                glxColor3d(0, 0.8, 0);
+                glxColorRGB(Style::Color(Style::DATUM));
                 glxDepthRangeOffset(6);
                 glBegin(GL_QUADS);
                     glxVertex3v(v.Plus (r).Plus (d));
@@ -356,15 +353,18 @@ void Entity::DrawOrGetDistance(void) {
                 }
 
                 hRequest hr = h.request();
-                double f = (i == 0 ? 0.4 : 1);
+                // Always draw the x, y, and z axes in red, green, and blue;
+                // brighter for the ones at the bottom left of the screen,
+                // dimmer for the ones at the model origin.
+                int f = (i == 0 ? 100 : 255);
                 if(hr.v == Request::HREQUEST_REFERENCE_XY.v) {
-                    glxColor3d(0, 0, f);
+                    glxColorRGB(RGB(0, 0, f));
                 } else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) {
-                    glxColor3d(f, 0, 0);
+                    glxColorRGB(RGB(f, 0, 0));
                 } else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) {
-                    glxColor3d(0, f, 0);
+                    glxColorRGB(RGB(0, f, 0));
                 } else {
-                    glxColor3d(0, 0.4, 0.4);
+                    glxColorRGB(Style::Color(Style::NORMALS));
                     if(i > 0) break;
                 }
 
@@ -396,7 +396,6 @@ void Entity::DrawOrGetDistance(void) {
                 LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis,-0.6)));
             }
             glxDepthRangeLockToFront(false);
-            glLineWidth(1.5);
             break;
         }
 
@@ -423,7 +422,7 @@ void Entity::DrawOrGetDistance(void) {
             Vector mp = p.Minus(us).Plus (vs);
 
             glLineWidth(1);
-            glxColor3d(0, 0.3, 0.3);
+            glxColorRGB(Style::Color(Style::NORMALS));
             glEnable(GL_LINE_STIPPLE);
             glLineStipple(3, 0x1111);
             if(!h.isFromRequest()) {
@@ -436,7 +435,6 @@ void Entity::DrawOrGetDistance(void) {
             LineDrawOrGetDistance(mm, mp);
             LineDrawOrGetDistance(mp, pp);
             glDisable(GL_LINE_STIPPLE);
-            glLineWidth(1.5);
 
             char *str = DescriptionString()+5;
             if(dogd.drawing) {
diff --git a/file.cpp b/file.cpp
index 353b0d5..814df0f 100644
--- a/file.cpp
+++ b/file.cpp
@@ -30,6 +30,7 @@ void SolveSpace::NewFile(void) {
     SK.constraint.Clear();
     SK.request.Clear();
     SK.group.Clear();
+    SK.style.Clear();
 
     SK.entity.Clear();
     SK.param.Clear();
@@ -150,6 +151,13 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = {
     { 'c',  "Constraint.disp.offset.z", 'f',    &(SS.sv.c.disp.offset.z)      },
     { 'c',  "Constraint.disp.style",    'x',    &(SS.sv.c.disp.style)         },
 
+    { 's',  "Style.h.v",                'x',    &(SS.sv.s.h.v)                },
+    { 's',  "Style.width",              'f',    &(SS.sv.s.width)              },
+    { 's',  "Style.widthHow",           'd',    &(SS.sv.s.widthHow)           },
+    { 's',  "Style.color",              'x',    &(SS.sv.s.color)              },
+    { 's',  "Style.visible",            'b',    &(SS.sv.s.visible)            },
+    { 's',  "Style.exportable",         'b',    &(SS.sv.s.exportable)         },
+
     { 0, NULL, NULL, NULL     },
 };
 
@@ -239,6 +247,14 @@ bool SolveSpace::SaveToFile(char *filename) {
         fprintf(fh, "AddConstraint\n\n");
     }
 
+    for(i = 0; i < SK.style.n; i++) {
+        sv.s = SK.style.elem[i];
+        if(sv.s.h.v >= Style::FIRST_CUSTOM) {
+            SaveUsingTable('s');
+            fprintf(fh, "AddStyle\n\n");
+        }
+    }
+
     // A group will have either a mesh or a shell, but not both; but the code
     // to print either of those just does nothing if the mesh/shell is empty.
 
@@ -363,6 +379,7 @@ bool SolveSpace::LoadFromFile(char *filename) {
     SK.group.Clear();
     SK.entity.Clear();
     SK.param.Clear();
+    SK.style.Clear();
     memset(&sv, 0, sizeof(sv));
 
     char line[1024];
@@ -383,20 +400,23 @@ bool SolveSpace::LoadFromFile(char *filename) {
             LoadUsingTable(key, val);
         } else if(strcmp(line, "AddGroup")==0) {
             SK.group.Add(&(sv.g));
-            memset(&(sv.g), 0, sizeof(sv.g));
+            ZERO(&(sv.g));
         } else if(strcmp(line, "AddParam")==0) {
             // params are regenerated, but we want to preload the values
             // for initial guesses
             SK.param.Add(&(sv.p));
-            memset(&(sv.p), 0, sizeof(sv.p));
+            ZERO(&(sv.p));
         } else if(strcmp(line, "AddEntity")==0) {
             // entities are regenerated
         } else if(strcmp(line, "AddRequest")==0) {
             SK.request.Add(&(sv.r));
-            memset(&(sv.r), 0, sizeof(sv.r));
+            ZERO(&(sv.r));
         } else if(strcmp(line, "AddConstraint")==0) {
             SK.constraint.Add(&(sv.c));
-            memset(&(sv.c), 0, sizeof(sv.c));
+            ZERO(&(sv.c));
+        } else if(strcmp(line, "AddStyle")==0) {
+            SK.style.Add(&(sv.s));
+            ZERO(&(sv.s));
         } else if(strcmp(line, VERSION_STRING)==0) {
             // do nothing, version string
         } else if(StrStartsWith(line, "Triangle ")      ||
diff --git a/glhelper.cpp b/glhelper.cpp
index 22760e9..b8112a8 100644
--- a/glhelper.cpp
+++ b/glhelper.cpp
@@ -44,6 +44,7 @@ void glxWriteTextRefCenter(char *str, Vector t, Vector u, Vector v,
 
 static void LineDrawCallback(void *fndata, Vector a, Vector b)
 {
+    glLineWidth(1);
     glBegin(GL_LINES);
         glxVertex3v(a);
         glxVertex3v(b);
@@ -95,26 +96,28 @@ void glxVertex3v(Vector u)
     glVertex3f((GLfloat)u.x, (GLfloat)u.y, (GLfloat)u.z);
 }
 
-void glxLockColorTo(double r, double g, double b)
+void glxLockColorTo(DWORD rgb)
 {
-    ColorLocked = false;    
-    glxColor3d(r, g, b);
+    ColorLocked = false;
+    glColor3d(REDf(rgb), GREENf(rgb), BLUEf(rgb));
     ColorLocked = true;
 }
 
 void glxUnlockColor(void)
 {
-    ColorLocked = false;    
+    ColorLocked = false;
 }
 
-void glxColor3d(double r, double g, double b)
+void glxColorRGB(DWORD rgb)
 {
-    if(!ColorLocked) glColor3d(r, g, b);
+    // Is there a bug in some graphics drivers where this is not equivalent
+    // to glColor3d? There seems to be...
+    glxColorRGBa(rgb, 1.0);
 }
 
-void glxColor4d(double r, double g, double b, double a)
+void glxColorRGBa(DWORD rgb, double a)
 {
-    if(!ColorLocked) glColor4d(r, g, b, a);
+    if(!ColorLocked) glColor4d(REDf(rgb), GREENf(rgb), BLUEf(rgb), a);
 }
 
 static void Stipple(BOOL forSel)
@@ -148,11 +151,11 @@ static void Stipple(BOOL forSel)
     }
 }
 
-static void StippleTriangle(STriangle *tr, BOOL s, double r, double g, double b)
+static void StippleTriangle(STriangle *tr, BOOL s, DWORD rgb)
 {
     glEnd();
     glDisable(GL_LIGHTING);
-    glColor3d(r, g, b);
+    glxColorRGB(rgb);
     Stipple(s);
     glBegin(GL_TRIANGLES);
         glxVertex3v(tr->a);
@@ -166,6 +169,9 @@ static void StippleTriangle(STriangle *tr, BOOL s, double r, double g, double b)
 
 void glxFillMesh(int specColor, SMesh *m, DWORD h, DWORD s1, DWORD s2)
 {
+    DWORD rgbHovered  = Style::Color(Style::HOVERED),
+          rgbSelected = Style::Color(Style::SELECTED);
+
     glEnable(GL_NORMALIZE);
     int prevColor = -1;
     glBegin(GL_TRIANGLES);
@@ -208,10 +214,10 @@ void glxFillMesh(int specColor, SMesh *m, DWORD h, DWORD s1, DWORD s2)
         if((s1 != 0 && tr->meta.face == s1) || 
            (s2 != 0 && tr->meta.face == s2))
         {
-            StippleTriangle(tr, TRUE, 1, 0, 0);
+            StippleTriangle(tr, TRUE, rgbSelected);
         }
         if(h != 0 && tr->meta.face == h) {
-            StippleTriangle(tr, FALSE, 1, 1, 0);
+            StippleTriangle(tr, FALSE, rgbHovered);
         }
     }
     glEnd();
@@ -283,13 +289,13 @@ void glxDebugPolygon(SPolygon *p)
             Vector a = (sc->l.elem[j]).p;
             Vector b = (sc->l.elem[j+1]).p;
 
-            glxLockColorTo(0, 0, 1);
+            glxLockColorTo(RGB(0, 0, 255));
             Vector d = (a.Minus(b)).WithMagnitude(-0);
             glBegin(GL_LINES);
                 glxVertex3v(a.Plus(d));
                 glxVertex3v(b.Minus(d));
             glEnd();
-            glxLockColorTo(1, 0, 0);
+            glxLockColorTo(RGB(255, 0, 0));
             glBegin(GL_POINTS);
                 glxVertex3v(a.Plus(d));
                 glxVertex3v(b.Minus(d));
@@ -327,7 +333,7 @@ void glxDebugMesh(SMesh *m)
     glxDepthRangeOffset(1);
     glxUnlockColor();
     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
-    glxColor4d(0, 1, 0, 1.0);
+    glxColorRGBa(RGB(0, 255, 0), 1.0);
     glBegin(GL_TRIANGLES);
     for(i = 0; i < m->l.n; i++) {
         STriangle *t = &(m->l.elem[i]);
diff --git a/groupmesh.cpp b/groupmesh.cpp
index b7d9512..53862ce 100644
--- a/groupmesh.cpp
+++ b/groupmesh.cpp
@@ -356,7 +356,8 @@ Group *Group::RunningMeshGroup(void) {
 void Group::DrawDisplayItems(int t) {
     int specColor;
     if(t == DRAWING_3D || t == DRAWING_WORKPLANE) {
-        specColor = RGB(25, 25, 25); // force the color to something dim
+        // force the color to something dim
+        specColor = Style::Color(Style::DIM_SOLID);
     } else {
         specColor = -1; // use the model color
     }
@@ -382,11 +383,9 @@ void Group::DrawDisplayItems(int t) {
         glDisable(GL_LIGHTING);
     }
     if(SS.GW.showEdges) {
-        glLineWidth(1);
         glxDepthRangeOffset(2);
-        glxColor3d(REDf  (SS.edgeColor),
-                   GREENf(SS.edgeColor), 
-                   BLUEf (SS.edgeColor));
+        glxColorRGB(Style::Color(Style::SOLID_EDGE));
+        glLineWidth(Style::Width(Style::SOLID_EDGE));
         glxDrawEdges(&displayEdges, false);
     }
 
@@ -418,14 +417,13 @@ void Group::Draw(void) {
         // it's just a nuisance.
         if(type == DRAWING_WORKPLANE) {
             glDisable(GL_DEPTH_TEST);
-            glxColor4d(1, 0, 0, 0.2);
-            glLineWidth(10);
+            glxColorRGBa(Style::Color(Style::DRAW_ERROR), 0.2);
+            glLineWidth (Style::Width(Style::DRAW_ERROR));
             glBegin(GL_LINES);
                 glxVertex3v(polyError.notClosedAt.a);
                 glxVertex3v(polyError.notClosedAt.b);
             glEnd();
-            glLineWidth(1);
-            glxColor3d(1, 0, 0);
+            glxColorRGB(Style::Color(Style::DRAW_ERROR));
             glxWriteText("not closed contour!",
                 polyError.notClosedAt.b, SS.GW.projRight, SS.GW.projUp,
                 NULL, NULL);
@@ -437,7 +435,7 @@ void Group::Draw(void) {
         // These errors occur at points, not lines
         if(type == DRAWING_WORKPLANE) {
             glDisable(GL_DEPTH_TEST);
-            glxColor3d(1, 0, 0);
+            glxColorRGB(Style::Color(Style::DRAW_ERROR));
             char *msg = (polyError.how == POLY_NOT_COPLANAR) ?
                             "points not all coplanar!" :
                             "contour is self-intersecting!";
@@ -447,7 +445,7 @@ void Group::Draw(void) {
             glEnable(GL_DEPTH_TEST);
         }
     } else {
-        glxColor4d(0, 0.1, 0.1, 0.5);
+        glxColorRGBa(Style::Color(Style::CONTOUR_FILL), 0.5);
         glxDepthRangeOffset(1);
         glxFillPolygon(&poly);
         glxDepthRangeOffset(0);
diff --git a/sketch.h b/sketch.h
index 8ff08a7..9aef0ec 100644
--- a/sketch.h
+++ b/sketch.h
@@ -597,28 +597,64 @@ public:
 
 
 class Style {
+public:
     int         tag;
     hStyle      h;
-    
-    static const int ACTIVE         = 1;
+  
+    // If an entity has no style, then it will be colored according to
+    // whether the group that it's in is active or not, whether it's
+    // construction or not, and so on.
+    static const int NO_STYLE       = 0;
+
+    static const int ACTIVE_GRP     = 1;
     static const int CONSTRUCTION   = 2;
-    static const int INACTIVE       = 3;
+    static const int INACTIVE_GRP   = 3;
     static const int DATUM          = 4;
     static const int SOLID_EDGE     = 5;
     static const int CONSTRAINT     = 6;
     static const int SELECTED       = 7;
     static const int HOVERED        = 8;
+    static const int CONTOUR_FILL   = 9;
+    static const int NORMALS        = 10;
+    static const int ANALYZE        = 11;
+    static const int DRAW_ERROR     = 12;
+    static const int DIM_SOLID      = 13;
 
     static const int FIRST_CUSTOM   = 0x1000;
 
-    static const int WIDTH_ABSOLUTE = 0;
-    static const int WIDTH_RELATIVE = 1;
-    static const int WIDTH_PIXELS   = 2;
+    NameStr     name;
+
+    static const int WIDTH_MM       = 0;
+    static const int WIDTH_PIXELS   = 1;
     double      width;
     int         widthHow;
     DWORD       color;
     bool        visible;
     bool        exportable;
+
+    // The default styles, for entities that don't have a style assigned yet,
+    // and for datums and such.
+    typedef struct {
+        hStyle  h;
+        char    *cnfPrefix;
+        DWORD   color;
+        double  width;
+    } Default;
+    static const Default Defaults[];
+
+    static char *CnfColor(char *prefix);
+    static char *CnfWidth(char *prefix);
+    static char *CnfPrefixToName(char *prefix);
+    static void CreateDefaultStyle(hStyle h);
+    static void FreezeDefaultStyles(void);
+    static void LoadFactoryDefaults(void);
+
+    static Style *Get(hStyle hs);
+    static DWORD Color(hStyle hs, bool forExport=false);
+    static float Width(hStyle hs);
+    static DWORD Color(int hs, bool forExport=false);
+    static float Width(int hs);
+    static hStyle ForEntity(hEntity he);
 };
 
 
diff --git a/solvespace.cpp b/solvespace.cpp
index 354e886..2f5333e 100644
--- a/solvespace.cpp
+++ b/solvespace.cpp
@@ -64,8 +64,6 @@ void SolveSpace::Init(char *cmdLine) {
     viewUnits = (Unit)CnfThawDWORD((DWORD)UNIT_MM, "ViewUnits");
     // Camera tangent (determines perspective)
     cameraTangent = CnfThawFloat(0.0f, "CameraTangent");
-    // Color for edges (drawn as lines for emphasis)
-    edgeColor = CnfThawDWORD(RGB(200, 200, 200), "EdgeColor");
     // Export scale factor
     exportScale = CnfThawFloat(1.0f, "ExportScale");
     // Export offset (cutter radius comp)
@@ -99,6 +97,10 @@ void SolveSpace::Init(char *cmdLine) {
     }
     RefreshRecentMenus();
 
+    // The default styles (colors, line widths, etc.) are also stored in the
+    // configuration file, but we will automatically load those as we need
+    // them.
+
     // Start with either an empty file, or the file specified on the
     // command line.
     NewFile();
@@ -144,8 +146,6 @@ void SolveSpace::Exit(void) {
     CnfFreezeDWORD((DWORD)viewUnits, "ViewUnits");
     // Camera tangent (determines perspective)
     CnfFreezeFloat((float)cameraTangent, "CameraTangent");
-    // Color for edges (drawn as lines for emphasis)
-    CnfFreezeDWORD(edgeColor, "EdgeColor");
     // Export scale (a float, stored as a DWORD)
     CnfFreezeFloat(exportScale, "ExportScale");
     // Export offset (cutter radius comp)
@@ -171,6 +171,9 @@ void SolveSpace::Exit(void) {
     // Show toolbar in the graphics window
     CnfFreezeDWORD(showToolbar, "ShowToolbar");
 
+    // And the default styles, colors and line widths and such.
+    Style::FreezeDefaultStyles();
+
     ExitNow();
 }
 
diff --git a/solvespace.h b/solvespace.h
index 2e6ebe0..9de5233 100644
--- a/solvespace.h
+++ b/solvespace.h
@@ -191,10 +191,10 @@ void glxWriteTextRefCenter(char *str, Vector t, Vector u, Vector v,
     glxLineFn *fn, void *fndata);
 double glxStrWidth(char *str);
 double glxStrHeight(void);
-void glxLockColorTo(double r, double g, double b);
+void glxLockColorTo(DWORD rgb);
 void glxUnlockColor(void);
-void glxColor3d(double r, double g, double b);
-void glxColor4d(double r, double g, double b, double a);
+void glxColorRGB(DWORD rgb);
+void glxColorRGBa(DWORD rgb, double a);
 void glxDepthRangeOffset(int units);
 void glxDepthRangeLockToFront(bool yes);
 
@@ -470,6 +470,7 @@ public:
     IdList<Group,hGroup>            group;
     IdList<CONSTRAINT,hConstraint>  constraint;
     IdList<Request,hRequest>        request;
+    IdList<Style,hStyle>            style;
 
     // These are generated from the above.
     IdList<ENTITY,hEntity>          entity;
@@ -481,7 +482,10 @@ public:
     inline Param   *GetParam  (hParam   h) { return param.  FindById(h); }
     inline Request *GetRequest(hRequest h) { return request.FindById(h); }
     inline Group   *GetGroup  (hGroup   h) { return group.  FindById(h); }
+    // Styles are handled a bit differently.
 };
+#undef ENTITY
+#undef CONSTRAINT
 
 class SolveSpace {
 public:
@@ -494,6 +498,7 @@ public:
         IdList<Request,hRequest>        request;
         IdList<Constraint,hConstraint>  constraint;
         IdList<Param,hParam>            param;
+        IdList<Style,hStyle>            style;
         hGroup                          activeGroup;
     } UndoState;
     static const int MAX_UNDO = 16;
@@ -522,7 +527,6 @@ public:
     double  chordTol;
     int     maxSegments;
     double  cameraTangent;
-    DWORD   edgeColor;
     float   exportScale;
     float   exportOffset;
     int     drawBackFaces;
@@ -582,6 +586,7 @@ public:
         Entity       e;
         Param        p;
         Constraint   c;
+        Style        s;
     } sv;
     static void MenuFile(int id);
     bool GetFilenameAndSave(bool saveAs);
diff --git a/style.cpp b/style.cpp
index aeebeb5..f63bed4 100644
--- a/style.cpp
+++ b/style.cpp
@@ -1,5 +1,167 @@
 #include "solvespace.h"
 
-//DWORD Style::Color(hStyle h) {
-//}
+const Style::Default Style::Defaults[] = {
+    { ACTIVE_GRP,   "ActiveGrp",    RGBf(1.0, 1.0, 1.0), 1.5, },
+    { CONSTRUCTION, "Construction", RGBf(0.1, 0.7, 0.1), 1.5, },
+    { INACTIVE_GRP, "InactiveGrp",  RGBf(0.5, 0.3, 0.0), 1.5, },
+    { DATUM,        "Datum",        RGBf(0.0, 0.8, 0.0), 1.5, },
+    { SOLID_EDGE,   "SolidEdge",    RGBf(0.8, 0.8, 0.8), 1.0, },
+    { CONSTRAINT,   "Constraint",   RGBf(1.0, 0.1, 1.0), 1.0, },
+    { SELECTED,     "Selected",     RGBf(1.0, 0.0, 0.0), 1.5, },
+    { HOVERED,      "Hovered",      RGBf(1.0, 1.0, 0.0), 1.5, },
+    { CONTOUR_FILL, "ContourFill",  RGBf(0.0, 0.1, 0.1), 1.0, },
+    { NORMALS,      "Normals",      RGBf(0.0, 0.4, 0.4), 1.0, },
+    { ANALYZE,      "Analyze",      RGBf(0.0, 1.0, 1.0), 1.0, },
+    { DRAW_ERROR,   "DrawError",    RGBf(1.0, 0.0, 0.0), 8.0, },
+    { DIM_SOLID,    "DimSolid",     RGBf(0.1, 0.1, 0.1), 1.0, },
+    { 0,            NULL,           0,                   0.0, },
+};
+
+char *Style::CnfColor(char *prefix) {
+    static char name[100];
+    sprintf(name, "Style_%s_Color", prefix);
+    return name;
+}
+char *Style::CnfWidth(char *prefix) {
+    static char name[100];
+    sprintf(name, "Style_%s_Width", prefix);
+    return name;
+}
+
+char *Style::CnfPrefixToName(char *prefix) {
+    static char name[100];
+    int i = 0, j = 0;
+    while(prefix[i] && j < 90) {
+        if(isupper(prefix[i]) && i != 0) {
+            name[j++] = '-';
+        }
+        name[j++] = tolower(prefix[i]);
+        i++;
+    }
+    name[j++] = '\0';
+    return name;
+}
+
+void Style::CreateDefaultStyle(hStyle h) {
+    const Default *d;
+    for(d = &(Defaults[0]); d->h.v; d++) {
+        if(d->h.v == h.v) break;
+    }
+    if(!d->h.v) {
+        // Not a default style; so just create it the same as our default
+        // active group entity style.
+        d = &(Defaults[0]);
+    }
+
+    Style ns;
+    ZERO(&ns);
+    ns.color      = CnfThawDWORD(d->color, CnfColor(d->cnfPrefix));
+    ns.width      = CnfThawFloat((float)(d->width), CnfWidth(d->cnfPrefix));
+    ns.widthHow   = WIDTH_PIXELS;
+    ns.visible    = true;
+    ns.exportable = true;
+    ns.name.strcpy(CnfPrefixToName(d->cnfPrefix));
+    ns.h          = h;
+
+    SK.style.Add(&ns);
+}
+
+void Style::LoadFactoryDefaults(void) {
+    const Default *d;
+    for(d = &(Defaults[0]); d->h.v; d++) {
+        Style *s = Get(d->h);
+
+        s->color      = d->color;
+        s->width      = d->width;
+        s->widthHow   = WIDTH_PIXELS;
+        s->visible    = true;
+        s->exportable = true;
+        s->name.strcpy(CnfPrefixToName(d->cnfPrefix));
+    }
+}
+
+void Style::FreezeDefaultStyles(void) {
+    const Default *d;
+    for(d = &(Defaults[0]); d->h.v; d++) {
+        CnfFreezeDWORD(Color(d->h), CnfColor(d->cnfPrefix));
+        CnfFreezeFloat((float)Width(d->h), CnfWidth(d->cnfPrefix));
+    }
+}
+
+//-----------------------------------------------------------------------------
+// Look up a style by its handle. If that style does not exist, then create
+// the style, according to our table of default styles.
+//-----------------------------------------------------------------------------
+Style *Style::Get(hStyle h) {
+    Style *s = SK.style.FindByIdNoOops(h);
+    if(s) {
+        // It exists, good.
+        return s;
+    } else {
+        // It doesn't exist; so we should create it and then return that.
+        CreateDefaultStyle(h);
+        return SK.style.FindById(h);
+    }
+}
+
+//-----------------------------------------------------------------------------
+// A couple of wrappers, so that I can call these functions with either an
+// hStyle or with the integer corresponding to that hStyle.v.
+//-----------------------------------------------------------------------------
+DWORD Style::Color(int s, bool forExport) {
+    hStyle hs = { s };
+    return Color(hs, forExport);
+}
+float Style::Width(int s) {
+    hStyle hs = { s };
+    return Width(hs);
+}
+
+//-----------------------------------------------------------------------------
+// Return the color associated with our style as 8-bit RGB.
+//-----------------------------------------------------------------------------
+DWORD Style::Color(hStyle h, bool forExport) {
+    Style *s = Get(h);
+    return s->color;
+}
+
+//-----------------------------------------------------------------------------
+// Return the width associated with our style in pixels..
+//-----------------------------------------------------------------------------
+float Style::Width(hStyle h) {
+    double r = 1.0;
+    Style *s = Get(h);
+    if(s->widthHow == WIDTH_MM) {
+        r = s->width * SS.GW.scale;
+    } else if(s->widthHow == WIDTH_PIXELS) {
+        r = s->width;
+    }
+    // This returns a float because glLineWidth expects a float, avoid casts.
+    return (float)r;
+}
+
+//-----------------------------------------------------------------------------
+// Return the appropriate style for our entity. If the entity has a style
+// explicitly assigned, then it's that style. Otherwise it's the appropriate
+// default style.
+//-----------------------------------------------------------------------------
+hStyle Style::ForEntity(hEntity he) {
+    Entity *e = SK.GetEntity(he);
+    // If the entity has a special style, use that. If that style doesn't
+    // exist yet, then it will get created automatically later.
+    if(e->style.v != 0) {
+        return e->style;
+    }
+
+    // Otherwise, we use the default rules.
+    hStyle hs;
+    if(e->group.v != SS.GW.activeGroup.v) {
+        hs.v = INACTIVE_GRP;
+    } else if(e->construction) {
+        hs.v = CONSTRUCTION;
+    } else {
+        hs.v = ACTIVE_GRP;
+    }
+    return hs;
+}
 
diff --git a/textscreens.cpp b/textscreens.cpp
index 02b5e50..c9a9cd2 100644
--- a/textscreens.cpp
+++ b/textscreens.cpp
@@ -606,41 +606,33 @@ void TextWindow::ScreenChangeColor(int link, DWORD v) {
     SS.TW.edit.meaning = EDIT_COLOR;
     SS.TW.edit.i = v;
 }
-void TextWindow::ScreenChangeEdgeColor(int link, DWORD v) {
-    char str[1024];
-    sprintf(str, "%.3f, %.3f, %.3f", 
-        REDf(SS.edgeColor), GREENf(SS.edgeColor), BLUEf(SS.edgeColor));
-
-    ShowTextEditControl(37, 3, str);
-    SS.TW.edit.meaning = EDIT_EDGE_COLOR;
-}
 void TextWindow::ScreenChangeChordTolerance(int link, DWORD v) {
     char str[1024];
     sprintf(str, "%.2f", SS.chordTol);
-    ShowTextEditControl(43, 3, str);
+    ShowTextEditControl(37, 3, str);
     SS.TW.edit.meaning = EDIT_CHORD_TOLERANCE;
 }
 void TextWindow::ScreenChangeMaxSegments(int link, DWORD v) {
     char str[1024];
     sprintf(str, "%d", SS.maxSegments);
-    ShowTextEditControl(47, 3, str);
+    ShowTextEditControl(41, 3, str);
     SS.TW.edit.meaning = EDIT_MAX_SEGMENTS;
 }
 void TextWindow::ScreenChangeCameraTangent(int link, DWORD v) {
     char str[1024];
     sprintf(str, "%.3f", 1000*SS.cameraTangent);
-    ShowTextEditControl(53, 3, str);
+    ShowTextEditControl(47, 3, str);
     SS.TW.edit.meaning = EDIT_CAMERA_TANGENT;
 }
 void TextWindow::ScreenChangeExportScale(int link, DWORD v) {
     char str[1024];
     sprintf(str, "%.3f", (double)SS.exportScale);
 
-    ShowTextEditControl(59, 3, str);
+    ShowTextEditControl(53, 3, str);
     SS.TW.edit.meaning = EDIT_EXPORT_SCALE;
 }
 void TextWindow::ScreenChangeExportOffset(int link, DWORD v) {
-    ShowTextEditControl(63, 3, SS.MmToString(SS.exportOffset));
+    ShowTextEditControl(57, 3, SS.MmToString(SS.exportOffset));
     SS.TW.edit.meaning = EDIT_EXPORT_OFFSET;
 }
 void TextWindow::ScreenChangeBackFaces(int link, DWORD v) {
@@ -674,7 +666,7 @@ void TextWindow::ScreenChangeCanvasSize(int link, DWORD v) {
 
         default: return;
     }
-    int row = 77, col;
+    int row = 71, col;
     if(v < 10) {
         row += v*2;
         col = 11;
@@ -711,12 +703,6 @@ void TextWindow::ShowConfiguration(void) {
             SS.lightIntensity[i], i, &ScreenChangeLightIntensity);
     }
 
-    Printf(false, "");
-    Printf(false, "%Ft edge color r,g,b%E");
-    Printf(false, "%Ba    %@, %@, %@ %Fl%Ll%f%D[change]%E",
-        REDf(SS.edgeColor), GREENf(SS.edgeColor), BLUEf(SS.edgeColor),
-        &ScreenChangeEdgeColor, 0);
-
     Printf(false, "");
     Printf(false, "%Ft chord tolerance (in screen pixels)%E");
     Printf(false, "%Ba   %2 %Fl%Ll%f%D[change]%E; now %d triangles",
@@ -997,16 +983,6 @@ void TextWindow::EditControlDone(char *s) {
             InvalidateGraphics();
             break;
         }
-        case EDIT_EDGE_COLOR: {
-            double r, g, b;
-            if(sscanf(s, "%lf, %lf, %lf", &r, &g, &b)==3) {
-                SS.edgeColor = RGB(r*255, g*255, b*255);
-            } else {
-                Error("Bad format: specify color as r, g, b");
-            }
-            SS.GenerateAll(0, INT_MAX);
-            break;
-        }
         case EDIT_EXPORT_SCALE: {
             Expr *e = Expr::From(s);
             if(e) {
diff --git a/ui.h b/ui.h
index ab6bf5b..0e3b0f2 100644
--- a/ui.h
+++ b/ui.h
@@ -77,10 +77,9 @@ public:
     static const int EDIT_CHORD_TOLERANCE       = 13;
     static const int EDIT_MAX_SEGMENTS          = 14;
     static const int EDIT_CAMERA_TANGENT        = 15;
-    static const int EDIT_EDGE_COLOR            = 16;
-    static const int EDIT_EXPORT_SCALE          = 17;
-    static const int EDIT_EXPORT_OFFSET         = 18;
-    static const int EDIT_CANVAS_SIZE           = 19;
+    static const int EDIT_EXPORT_SCALE          = 16;
+    static const int EDIT_EXPORT_OFFSET         = 17;
+    static const int EDIT_CANVAS_SIZE           = 18;
     // For the helical sweep
     static const int EDIT_HELIX_TURNS           = 20;
     static const int EDIT_HELIX_PITCH           = 21;
@@ -167,7 +166,6 @@ public:
     static void ScreenChangeChordTolerance(int link, DWORD v);
     static void ScreenChangeMaxSegments(int link, DWORD v);
     static void ScreenChangeCameraTangent(int link, DWORD v);
-    static void ScreenChangeEdgeColor(int link, DWORD v);
     static void ScreenChangeExportScale(int link, DWORD v);
     static void ScreenChangeExportOffset(int link, DWORD v);
 
diff --git a/undoredo.cpp b/undoredo.cpp
index 87c68d6..f48f59e 100644
--- a/undoredo.cpp
+++ b/undoredo.cpp
@@ -77,6 +77,9 @@ void SolveSpace::PushFromCurrentOnto(UndoStack *uk) {
     for(i = 0; i < SK.param.n; i++) {
         ut->param.Add(&(SK.param.elem[i]));
     }
+    for(i = 0; i < SK.style.n; i++) {
+        ut->style.Add(&(SK.style.elem[i]));
+    }
     ut->activeGroup = SS.GW.activeGroup;
 
     uk->write = WRAP(uk->write + 1, MAX_UNDO);
@@ -110,12 +113,14 @@ void SolveSpace::PopOntoCurrentFrom(UndoStack *uk) {
     SK.request.Clear();
     SK.constraint.Clear();
     SK.param.Clear();
+    SK.style.Clear();
 
     // And then do a shallow copy of the state from the undo list
     ut->group.MoveSelfInto(&(SK.group));
     ut->request.MoveSelfInto(&(SK.request));
     ut->constraint.MoveSelfInto(&(SK.constraint));
     ut->param.MoveSelfInto(&(SK.param));
+    ut->style.MoveSelfInto(&(SK.style));
     SS.GW.activeGroup = ut->activeGroup;
 
     // No need to free it, since a shallow copy was made above
@@ -150,6 +155,7 @@ void SolveSpace::UndoClearState(UndoState *ut) {
     ut->request.Clear();
     ut->constraint.Clear();
     ut->param.Clear();
+    ut->style.Clear();
     ZERO(ut);
 }